Skip to content

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: - SmrtHubsmrt-hub - TriggerManagertrigger-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"
  }
}
Additional enrichment properties can be added via .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.Logger writes 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.txt and 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):

^(?i)([a-z0-9\-]+\-(config|state)\.json|[a-z0-9\-]+\-log\.(txt|json)(\d{8})?)$
Note: Serilog daily rolling produces suffixes like 20251018 (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
}
Note: This is an illustrative example. Automatic .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