string Name { get; }
Source
Gets the stable property name accepted in event payloads.
Captures AppSurface-owned product-intelligence events.
Implementations validate against AppSurfaceProductEventRegistry before forwarding events to any host-owned transport. The interface is intentionally vendor-neutral and must not require PostHog, OpenTelemetry, or a browser analytics SDK.
ValueTask CaptureAsync(AppSurfaceProductEvent productEvent, CancellationToken cancellationToken = default)
Source
Captures a product-intelligence event if the registry and options allow it.
productEventProduct event to capture.cancellationTokenCancellation token for the capture path.A task that completes when registered sinks have accepted or skipped the event.
Shared privacy-preserving validator for static and composed product-event registries.
AppSurfaceProductEventValidationResult Validate(AppSurfaceProductEvent productEvent, IReadOnlyDictionary<string, AppSurfaceProductEventContract> contractsByName, IReadOnlySet<string> forbiddenPropertyNames)
Source
Validates a product event against registered contracts and returns a privacy-safe sanitization result.
productEventEvent to validate.contractsByNameRegistered contracts keyed by event name using ordinal comparison.forbiddenPropertyNamesGlobal property names that must always be rejected.A result containing event validity, sanitized properties, rejected-property names, diagnostics, reason codes, and an optional safe fix hint.
Optional-property failures are rejected at the property level while required-property failures invalidate the event. Callers should gate sink emission on AppSurfaceProductEventValidationResult.IsValid and should not infer success from sanitized-property count alone.
Registers the surface-neutral AppSurface product-intelligence composition boundary.
The module registers a passive dispatcher and options type. It does not configure PostHog, OpenTelemetry, cookies, JavaScript autocapture, persistence, retention, access control, dashboards, or session replay.
void ConfigureServices(StartupContext context, IServiceCollection services)
Source
Registers product-intelligence services for the current startup graph.
contextStartup context for the current AppSurface composition pass.servicesService collection that receives product-intelligence services.void RegisterDependentModules(ModuleDependencyBuilder builder)
Source
Registers dependent modules required by product intelligence.
builderThe module dependency builder for the current startup graph.Receives validated AppSurface product-intelligence events for host-owned transport.
Sinks are optional. AppSurface does not configure persistence, retention, access control, dashboards, or vendor libraries. Register a sink only after deciding which host-owned analytics system should receive the sanitized event stream.
ValueTask CaptureAsync(AppSurfaceProductEvent productEvent, CancellationToken cancellationToken = default)
Source
Receives one sanitized product-intelligence event.
productEventSanitized product event with only registry-allowed properties and safe envelope fields.cancellationTokenCancellation token for the capture path.A task that completes when the sink has accepted the event.
Normalizes and sanitizes product-event metadata before registry validation or sink emission.
This internal helper separates constructor-time normalization, which trims and rejects empty required values, from emission-time sanitization, which may return null for unsafe optional envelope values. Methods that validate required input throw for empty or duplicate values; methods named Sanitize* return null when values are too long, contain unsafe characters, or look like tokens, cookies, secrets, connection strings, bearer headers, stack traces, full URLs, query strings, or fragments.
string RequireIdentifier(string value, string parameterName)
Source
Trims and requires a non-empty identifier-like value.
This method is for event and property names. It does not apply the stricter route or envelope sanitizers. Callers receive an ArgumentException for null, empty, or whitespace-only input.
string RequireText(string value, string parameterName)
Source
Trims and requires non-empty text.
Use this for registry descriptions, owners, examples, and allowed values. It throws for null, empty, or whitespace-only values, and otherwise returns the trimmed text unchanged.
string? NormalizeOptionalText(string? value)
Source
Trims optional text and normalizes null, empty, or whitespace-only values to null.
IReadOnlyDictionary<string, string> NormalizeProperties(IReadOnlyDictionary<string, string>? properties, string parameterName)
Source
Copies property keys and values into an ordinal, read-only dictionary.
Property names are required and trimmed through RequireIdentifier. Property values are trimmed, with null or whitespace-only values normalized to an empty string so optional string properties can still be represented. Later duplicate keys replace earlier values according to dictionary assignment semantics.
IReadOnlyList<AppSurfaceProductEventPropertyContract> NormalizeContracts(IEnumerable<AppSurfaceProductEventPropertyContract> contracts, string parameterName)
Source
Copies and validates a required property-contract sequence.
The returned list is a read-only wrapper over a private copy. Empty sequences and duplicate property names throw because every event contract must publish an explicit schema. Null property-contract entries throw as invalid contract metadata instead of surfacing later as registry or dispatcher failures.
IReadOnlyList<string> NormalizeTextList(IEnumerable<string> values, string parameterName)
Source
Copies and validates a required list of non-empty text values.
Values are trimmed with RequireText and returned as a read-only wrapper over a private copy. Duplicate handling is left to callers because some lists may allow duplicates while others do not.
IReadOnlyList<string> NormalizeOptionalTextList(IEnumerable<string>? values, string parameterName)
Source
Copies and validates an optional list of unique non-empty text values.
Null input becomes an empty read-only list. Non-null input is trimmed, duplicate values throw, and the returned value should be treated as immutable by registry contracts.
string? SanitizeEnvelopeIdentifier(string? value)
Source
Filters optional actor, session, or correlation identifiers for sink-safe emission.
Safe identifiers are short ASCII tokens containing only letters, digits, -, _, ., :, or |. Values with whitespace, PII-shaped punctuation, bearer headers, tokens, cookies, secrets, connection strings, or stack-trace text are dropped by returning null.
string? SanitizeRoute(string? value)
Source
Filters optional route templates or surface names for sink-safe emission.
Routes may contain path separators and route-template braces, but full URLs, protocol-relative URLs, query strings, fragments outside route tokens, unsafe characters, high-risk value shapes, and overlong routes are dropped. Do not pass concrete route values that contain user, tenant, object, or channel identifiers.
string SanitizeDiagnosticPropertyName(string value)
Source
Filters property names before they are included in safe diagnostics.
Product-event property keys are caller input. Diagnostic output must not echo raw keys that look like URLs, email addresses, credentials, secrets, bearer headers, or other high-risk values.
string SanitizeDiagnosticEventName(string value)
Source
Filters event names before they are included in safe diagnostics.
Product-event names may come from callers before registry matching. Diagnostic output must not echo raw names that look like URLs, email addresses, credentials, secrets, bearer headers, or other high-risk values.
string SanitizeDiagnosticOwner(string value)
Source
Filters owner/source labels before they are included in safe diagnostics.
Owners are contract metadata rather than event payloads, but registration failures are often copied into issue trackers and CI logs. This keeps those diagnostics descriptive without echoing accidental addresses, URLs, or credential-shaped labels.
bool IsSafeDiagnosticIdentifier(string value)
Source
Returns whether a caller-supplied identifier can be emitted in diagnostics without scrubbing.
valueIdentifier to inspect.true when diagnostics may include the identifier as-is.
bool ContainsForbiddenValueShape(string value)
Source
Returns whether a value looks like a secret, credential, connection string, bearer header, or stack trace.
bool ContainsQueryOrFragment(string value)
Source
Returns whether a route-like value contains a query string or fragment outside a route token.
Describes one allowed property on a registered AppSurface product event.
string Name { get; }
Source
Gets the stable property name accepted in event payloads.
string Description { get; }
Source
Gets the human-readable purpose and expected value shape.
AppSurfaceProductEventSensitivity Sensitivity { get; }
Source
Gets the privacy sensitivity classification.
AppSurfaceProductEventCardinality Cardinality { get; }
Source
Gets the expected cardinality budget.
bool Required { get; }
Source
Gets a value indicating whether capture should drop the event when the property is absent.
IReadOnlyList<string> AllowedValues { get; }
Source
Gets the optional bounded set of allowed values for low-cardinality dimensions.
int MaxLength { get; }
Source
Gets the maximum allowed emitted value length.
AppSurfaceProductEventValueShape ValueShape { get; }
Source
Gets the sanitized value shape accepted for this property.
The registry uses this metadata to validate built-in and host-registered contract packs consistently. It no longer infers integer or token behavior from property names.
Validates product-intelligence options that affect registry composition.
This validator builds the same immutable registry as the dispatcher path so duplicate names and unsafe custom contracts fail during service-provider validation when hosts enable options validation on build/start.
Exception thrown when product-intelligence contract packs cannot be safely composed.
Registration exceptions describe contract names, owners, and schema conflicts, but never event payload values. They are intended to fail startup or service-provider validation before capture paths begin dropping events.
Configures AppSurface product-intelligence capture.
AppSurfaceProductIntelligenceOptions EnableExperimentalEvents()
Source
Allows experimental event contracts to be emitted.
The same options instance for fluent configuration.
This method is a one-way toggle on the same options instance: it sets ExperimentalEventsEnabled to true and does not provide a matching disable operation. Avoid sharing and mutating one options instance across unrelated components when that one-way behavior would be surprising.
AppSurfaceProductIntelligenceOptions EnableExperimentalEvents(params string[] eventNames)
Source
Allows selected experimental event contracts to be emitted.
eventNamesRegistered experimental event names to allow.The same options instance for fluent configuration.
Use this when a host or package wants a narrow product-intelligence surface without enabling every experimental AppSurface event. Blank names are rejected during configuration so typos do not silently widen or narrow capture. Registered sinks still receive only events that pass AppSurfaceProductEventRegistry.Validate(AppSurfaceProductEvent).
AppSurfaceProductIntelligenceOptions RegisterEventContracts(IEnumerable<AppSurfaceProductEventContract> contracts)
Source
Registers host/package event contracts for the composed product-intelligence registry.
contractsContracts to compose with built-in AppSurface contracts.The same options instance for fluent configuration.
Use this for reusable domain contract packs such as SkoolieLaunchIntelligenceContracts.All. The registry keeps normal capture best-effort while preserving AppSurface privacy protections: globally forbidden property names, forbidden value shapes, lifecycle gating, and safe diagnostics. Null contract sequences and null elements throw during configuration; empty sequences are a no-op.
AppSurfaceProductIntelligenceOptions RegisterEventContracts(params AppSurfaceProductEventContract[] contracts)
Source
Registers host/package event contracts for the composed product-intelligence registry.
contractsContracts to compose with built-in AppSurface contracts.The same options instance for fluent configuration.
AppSurfaceProductIntelligenceOptions ThrowOnInvalidEvents()
Source
Throws a safe development exception when an event would otherwise be dropped.
The same options instance for fluent configuration.
This mode throws only for invalid events or disabled experimental events. It does not throw when optional properties are sanitized away and the remaining event is still valid for sink emission.
bool IsExperimentalEventEnabled(string eventName)
Source
Determines whether one experimental event name is allowed by this options instance.
eventNameThe experimental event name to test.true when all experimental events or the specific event name are enabled.
bool ExperimentalEventsEnabled { get; set; }
Source
Gets a value indicating whether experimental event contracts may be emitted.
The default is false. Keep this disabled for hosts that have not yet chosen product-event sinks, retention rules, and access controls. Enable it only during explicit dogfooding or host configuration where experimental AppSurface event contracts are expected to flow.
IReadOnlySet<string> EnabledExperimentalEventNames { get; }
Source
Gets experimental event names that are enabled without enabling every experimental contract.
This allowlist lets package integrations enable one product area, such as AppSurface Docs search-quality metrics, without turning on unrelated experimental dogfood events. Values are event names from AppSurfaceProductEventRegistry. The returned set is a copy so callers cannot mutate options state after configuration.
IReadOnlyList<AppSurfaceProductEventContract> RegisteredEventContracts { get; }
Source
Gets host/package event contracts registered for the composed product-intelligence registry.
The returned list is a copy so callers cannot mutate options state after configuration. The default registry composes these contracts with built-in AppSurface contracts during service construction. Identical semantic duplicates are idempotent; incompatible duplicate event names fail registry construction with safe diagnostics.
bool ThrowOnInvalidEventsEnabled { get; set; }
Source
Gets a value indicating whether invalid events should throw instead of being silently dropped.
The default is false so capture remains best-effort in production request paths. Enable this in development or tests when missing registrations and malformed events should fail loudly. The exception is safe by design and never includes raw property values.
Exception thrown by development diagnostics when a product event would be dropped.
The exception is safe to surface in development and tests. It includes event names only after constructor normalization, property names only from registry diagnostics, stable reason codes, a documentation path, and a fix hint. It never includes raw property values or serialized event payloads.
string EventName { get; }
Source
Gets the safe event name that failed validation.
IReadOnlyList<AppSurfaceProductEventValidationFailureReason> ReasonCodes { get; }
Source
Gets stable safe reason codes explaining why the event was dropped.
IReadOnlyList<string> RejectedProperties { get; }
Source
Gets safe property names rejected during validation.
string DocsLink { get; }
Source
Gets a repository documentation path with registration and validation guidance.
string FixHint { get; }
Source
Gets a safe remediation hint that does not echo event payload values.
Identifies safe, stable product-event validation failure categories.
Reason codes are intentionally coarse so they can be logged, surfaced in development exceptions, or used by tests without exposing raw event payload values.
Registers AppSurface product-intelligence services.
IServiceCollection AddAppSurfaceProductIntelligence(this IServiceCollection services, Action<AppSurfaceProductIntelligenceOptions>? configure = null)
Source
Adds the AppSurface product-intelligence dispatcher and options.
servicesService collection to configure.configureOptional options callback. Call AppSurfaceProductIntelligenceOptions.EnableExperimentalEvents() to emit dogfood events.The original service collection.
Hosts own IAppSurfaceProductIntelligenceSink registration, transport, retention, and vendor setup. This extension registers an immutable composed IAppSurfaceProductEventRegistry and the default dispatcher with TryAddScoped, so repeated calls do not replace an existing IAppSurfaceProductIntelligence registration. Repeated calls can still accumulate options configuration delegates, but identical contract registrations are idempotent. Call AppSurfaceProductIntelligenceOptions.EnableExperimentalEvents() only when experimental dogfood events should be emitted to host-owned sinks.
Default product-intelligence dispatcher registered by AppSurface.
ValueTask CaptureAsync(AppSurfaceProductEvent productEvent, CancellationToken cancellationToken = default)
Source
Invalid events are discarded silently after IAppSurfaceProductEventRegistry.Validate(AppSurfaceProductEvent). Experimental contracts are also ignored unless AppSurfaceProductIntelligenceOptions.ExperimentalEventsEnabled enables the whole experimental surface or AppSurfaceProductIntelligenceOptions.IsExperimentalEventEnabled(string) enables that specific event name through the per-event allowlist. Registered sinks run sequentially, and non-cancellation sink failures are swallowed so product-intelligence capture cannot break request paths. Callers should treat this method as best-effort delivery and should not rely on it to surface sink errors or guarantee that a downstream analytics provider accepted the event.
Describes one AppSurface product-intelligence event instance.
AppSurface product events are semantic product signals, not logs, traces, request captures, or vendor-specific analytics payloads. Hosts own transport, retention, access control, and downstream product analytics setup.
AppSurfaceProductEvent WithSanitizedEnvelope(IReadOnlyDictionary<string, string> properties)
Source
Creates a new event with sanitized envelope metadata and replacement properties.
propertiesReplacement event properties that have already passed registry sanitization.A new event with sanitized envelope metadata.
This internal API does not mutate the current event. The supplied properties replace the original property set, while ActorId, SessionId, and CorrelationId are filtered through AppSurfaceProductEventMetadata.SanitizeEnvelopeIdentifier and Route is filtered through AppSurfaceProductEventMetadata.SanitizeRoute. Sanitization may drop optional envelope values by returning null when they are too long, contain unsafe characters, or look like secrets, PII, bearer headers, query strings, fragments, full URLs, or concrete high-cardinality route values. Callers must supply an already-sanitized property dictionary and must not assume original envelope values survive this step.
string Name { get; }
Source
Gets the registered event name.
DateTimeOffset Timestamp { get; }
Source
Gets the timestamp supplied by the caller.
IReadOnlyDictionary<string, string> Properties { get; }
Source
Gets property values copied with ordinal keys.
string? ActorId { get; }
Source
Gets the optional host-normalized actor identifier.
string? SessionId { get; }
Source
Gets the optional host-normalized session identifier.
string? CorrelationId { get; }
Source
Gets the optional low-cardinality correlation identifier for trace or request joins.
string? Route { get; }
Source
Gets the optional route template or surface name.
Describes the lifecycle promise attached to an AppSurface product event contract.
The numeric values are explicit because this public enum may be serialized, persisted, or used in generated registry documentation. New values should be appended without changing the values documented here.
Describes a registered AppSurface product-event contract.
string Name { get; }
Source
Gets the stable event name.
AppSurfaceProductEventLifecycle Lifecycle { get; }
Source
Gets the current lifecycle state.
string Purpose { get; }
Source
Gets the decision the event is meant to support.
string Owner { get; }
Source
Gets the owning AppSurface component or package.
string RetentionExpectation { get; }
Source
Gets the expected retention class for downstream sinks.
IReadOnlyList<AppSurfaceProductEventPropertyContract> Properties { get; }
Source
Gets the allowed property schema.
IReadOnlyList<string> ForbiddenExamples { get; }
Source
Gets examples of values or property shapes that must not be captured.
Describes the expected cardinality budget for a product-event property.
The numeric values are explicit because this public enum may be serialized, persisted, or used in generated registry documentation. New values should be appended without changing the values documented here.
Describes registry validation and sanitization for one product event.
AppSurfaceProductEventContract? Contract { get; }
Source
Gets the matched event contract, or null when the event is not registered.
bool IsValid { get; }
Source
Gets a value indicating whether the event may be emitted after sanitization.
IReadOnlyDictionary<string, string> SanitizedProperties { get; }
Source
Gets properties that may be emitted to a sink.
IReadOnlyList<string> RejectedProperties { get; }
Source
Gets property names rejected during validation.
IReadOnlyList<string> Diagnostics { get; }
Source
Gets safe diagnostics that describe rejected schema decisions without echoing rejected values.
IReadOnlyList<AppSurfaceProductEventValidationFailureReason> ReasonCodes { get; }
Source
Gets stable safe reason codes for validation decisions that rejected the event or individual properties.
string? FixHint { get; }
Source
Gets a safe remediation hint when validation can suggest a direct fix.
Provides the composed product-intelligence event-contract registry used by dispatchers and diagnostics.
The default implementation is immutable after construction and composes AppSurface built-in contracts with host/package contract packs registered through AppSurfaceProductIntelligenceOptions.RegisterEventContracts(IEnumerable{AppSurfaceProductEventContract}). Replacing this service is a supported advanced escape hatch, but replacement registries must preserve the same privacy boundary: forbidden property names, forbidden value shapes, lifecycle gating, and safe diagnostics.
AppSurfaceProductEventContract? Find(string name)
Source
Finds a registered contract by event name.
nameEvent name to look up.The matching contract, or null when no contract is registered.
AppSurfaceProductEventValidationResult Validate(AppSurfaceProductEvent productEvent)
Source
Validates and sanitizes a product event against the composed registry.
productEventEvent instance to validate.Validation result with safe diagnostics and sanitized properties.
IReadOnlyList<AppSurfaceProductEventContract> All { get; }
Source
Gets every composed event contract available for validation and diagnostics.
IReadOnlySet<string> ForbiddenProperties { get; }
Source
Gets globally forbidden property names that are always dropped from emitted payloads.
Source-of-truth registry for AppSurface product-intelligence event contracts.
AppSurfaceProductEventContract? Find(string name)
Source
Finds a registered contract by event name.
nameEvent name to look up.The matching contract, or null when no contract is registered.
AppSurfaceProductEventValidationResult Validate(AppSurfaceProductEvent productEvent)
Source
Validates and sanitizes a product event against the typed registry.
productEventEvent instance to validate.Validation result with safe diagnostics and sanitized properties.
IReadOnlyList<AppSurfaceProductEventContract> All { get; }
Source
Gets every registered AppSurface product event contract.
IReadOnlySet<string> ForbiddenProperties { get; }
Source
Gets globally forbidden property names that are always dropped from emitted payloads.
Classifies the privacy sensitivity of a product-event property.
The numeric values are explicit because this public enum may be serialized, persisted, or used in generated registry documentation. New values should be appended without changing the values documented here.
Default immutable composed product-event registry.
The registry composes AppSurface built-in contracts with host/package contracts registered through options. Built-in contracts are trusted framework contracts. Custom contracts keep the same runtime privacy sanitizers and are also checked for registration-time constraints: no sensitive properties, no high-cardinality properties, no globally forbidden property names, and no incompatible duplicate event names.
Describes the sanitized value shape accepted for a product-event property.
Value shapes are part of the privacy boundary for built-in and host-registered contract packs. They tell the registry how to validate a property value without relying on property-name conventions. New values should be appended without changing the numeric values documented here because this public enum may be serialized or used in generated registry documentation.