SmrtHub Operational Data Policy v1.0¶
Consolidated governance for Configuration, State, and Logging artifacts across all SmrtHub runtime components.
1. Purpose¶
Provide a single, authoritative specification for where and how SmrtHub runtime artifacts (configuration files, persisted state, and logs) are stored, named, written, rotated, versioned, and retired on Windows systems. This eliminates ambiguity, prevents path sprawl, and enables consistent tooling & support.
2. Scope¶
Applies to every executable, background service, agent, helper process, or shared library that creates or consumes: - Config data (authoritative user or system settings) - State data (ephemeral or recoverable operational persistence) - Text logs (human-friendly diagnostic streams) - Structured logs (machine-indexable JSON events)
Excludes: Transient temp files, in-memory caches, external database storage, and user-selected export locations.
3. Definitions¶
| Term | Definition |
|---|---|
| Component Name | The human or PascalCase identifier used in code / UI (e.g. SmrtHub, TriggerManager). |
| Slug | Lowercase kebab-case stable identifier derived from Component Name (e.g. smrt-hub, trigger-manager). |
| Config File | Authoritative settings managed by product or user; editing is intentional and controlled. |
| State File | Recoverable operational state (recent document lists, window geometry, job queues, caches). Loss is non-catastrophic. |
| Text Log | Line-oriented .log file optimized for quick manual inspection. |
| Structured Log | .log.json file containing one JSON object per line with enriched context. |
| AppData Scope | Roaming (syncs with user profile) or Local (machine-specific). |
4. Directory & Path Conventions¶
All standard operational data lives beneath a root determined by scope:
- Config/State root (Roaming): %AppData%/SmrtHub/Config/<slug>
- Config/State root (Local): %LocalAppData%/SmrtHub/Config/<slug>
- Shared config root (machine-wide): %ProgramData%/SmrtHub/Config/<slug> for credentials or evidence that must be consumed by both LocalSystem services and user-scoped tooling (e.g., Storage Guard shared secrets). These directories inherit ProgramData ACLs so services and standard users can read/write without elevation.
- Logs root (Roaming): %AppData%/SmrtHub/Logs/<slug>
Each component receives a dedicated subfolder under the appropriate category (Config or Logs).
4.1 Slug Normalization Algorithm¶
Input: arbitrary component name (camelCase, PascalCase, snake_case, space-delimited, dotted, mixed). Steps: 1. Replace underscores, spaces, dots with single hyphen 2. Insert hyphen boundaries between a lower->upper transition 3. Remove disallowed characters (only alphanumeric and hyphens allowed) 4. Lowercase result 5. Collapse multiple hyphens 6. Trim leading/trailing hyphens
Examples:
- SmrtHub → smrt-hub
- TriggerManager → trigger-manager
5. File Naming¶
Inside a component directory:
| Artifact | Pattern | Example |
| -------- | ------- | ------- |
| Config file | <slug>-config.json | cloud-providers-config.json |
| State file | <slug>-state.json | smrt-hub-state.json |
| Text log | <slug>-log.txt | smrt-hub-log.txt |
| Structured log | <slug>-log.json | smrt-hub-log.json |
| Custom / auxiliary | Explicitly named; MUST avoid collisions with reserved patterns | migration-report-2025-10.txt |
5.1 Extensions¶
- Config & State:
.json(default), configurable via API - Logs:
-log.txt(text),-log.json(structured JSON) - Custom: Must validate extension; avoid purely extension-less names.
6. Config & State Governance¶
| Concern | Policy |
|---|---|
| Format | UTF-8 JSON, no BOM, deterministic ordering (stable key ordering preferred) |
| Atomic Write | The Smrt.Config helpers provide an atomic write implementation (ConfigJson.AtomicWrite) which writes to a temporary file with WriteThrough, flushes, then performs an atomic replace. On existing files a single .bak backup is created via File.Replace as a best-effort safety net. |
| Version Field | Optional root property schemaVersion (integer) for forward migrations |
| Backups | ConfigJson.AtomicWrite creates a single <name>.bak backup when replacing an existing file. The library does not maintain multiple rolling backups. |
| Corruption Recovery | ConfigJson.LoadObject and Load<T> will attempt 3 retries with 30ms delays on IO/parse failures and return an empty/default result on persistent failure. There is no automatic fallback that replays the .bak file; callers needing that behavior must implement their own recovery logic. |
| Sensitive Data | Must not store secrets or credentials—use secure secret store mechanisms (not implemented by Smrt.Config). |
| Concurrency | A cross-process named mutex helper is provided (ConfigJson.WithNamedMutex) which callers can use to coordinate single-writer access. This helper is opt-in only; not automatically applied by GetFilePath/Save calls. |
| Validation | The library provides canonical JsonSerializerOptions (ConfigJson.Options) with camelCase naming and basic filename/extension validation. Schema validation of file contents is not implemented and should be performed by callers if required. |
7. Logging Governance¶
7.1 Log Separation¶
- Text log optimized for tailing & quick triage
- Structured log optimized for ingestion, indexing, analytics
7.2 Structured Log Event Shape (minimum fields)¶
Serilog JSON formatter output (actual implementation):
{
"Timestamp": "2025-10-17T09:45:27.1234567-04:00",
"Level": "Information",
"MessageTemplate": "Component started",
"Properties": {
"Component": "smrt-hub",
"Machine": "DESKTOP-ABC123",
"User": "username"
}
}
.Enrich.WithProperty() calls in application code.
7.3 Rotation & Retention¶
| Aspect | Policy |
|---|---|
| Roll Strategy | Daily rolling (new file per day via Serilog RollingInterval.Day) |
| Roll Pattern | <slug>-logYYYYMMDD.txt / <slug>-logYYYYMMDD.json (no hyphens before date, e.g., smrt-hub-log20251018.txt) |
| Max Segments | 14 days retained by default |
| Deletion | Files older than retention limit automatically pruned by Serilog |
7.4 Error Handling¶
- The
SmrtHub.Logging.Loggerwrites to Serilog file sinks which may surface IO exceptions to the calling process if log directory is inaccessible or disk is full. - Logger includes best-effort fallback: on write failures, attempts to log to
%TEMP%\SmrtHub-Logger-Fallback.txtand increments internal dropped-write counter. - No built-in in-memory ring buffer is implemented. Callers requiring guaranteed delivery should integrate a durable or buffered Serilog sink.
7.5 Privacy & PII¶
7.6 Windows Event Log Emission¶
- Emission to the Windows Event Log is disabled by default for developer scenarios to avoid noisy local logs and elevation requirements.
- In production, Event Log emission is enabled only when both conditions are met:
- The environment is
Production(or equivalent), and - The process is running with administrative privileges.
-
File-based sinks (text and structured JSON) remain primary and are always enabled unless explicitly disabled in configuration.
-
No raw user content (documents, extraction text) in structured logs
- Correlation identifiers must be opaque non-guessable tokens (GUIDv4 or ULID)
8. Security & Privacy Consolidated¶
| Area | Enforcement |
|---|---|
| Access Control | Rely on OS-level user profile isolation; no elevation required for writes |
| Secrets | Never persist secrets in config/state/logs; use Windows Credential Manager or DPAPI store |
| Sanitization | Apply sensitive field scrubbing (emails, tokens) before log emission |
9. Retention & Cleanup¶
| Artifact | Default Retention Guidance |
|---|---|
| Config | Persistent until uninstall or schema migration requires rewrite |
| State | May be pruned opportunistically when inactive > 90 days |
| Logs | Serilog auto-deletes files beyond RetainedFileCountLimit (default: 14 files); optionally purge log directories older than 30 days of inactivity |
| Backups | Single .bak backup created per atomic write; no automatic rotation. Callers requiring backup history must implement their own policy. |
10. Implementation Helpers (C#)¶
10.1 Config Path Resolution¶
Provided by SmrtHub.Config.ConfigPathResolver:
| Method | Purpose |
| ------ | ------- |
| GetFilePath(component, kind, scope, customName, jsonExtOverride) | Generic API for Config, State, or Custom files |
| GetComponentDirectory(component, scope) | Returns %AppData%/SmrtHub/Config/<slug> directory |
All helpers enforce slug normalization & directory creation.
10.2 Shared Data Path Resolution¶
Provided by SmrtHub.Config.SharedDataPathResolver:
| Method | Purpose |
| ------ | ------- |
| GetComponentDirectory(component) | Returns %ProgramData%/SmrtHub/Config/<slug> directory for machine-wide secrets/evidence |
| GetFilePath(component, fileName) | Resolves arbitrary file names under the shared component directory |
Use this helper when artifacts must be accessible to both LocalSystem services and user-scoped processes (e.g., Storage Guard shared secrets).
10.3 Logging Initialization¶
Provided by SmrtHub.Logging.Logger (Serilog-based):
| Method | Purpose |
| ------ | ------- |
| Initialize(componentName, level) | Sets up dual logging (JSON + text) under %AppData%/SmrtHub/Logs/<slug>. Default level: Information |
| Info(string message) | Log information-level message |
| Warning(string message) | Log warning-level message |
| Error(string message, Exception? ex = null) | Log error with optional exception |
| Debug(string message) | Log debug-level message |
| ExportHtmlLog() | Export JSON logs to HTML for diagnostics UI |
Logger auto-configures:
- Daily rolling interval
- 14-day retention
- Enrichment with Component, Machine, User properties
- JSON formatter (Serilog.Formatting.Json.JsonFormatter)
- Text template: [{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}
11. Reserved Names & Collisions¶
Reserved file name bases within a component directory (case-insensitive):
- <slug>-config.json
- <slug>-state.json
- <slug>-log.txt / <slug>-log.json
- Any pattern matching reserved plus rotation segments (e.g., <slug>-logYYYYMMDD.txt where YYYYMMDD is 8 digits)
Custom artifacts must NOT shadow these. A validation guard in GetFilePath prevents accidental collision when using AppFileKind.Custom.
12. Change Management & Versioning¶
| Change Type | Version Impact | Action |
|---|---|---|
| Add optional log enrichment key | Patch | Document in appendix C |
| Rename reserved file | Minor | Provide migration shim & dual-write until adoption |
| Modify structured event key semantics | Minor or Major | Dual field period or versioned field name |
| Remove required event key | Major | Requires at least one release cycle deprecation notice |
13. Testing & Validation Expectations¶
| Layer | Validation |
|---|---|
| Unit | Slug normalization, path generation correctness |
| Integration | Atomic write recoveries and backup chain |
| Load | Log rotation under rapid emission |
| Failure Injection | Simulated disk full & permission denial |
Appendix A: Component Path Examples¶
Assume %AppData% = C:\Users\User\AppData\Roaming, %LocalAppData% = C:\Users\User\AppData\Local.
Component: SmrtHub (slug smrt-hub)
- Config (Roaming): C:\Users\User\AppData\Roaming\SmrtHub\Config\smrt-hub\smrt-hub-config.json
- State (Roaming): C:\Users\User\AppData\Roaming\SmrtHub\Config\smrt-hub\smrt-hub-state.json
- Text Log (Roaming): C:\Users\User\AppData\Roaming\SmrtHub\Logs\smrt-hub\smrt-hub-log.txt
- Rolled: smrt-hub-log20251018.txt, smrt-hub-log20251017.txt, etc.
- Structured Log (Roaming): C:\Users\User\AppData\Roaming\SmrtHub\Logs\smrt-hub\smrt-hub-log.json
- Rolled: smrt-hub-log20251018.json, smrt-hub-log20251017.json, etc.
Appendix B: Reserved Patterns¶
Regex (illustrative):
Note: Serilog daily rolling produces suffixes like20251018 (8 digits, no separating hyphen).
Appendix C: Structured Log Event Example (Extended)¶
{
"Timestamp": "2025-10-17T09:45:27.1234567-04:00",
"Level": "Warning",
"MessageTemplate": "Fallback to backup config due to corruption",
"Properties": {
"Component": "smrt-hub",
"Machine": "DESKTOP-ABC123",
"User": "username",
"RecoveredFrom": "cloud-providers-config.json.bak",
"ElapsedMs": 17
},
"Exception": null
}
.bak recovery is not implemented in Smrt.Config; callers must implement if needed.
Appendix D: Rationale Summary¶
| Decision | Rationale |
|---|---|
| Separate config vs state naming | Simplifies grep, prevents accidental diff churn |
| Dual log modalities | Fast human triage + machine analytics without format compromise |
| Slug normalization | Stable cross-subsystem identifier (metrics, telemetry, file naming) |
| Atomic writes with backups | Resilience against power loss & partial writes |
| Consolidated policy file | Single source of truth reduces onboarding friction |
Maintainers: Engineering Productivity / Runtime Platform Team Initial Release: v1.0