yatta/observe
Tracing, metrics, issues, incident analysis and the dashboard.
89 exported symbols and 293 members, read from src/types/observe.ts.
Construct
attachSubsystems
functionattachSubsystems(subsystems: { db?: unknown; auth?: unknown; jobs?: unknown; cache?: unknown; storage?: unknown; mail?: unknown; realtime?: unknown; }): voidcomputeApdexAndPercentiles
functioncomputeApdexAndPercentiles(transactions: Array<{ durationMs: number; status: number; }>, T?: )computeSmartFingerprint
functioncomputeSmartFingerprint(error: unknown, frames: ParsedStackFrame[]): stringcreateObserver
functionCreates and starts a new .
Always constructs. It used to return a cached instance on every call after the first, which meant the second caller's config was silently discarded *and* config validation was skipped entirely on that path — so a typo in the second observer's config threw nothing and simply had no effect. Two observers then shared one set of counters, and a test asserting against a fresh observer read the first one's state. For a process-wide shared instance use or the proxy, which is what most applications want.
createObserver(config?: ObserveConfig): ObserverescapeHtml
functionescapeHtml(value: unknown): stringformatTraceparent
functionformatTraceparent(ctx: TraceContext): stringgenerateSpanId
functiongenerateSpanId(): stringgenerateTraceId
functiongenerateTraceId(): stringgetDefaultObserver
functiongetDefaultObserver(): ObserverisAlreadyObserved
functionisAlreadyObserved(error: unknown): booleanmarkObserved
functionmarkObserved(error: unknown): voidmaskHeaders
functionmaskHeaders(headers: Headers | Record<string, string> | null | undefined): Record<string, string>maskPayload
functionmaskPayload<T>(data: T, seen?: , depth?: , maxDepth?: ): TobserveDashboard
functionobserveDashboard(obs?: Observer)parseStackTrace
functionparseStackTrace(stack?: string): ParsedStackFrame[]parseTraceparent
functionparseTraceparent(header: string | null | undefined): TraceContext | nullpercentile
functionNearest-rank percentile calculation. Expects a sorted array.
percentile(sorted: number[], p: number): numberreport
functionreport(error: unknown, request?: Request): voidsanitizeUrlString
functionsanitizeUrlString(rawUrl: string): stringCounter
classclass Counter1 member
addadd(delta: number, labels?: Record<string, string>): number
ErrorReporter
classclass ErrorReporter8 members
issuespropertyissues:crumbsStoragepropertycrumbsStorage:globalCrumbspropertyglobalCrumbs: Breadcrumb[]breadcrumbbreadcrumb(category: Breadcrumb["category"], message: string, data?: Record<string, unknown>, level?: Breadcrumb["level"]): voidcapturecapture(error: unknown, context?: { request?: any; route?: string; handled?: boolean; }): ErrorIssueingestingest(occurrence: ErrorOccurrence): ErrorIssueGroups a pre-built occurrence into the issue queue.
applyActionapplyAction(fingerprint: string, action: "resolve" | "ignore" | "unresolve")statsstats()
Gauge
classclass Gauge2 members
setset(value: number, labels?: Record<string, string>): numbergetget(labels?: Record<string, string>): number
HardwareEngine
classclass HardwareEngine9 members
histogrampropertyhistogram?: ReturnType<typeof monitorEventLoopDelay>lastCpupropertylastCpu:lastCpuTimepropertylastCpuTime:timerpropertytimer?: ReturnType<typeof setInterval>currentCpuPercentpropertycurrentCpuPercent:startstart(): voidstopstop(): voidsamplesample(): voidgetSnapshotgetSnapshot(): HardwareMetrics
Histogram
classclass Histogram4 members
statepropertystate:readAllreadAll(): Array<{ labels: string; count: number; sum: number; min: number; max: number; buckets: number[]; bucketCounts: number[]; samples: number[]; }>Reads every recorded label set back, for .
Returns copies rather than the live state, so a caller cannot mutate the registry's buckets or samples through what it is handed.
observeobserve(value: number, labels?: Record<string, string>): voidstartTimerstartTimer(labels?: Record<string, string>): () => number
Logger
classclass Logger5 members
loglog(level: LogRecord["level"], message: string, context?: Record<string, unknown>): voiddebugdebug(msg: string, ctx?: Record<string, unknown>)infoinfo(msg: string, ctx?: Record<string, unknown>)warnwarn(msg: string, ctx?: Record<string, unknown>)errorerror(msg: string, ctx?: Record<string, unknown>)
MetricsRegistry
classclass MetricsRegistry7 members
counterspropertycounters:gaugespropertygauges:histogramspropertyhistograms:countercounter(name: string, description?: ): Countergaugegauge(name: string, description?: ): Gaugehistogramhistogram(name: string, description?: ): Histogramsnapshotsnapshot(): { counters: Array<{ name: string; description: string; values: Record<string, number>; }>; gauges: Array<{ name: string; description: string; values: Record<string, number>; }>; histograms: Array<{ name: string; description: string; values: Array<{ labels: string; count: number; sum: number; min: number; max: number; buckets: number[]; bucketCounts: number[]; samples: number[]; }>; }>; }Reads every recorded metric back.
The registry previously only accepted writes: a counter could be incremented but there was no supported way to read its value, so a metric could be recorded, exported nowhere, and never checked. Label sets are returned as strings rather than parsed, because the registry does not keep the original label object and reconstructing one would mean encoding keys back into an object with a different key order.
ObserveError
classclass ObserveError extends ErrorObserver
classclass Observer80 members
configpropertyconfig: ObserveConfigservicepropertyservice: stringreleasepropertyrelease?: stringenvironmentpropertyenvironment: stringversionpropertyversion: stringtracerpropertytracer: Tracerreleasespropertyreleases:safeActionspropertysafeActions:goldenspropertygoldens:Traces developers have marked as known-good baselines.
sourceResolverpropertysourceResolver?: TraceToCodeResolverSource lookup for trace-to-code.
Replaceable, and unset by default: a production build usually has no sources on disk, and reading files off a running server is not something to enable implicitly.
historypropertyhistory:Retained metric samples, bounded — an unbounded series is itself a leak.
baselinespropertybaselines:metricspropertymetrics: MetricsRegistryerrorspropertyerrors: ErrorReporterlogpropertylog: Loggerhardwarepropertyhardware: HardwareEnginerouterpropertyrouter: ObserverRouterspanspropertyspans: RingBuffer<Span>errorLogpropertyerrorLog: RingBuffer<ErrorOccurrence>logspropertylogs: RingBuffer<LogRecord>transactionspropertytransactions: RingBuffer<HttpTransaction>slowQueriespropertyslowQueries: RingBuffer<SlowQueryRecord>auditLogspropertyauditLogs: RingBuffer<AuditRecord>attachedSubsystemspropertyattachedSubsystems: Record<string, any>startedAtpropertystartedAt:safetyHandlersAttachedpropertysafetyHandlersAttached:installedpropertyinstalled:sseSubscriberspropertysseSubscribers:heartbeatTimerpropertyheartbeatTimer?: ReturnType<typeof setInterval>cachedDbStatspropertycachedDbStats: { lastChecked: number; tables: Array<{ name: string; rows: number; }>; }slowQueryThresholdMsgetterslowQueryThresholdMs: numberSingle source of truth for the slow-query threshold.
v5 instrumented at 50ms but filtered the buffer at 100ms, so queries in the 50–100ms band were announced as slow yet never recorded. Both sides now read this value.
onUnhandledRejectionpropertyonUnhandledRejection:onUncaughtExceptionpropertyonUncaughtException:startstart(): voidanalyzeIncidentanalyzeIncident(fingerprint: string): IncidentAnalysis | nullCorrelate everything recorded about one issue fingerprint.
Returns `null` for an unknown fingerprint. A returned analysis may contain no suspects — that means the data did not explain the failure, which is a legitimate answer, reported as such rather than filled in with a guess.
adaptiveThresholdadaptiveThreshold(metric: string, windowMs?: ): number | nullAdaptive threshold for one metric, or null while the baseline is immature.
Returning null rather than a number is deliberate: a threshold derived from four samples would flag normal traffic as anomalous.
detectAnomaliesdetectAnomalies(values: Record<string, number>, windowMs?: ): AnomalyVerdict[]Evaluate current values against their own history.
memoryTrendmemoryTrend(metric?: "rssMb" | "heapUsedMb" | "heapTotalMb" | "externalMb", windowMs?: ): MemoryTrendMemory growth over the baseline window.
incidentReportincidentReport(fingerprint: string): IncidentReport | nullSelf-contained incident payload, suitable for a model or a colleague with
Conclusions travel with the evidence and the reasons they might be wrong, so a reader cannot act on a suspicion without seeing how weak it is.
previewActionpreviewAction(request: { kind: string; payload?: Record<string, unknown>; }, fingerprint?: string): ActionPreview | nullDescribe an action without performing it.
Separate from `performAction` so a confirmation prompt can show exactly what will be affected before anything happens.
performActionperformAction(request: { kind: string; payload?: Record<string, unknown>; idempotencyKey?: string; }, executor: (kind: string, payload: Record<string, unknown>) => unknown | Promise<unknown>, options?: { confirmed?: boolean; actor?: string; }): Promise<ActionOutcome>Perform a guarded action.
Enforces confirmation and an idempotency key for anything that changes state, and records an audit entry either way — including for attempts that were blocked, since "someone tried to purge the queue at 3am" is itself the record worth keeping.
saveGoldenTracesaveGoldenTrace(name: string, traceId: string): GoldenTrace | nullCapture a live trace as a named baseline to compare future runs against.
compareToGoldencompareToGolden(name: string, traceId: string): GoldenTraceComparison | nullCompare a live trace against a saved baseline, newest first.
sourceForsourceFor(frame: SourceContext, contextLines?: number): SourceSnippetSource lines around a stack frame.
Returns a snippet whose `missing` flag is set when no resolver is installed or the file is absent, rather than pretending the line is unknown.
serviceMapserviceMap(): ServiceMapDependency graph inferred from recorded spans.
detectNPlusOnedetectNPlusOne(minCalls?: number): NPlusOneFinding[]Repeated identical queries under one parent — the N+1 shape.
evaluateSloevaluateSlo(slo: { name: string; target: number; windowMs: number; query?: { route?: string; method?: string; }; }): SloResultEvaluate one objective.
evaluateSlosevaluateSlos(): SloResult[]Evaluate every configured objective. Empty when none are configured.
jobHealthjobHealth(windowMs?: ): Promise<JobHealth>Queue and job health, using retained history for backlog direction.
Reads the attached jobs store directly. It previously consulted `AnalysisSource.jobSnapshot`, which nothing ever populated, so every call returned zeros and a null success rate — indistinguishable from a queue that had genuinely done nothing.
releaseHealthreleaseHealth(): ReleaseHealth[]Per-release health, oldest first. Empty until a release has been recorded.
whatChangedwhatChanged(release?: string): WhatChanged | nullCompare the running release against the previous one.
Null when nothing has been recorded yet; on a first deploy it returns a result whose `previousRelease` is null and whose caveats say so.
analyzeAllIncidentsanalyzeAllIncidents(): IncidentAnalysis[]Analyse every non-ignored issue, most recent failure first.
stopstop(): voidisInstalledisInstalled(): booleanrecordSpanrecordSpan(span: Span): voidrecordErrorrecordError(occurrence: ErrorOccurrence): voidpushErrorpushError(occurrence: ErrorOccurrence): voidrecordLogrecordLog(record: LogRecord): voidrecordTransactionrecordTransaction(tx: HttpTransaction): voidrecordSlowQueryrecordSlowQuery(q: SlowQueryRecord): voidrecordAuditrecordAudit(audit: Omit<AuditRecord, "id" | "timestamp">): voidregisterSseSubscriberregisterSseSubscriber(controller: ReadableStreamDefaultController<Uint8Array>): () => voidnotifySsenotifySse(event: string, data: any)attachSafetyHandlersattachSafetyHandlers(): voiddetachSafetyHandlersdetachSafetyHandlers(): voidattachattach(name: string, instance: any): anysubsystemsgettersubsystems:subsystemsubsystem(name: string): unknownRead-only access to an attached subsystem, for the engines that sample it.
instrumentDbinstrumentDb(db: any): anyinstrumentCacheinstrumentCache(cache: any): anyinstrumentJobsinstrumentJobs(jobs: any): anyinstrumentAuthinstrumentAuth(auth: any): anyinstrumentMailinstrumentMail(mail: any): anyinstrumentStorageinstrumentStorage(storage: any): anyinstrumentRealtimeinstrumentRealtime(realtime: any): anytraceDbtraceDb<T>(name: string, fn: (span: Span) => Promise<T>): Promise<T>traceJobtraceJob<T>(name: string, fn: (span: Span) => Promise<T>): Promise<T>instrumentinstrument(handler: (req: Request) => Response | Promise<Response>, routeName?: (req: Request) => string): (req: Request) => Promise<Response>getDeepApplicationStategetDeepApplicationState()getTraceWaterfallgetTraceWaterfall(traceId: string)
ObserverRouter
classclass ObserverRouter7 members
basePathpropertybasePath: stringapiKeypropertyapiKey?: stringwithDashboardpropertywithDashboard: booleanrunAnalysisActionrunAnalysisAction(kind: string, payload: Record<string, unknown>): Promise<unknown>Real work behind a guarded action. Only reached after the guard approves.
actorForactorFor(req: Request): stringWho to record an audit entry against.
Read from `x-observe-actor` when the caller sends one, and otherwise stated as unknown. The dashboard is behind a single shared token, so the server has no way to tell two people holding that token apart — which means every entry used to say `dashboard_operator` and the audit log could not answer "who flushed the cache". The header is a self-declared label, not an identity: anyone with the token can put anything in it. It is useful for telling a CI job from a human, and worthless as proof. Anything that needs to attribute an action to a person needs real authentication, not this.
matchesmatches(pathname: string): booleanhandlehandle(req: Request): Promise<Response | null>
RingBuffer
classclass RingBuffer9 members
bufpropertybuf: Array<T | undefined>headpropertyhead:countpropertycount:droppedpropertydropped:pushpush(item: T): voidsizegettersize: numberdroppedCountgetterdroppedCount: numberallall(): T[]clearclear(): void
Span
classclass Span28 members
traceIdpropertytraceId: stringspanIdpropertyspanId: stringparentSpanIdpropertyparentSpanId?: stringnamepropertyname: stringkindpropertykind: SpanKindtraceFlagspropertytraceFlags: numbersampledpropertysampled: booleanstartTimepropertystartTime: numberstartPerfpropertystartPerf: numberendTimepropertyendTime?: numberfinishedpropertyfinished:statuspropertystatus: SpanStatusattributespropertyattributes: Record<string, SpanAttributeValue>eventspropertyevents: SpanEvent[]aipropertyai?: AiSpanDataendedpropertyended:endPerfpropertyendPerf:durationMsgetterdurationMs: number | undefinedsetAttributesetAttribute(key: string, value: string | number | boolean): thissetAttributessetAttributes(attrs: Record<string, string | number | boolean>): thisaddEventaddEvent(name: string, attributes?: Record<string, string | number | boolean>): thissetStatussetStatus(status: SpanStatus): thisokok(): thisfailfail(error?: unknown): thissetHttpStatussetHttpStatus(status: number): thisrecordErrorrecordError(error: unknown): thisendend(): thistoJSONtoJSON(): SpanData
Tracer
classclass Tracer12 members
storagepropertystorage:inFlightMappropertyinFlightMap:activegetteractive: Span | undefinedactiveTraceIdgetteractiveTraceId: string | undefinedsampleCounterpropertysampleCounter:Position in the deterministic sampling cycle.
startSpanstartSpan(name: string, options?: { kind?: SpanKind; parent?: TraceContext; attributes?: Record<string, any>; ai?: AiSpanData; }): SpansampleDecisionsampleDecision(): booleanDecides whether a request's spans are recorded.
Called only for a span with no parent, so one roll covers a whole request: every span below it inherits the decision through the parent chain. Rolling per span would give a request with ten spans a chance of 0.1^10 of being recorded in full, which is not what "10% of requests" means. Not random sampling — a seedable one. A recorded trace is a sample you can read, and a real sample cannot be chosen after seeing the results.
startRequestSpanstartRequestSpan(req: Request, route?: string): SpanwithSpanwithSpan<T>(name: string, options: { kind?: SpanKind; attributes?: Record<string, any>; ai?: AiSpanData; }, fn: (span: Span) => Promise<T>): Promise<T>activateactivate<T>(span: Span, fn: () => T): TendSpanendSpan(span: Span): SpangetInFlightRequestsgetInFlightRequests(): HttpTransaction[]
Types
AiSpanData
interfaceinterface AiSpanData7 members
operationpropertyoperation: stringproviderpropertyprovider?: stringmodelpropertymodel?: stringinputTokenspropertyinputTokens?: numberoutputTokenspropertyoutputTokens?: numbercostUsdpropertycostUsd?: numberstreamingpropertystreaming?: boolean
AuditRecord
interfaceinterface AuditRecord8 members
idpropertyid: stringtimestamppropertytimestamp: numberactionpropertyaction: stringtargetpropertytarget?: stringactorpropertyactor: stringippropertyip?: stringtraceIdpropertytraceId?: stringmetapropertymeta?: Record<string, unknown>Structured detail about the change.
Without it an entry says only *that* something happened: "member.updated" cannot answer which role someone moved from or to, which is the whole question an audit log gets consulted for. Optional, so callers with nothing to add are not pushed into inventing a value.
Breadcrumb
interfaceinterface Breadcrumb5 members
timestamppropertytimestamp: numbercategorypropertycategory: "http" | "db" | "cache" | "job" | "auth" | "mail" | "storage" | "realtime" | "log" | "system"messagepropertymessage: stringlevelpropertylevel: "info" | "warn" | "error" | "debug"datapropertydata?: Record<string, unknown>
ErrorIssue
interfaceinterface ErrorIssue12 members
fingerprintpropertyfingerprint: stringstatuspropertystatus: "unresolved" | "resolved" | "ignored"firstSeenpropertyfirstSeen: numberlastSeenpropertylastSeen: numbercountpropertycount: numberstatusCodepropertystatusCode?: numbernamepropertyname: stringmessagepropertymessage: stringtopFramepropertytopFrame?: ParsedStackFrameroutespropertyroutes: string[]occurrencespropertyoccurrences: ErrorOccurrence[]sparklinepropertysparkline: Array<{ minute: number; count: number; }>
ErrorOccurrence
interfaceinterface ErrorOccurrence14 members
idpropertyid: stringfingerprintpropertyfingerprint: stringtimestamppropertytimestamp: numbertraceIdpropertytraceId?: stringspanIdpropertyspanId?: stringmessagepropertymessage: stringnamepropertyname: stringstackpropertystack?: stringparsedFramespropertyparsedFrames: ParsedStackFrame[]levelpropertylevel: "fatal" | "error" | "warning"statusCodepropertystatusCode?: numberrequestpropertyrequest: { method?: string; url?: string; route?: string; status?: number; headers?: Record<string, string>; }breadcrumbspropertybreadcrumbs: Breadcrumb[]handledpropertyhandled: boolean
HardwareMetrics
interfaceinterface HardwareMetrics5 members
ospropertyos: { platform: string; arch: string; hostname: string; uptimeSeconds: number; loadAverage: number[]; }cpupropertycpu: { cores: number; model: string; processCpuPercent: number; }memorypropertymemory: { totalMb: number; freeMb: number; usedMb: number; rssMb: number; heapUsedMb: number; heapTotalMb: number; externalMb: number; }eventLooppropertyeventLoop: { delayMs: number; isBlocked: boolean; }storagepropertystorage: { appDbBytes: number; jobsDbBytes: number; cacheDbBytes: number; }
HttpTransaction
interfaceinterface HttpTransaction10 members
idpropertyid: stringtraceIdpropertytraceId: stringmethodpropertymethod: stringroutepropertyroute: stringurlpropertyurl: stringstatuspropertystatus: numberdurationMspropertydurationMs: numberclientIppropertyclientIp?: stringtimestamppropertytimestamp: numberinFlightpropertyinFlight: boolean
LogRecord
interfaceinterface LogRecord6 members
timestamppropertytimestamp: numberlevelpropertylevel: "trace" | "debug" | "info" | "warn" | "error" | "fatal"messagepropertymessage: stringtraceIdpropertytraceId?: stringspanIdpropertyspanId?: stringcontextpropertycontext: Record<string, unknown>
ObserveConfig
interfaceinterface ObserveConfig19 members
servicepropertyservice: stringreleasepropertyrelease?: stringenvironmentpropertyenvironment?: stringversionpropertyversion?: stringbufferSizepropertybufferSize?: numberdashboardpropertydashboard?: booleandashboardPathpropertydashboardPath?: stringdashboardApiKeypropertydashboardApiKey?: stringruntimeMetricspropertyruntimeMetrics?: booleanruntimeSampleMspropertyruntimeSampleMs?: numberlogLevelpropertylogLevel?: LogLevel | "silent"slowQueryThresholdMspropertyslowQueryThresholdMs?: numberslowQueryModepropertyslowQueryMode?: "fixed" | "adaptive""fixed" uses `slowQueryThresholdMs` as an absolute number.
"adaptive" treats it as a multiple of the query's own rolling p95, so a table whose queries normally take 400ms does not flag every one of them and a table that normally takes 5ms still gets caught at 50ms.
baselineWindowMspropertybaselineWindowMs?: numberWindow used for adaptive thresholds and baselines. Default 15m.
slospropertyslos?: Array<{ name: string; target: number; windowMs: number; query?: { route?: string; method?: string; }; }>Service level objectives, evaluated and reported on the dashboard.
Each needs real traffic to mean anything — an objective with no samples reports `no-data` rather than a flattering 100%.
fatalErrorPolicypropertyfatalErrorPolicy?: "exit" | "continue"trustProxypropertytrustProxy?: booleantracesSampleRatepropertytracesSampleRate?: numberShare of requests to trace, from 0 to 1.
Defaults to 1, which records every span. At 0.1, roughly one request in ten gets a span, and the rest record nothing. A decision made upstream wins: if an incoming `traceparent` header says the request was not sampled, its spans are not sampled either, whatever this is set to. That keeps a sampled caller from silently losing its trace, and an unsampled one from flooding this service.
subsystemspropertysubsystems?: Record<string, any>
ParsedStackFrame
interfaceinterface ParsedStackFrame6 members
functionNamepropertyfunctionName: stringfileNamepropertyfileName: stringlinenopropertylineno: numbercolnopropertycolno: numberinApppropertyinApp: booleanrawpropertyraw: string
SlowQueryRecord
interfaceinterface SlowQueryRecord7 members
idpropertyid: stringsqlpropertysql: stringdurationMspropertydurationMs: numbertimestamppropertytimestamp: numbertablepropertytable?: stringtraceIdpropertytraceId?: stringspanIdpropertyspanId?: string
SpanAttributeValue
interfaceinterface SpanAttributeValue3 members
stringpropertystring?: stringnumberpropertynumber?: numberbooleanpropertyboolean?: boolean
SpanData
interfaceinterface SpanData12 members
traceIdpropertytraceId: stringspanIdpropertyspanId: stringparentSpanIdpropertyparentSpanId?: stringnamepropertyname: stringkindpropertykind: SpanKindstartTimepropertystartTime: numberendTimepropertyendTime?: numberdurationMspropertydurationMs?: numberstatuspropertystatus: SpanStatusattributespropertyattributes: Record<string, SpanAttributeValue>eventspropertyevents: SpanEvent[]aipropertyai?: AiSpanData
SpanEvent
interfaceinterface SpanEvent3 members
namepropertyname: stringtimestamppropertytimestamp: numberattributespropertyattributes: Record<string, SpanAttributeValue>
TraceContext
interfaceinterface TraceContext4 members
traceIdpropertytraceId: stringspanIdpropertyspanId: stringtraceFlagspropertytraceFlags: numbersampledpropertysampled: boolean
LogLevel
typetype LogLevel = "trace" | "debug" | "info" | "warn" | "error" | "fatal"SpanKind
typetype SpanKind = "internal" | "server" | "client" | "producer" | "consumer"SpanStatus
typetype SpanStatus = "unset" | "ok" | "error"Other
analyzeMemoryTrend
unknownanalyzeMemoryTrendAnomalyVerdict
unknownBaseline
unknownBaselineEngine
unknownbuildIncidentReport
unknownbuildIncidentReportbuildSpanTree
unknownbuildSpanTreeConfidence
unknownEvidence
unknownFileSystemTraceToCode
unknownflattenTree
unknownflattenTreeGoldenTraceStore
unknownHistorySample
unknownIncidentAnalysis
unknownIncidentAnalyzer
unknownIncidentReport
unknownJobHealth
unknownJobIntelligence
unknownMemoryTrend
unknownMetricDelta
unknownMetricsHistory
unknownNPlusOneDetector
unknownNPlusOneFinding
unknownReleaseHealth
unknownReleaseIntelligence
unknownReleaseMarker
unknownReleaseTimeline
unknownSafeActions
unknownServiceCall
unknownServiceMap
unknownServiceMapBuilder
unknownServiceNode
unknownSloDefinition
unknownSloEngine
unknownSloResult
unknownSuggestedAction
unknownSuspect
unknownTimelineEvent
unknownTraceToCodeResolver
unknownWhatChanged
unknownAuthConfig or JobPayload — declaring your schema once is enough for the rest to follow. See Typed keys.