DIAGNOSTICS.md

RowGuard Rejection Diagnostics

Purpose

Diagnostics provide structured, machine-readable information about rejection handling without changing validation behavior. They are intended for debugging, observability, auditing, metrics, tracing, and performance analysis.

Unlike exceptions, diagnostics describe what happened, not merely what failed.


Design Principles

  • Diagnostics are structured, not formatted strings.

  • Diagnostic generation must never change execution results.

  • Stable diagnostic codes are preferred over human-readable messages.

  • Sensitive values must respect redaction policies.

  • Sync, async, buffered, and streaming execution expose equivalent diagnostics.


Lifecycle

Rejected Row
      │
      ▼
Rejection Policy
      │
      ▼
Diagnostics
      │
      ├── Metrics
      ├── Logs
      ├── Traces
      ├── QueryResult
      └── Quarantine

Diagnostic Event

Suggested structure:

@dataclass(frozen=True, slots=True)
class Diagnostic:
    code: str
    severity: Literal["debug", "info", "warning", "error"]
    timestamp: datetime
    execution_id: str
    row_index: int | None
    metadata: Mapping[str, object]

Diagnostics should be immutable.


Standard Diagnostic Codes

Validation:

  • validation.started

  • validation.accepted

  • validation.rejected

  • validation.unexpected_exception

Rejection:

  • rejection.raise

  • rejection.collect

  • rejection.skip

  • rejection.callback

  • rejection.quarantine

  • rejection.threshold_exceeded

Callbacks:

  • callback.started

  • callback.completed

  • callback.failed

Quarantine:

  • quarantine.write_started

  • quarantine.write_succeeded

  • quarantine.write_failed

Streaming:

  • stream.started

  • stream.completed

  • stream.cancelled


Severity

Suggested mapping:

Severity

Meaning

debug

Internal execution detail

info

Normal lifecycle event

warning

Recoverable rejection

error

Execution failure


Metrics

Recommended counters:

  • rows_processed

  • rows_accepted

  • rows_rejected

  • callback_failures

  • quarantine_failures

  • rejection_rate

Recommended timers:

  • validation_duration

  • callback_duration

  • quarantine_duration

  • total_execution_duration

Avoid high-cardinality metric labels.


Logging

Diagnostics should support structured logging.

Example:

{
  "event": "validation.rejected",
  "model": "UserRead",
  "row_index": 42,
  "error_count": 2
}

Raw row values should not be logged by default.


Tracing

Suggested trace attributes:

  • rowguard.execution_id

  • rowguard.model

  • rowguard.rejection_policy

  • rowguard.rows_processed

  • rowguard.rows_rejected

Tracing should avoid embedding rejected payloads.


QueryResult Integration

QueryResult may expose:

result.diagnostics

Applications can inspect:

  • warnings

  • policy changes

  • callback timings

  • rejection summaries

Diagnostics are supplemental and should not replace statistics.


Streaming

Streaming diagnostics should be emitted incrementally.

Events should preserve processing order.

Closing a stream should emit a final completion or cancellation event.


Async

Async execution should produce the same diagnostic events and codes as synchronous execution.


Privacy

Diagnostics must obey:

  • row retention settings

  • redaction policies

  • metadata-only modes

Sensitive values should never appear unless explicitly configured.


Testing

Tests should verify:

  • stable diagnostic codes

  • ordering

  • timestamps

  • severity

  • callback diagnostics

  • quarantine diagnostics

  • streaming diagnostics

  • async parity

  • redaction


MVP Scope

Initial implementation:

  • immutable Diagnostic objects

  • stable event codes

  • execution IDs

  • row indices

  • structured metadata

  • QueryResult diagnostics

  • logging integration

  • metrics integration

Deferred:

  • OpenTelemetry exporters

  • live dashboards

  • external diagnostic plugins

  • distributed tracing helpers


Design Principles

  • Diagnostics are observable facts.

  • Stable codes are part of the public API.

  • Diagnostics never change execution behavior.

  • Privacy is enforced before emission.

  • Every rejection should be explainable through structured events.