@equipe-tech/observability 0.2.1 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/README.md +11 -9
  2. package/dist/BrowserEvents.d.ts +89 -12
  3. package/dist/BrowserEvents.js +67 -13
  4. package/dist/Correlation.d.ts +51 -0
  5. package/dist/Correlation.js +95 -0
  6. package/dist/Metrics.d.ts +19 -3
  7. package/dist/Metrics.js +16 -0
  8. package/dist/MetricsRuntime.d.ts +45 -3
  9. package/dist/MetricsRuntime.js +478 -120
  10. package/dist/PolicyOtlpLogger.d.ts +18 -0
  11. package/dist/PolicyOtlpLogger.js +142 -0
  12. package/dist/RedactionPolicy.d.ts +2 -9
  13. package/dist/RedactionPolicy.js +2 -246
  14. package/dist/ResourceIdentity.d.ts +47 -0
  15. package/dist/ResourceIdentity.js +82 -0
  16. package/dist/Telemetry.d.ts +18 -3
  17. package/dist/Telemetry.js +43 -25
  18. package/dist/TelemetryConfig.d.ts +11 -4
  19. package/dist/TelemetryConfig.js +38 -10
  20. package/dist/audit/AuditCommitDocument.d.ts +41 -0
  21. package/dist/audit/AuditCommitDocument.js +58 -0
  22. package/dist/audit/AuditDigest.d.ts +11 -0
  23. package/dist/audit/AuditDigest.js +25 -0
  24. package/dist/audit/AuditOutbox.d.ts +75 -0
  25. package/dist/audit/AuditOutbox.js +104 -0
  26. package/dist/audit/AuditPublisher.d.ts +49 -0
  27. package/dist/audit/AuditPublisher.js +60 -0
  28. package/dist/audit/AuditRecord.d.ts +128 -0
  29. package/dist/audit/AuditRecord.js +172 -0
  30. package/dist/audit/CommittedAuditRecordInternal.d.ts +11 -0
  31. package/dist/audit/CommittedAuditRecordInternal.js +13 -0
  32. package/dist/audit/InvalidAuditRecord.d.ts +12 -0
  33. package/dist/audit/InvalidAuditRecord.js +16 -0
  34. package/dist/audit/index.d.ts +7 -0
  35. package/dist/audit/index.js +7 -0
  36. package/dist/browser/BrowserClient.d.ts +82 -3
  37. package/dist/browser/BrowserClient.js +322 -28
  38. package/dist/browser/BrowserEventLimits.d.ts +8 -0
  39. package/dist/browser/BrowserEventLimits.js +8 -0
  40. package/dist/browser/BrowserMetricRecorder.d.ts +17 -0
  41. package/dist/browser/BrowserMetricRecorder.js +35 -0
  42. package/dist/browser/BrowserTelemetry.d.ts +40 -0
  43. package/dist/browser/BrowserTelemetry.js +105 -0
  44. package/dist/browser/ClientPolicy.d.ts +3 -0
  45. package/dist/browser/ClientPolicy.js +52 -0
  46. package/dist/browser/client.d.ts +3 -2
  47. package/dist/browser/client.js +2 -1
  48. package/dist/browser/index.d.ts +4 -37
  49. package/dist/browser/index.js +3 -95
  50. package/dist/contract/ContractCompatibility.d.ts +31 -0
  51. package/dist/contract/ContractCompatibility.js +274 -0
  52. package/dist/contract/ContractIndex.d.ts +54 -0
  53. package/dist/contract/ContractIndex.js +178 -0
  54. package/dist/contract/ContractSurface.d.ts +111 -0
  55. package/dist/contract/ContractSurface.js +116 -0
  56. package/dist/contract/EventName.d.ts +6 -0
  57. package/dist/contract/EventName.js +55 -0
  58. package/dist/contract/EventProducer.d.ts +123 -0
  59. package/dist/contract/EventProducer.js +209 -0
  60. package/dist/contract/MetricContractError.d.ts +19 -0
  61. package/dist/contract/MetricContractError.js +19 -0
  62. package/dist/contract/MetricDefinition.d.ts +61 -0
  63. package/dist/contract/MetricDefinition.js +91 -0
  64. package/dist/contract/MetricProducer.d.ts +42 -0
  65. package/dist/contract/MetricProducer.js +123 -0
  66. package/dist/contract/OrganizationEvents.d.ts +165 -0
  67. package/dist/contract/OrganizationEvents.js +97 -0
  68. package/dist/contract/TelemetryContract.d.ts +95 -0
  69. package/dist/contract/TelemetryContract.js +451 -0
  70. package/dist/contract/TelemetryContractError.d.ts +44 -0
  71. package/dist/contract/TelemetryContractError.js +64 -0
  72. package/dist/contract/TelemetryEvent.d.ts +59 -0
  73. package/dist/contract/TelemetryEvent.js +54 -0
  74. package/dist/contract/index.d.ts +11 -0
  75. package/dist/contract/index.js +11 -0
  76. package/dist/effect/WideEvent.d.ts +3 -0
  77. package/dist/effect/WideEvent.js +20 -0
  78. package/dist/effect/WideEventSink.d.ts +3 -0
  79. package/dist/effect/WideEventSink.js +79 -0
  80. package/dist/effect/index.d.ts +2 -0
  81. package/dist/effect/index.js +2 -0
  82. package/dist/index.d.ts +17 -2
  83. package/dist/index.js +11 -2
  84. package/dist/metrics/InstrumentGrammar.d.ts +5 -0
  85. package/dist/metrics/InstrumentGrammar.js +12 -0
  86. package/dist/metrics/MetricScalar.d.ts +3 -0
  87. package/dist/metrics/MetricScalar.js +5 -0
  88. package/dist/node/AuditDigest.d.ts +3 -0
  89. package/dist/node/AuditDigest.js +6 -0
  90. package/dist/node/BrowserEventIngest.d.ts +7 -2
  91. package/dist/node/BrowserEventIngest.js +111 -19
  92. package/dist/node/LocalCollector.d.ts +20 -0
  93. package/dist/node/LocalCollector.js +188 -0
  94. package/dist/node/Observability.d.ts +49 -0
  95. package/dist/node/Observability.js +190 -0
  96. package/dist/node/Runtime.d.ts +4 -1
  97. package/dist/node/index.d.ts +4 -0
  98. package/dist/node/index.js +3 -0
  99. package/dist/policy/BrowserFieldPolicy.d.ts +7 -0
  100. package/dist/policy/BrowserFieldPolicy.js +298 -0
  101. package/dist/policy/DataPolicy.d.ts +19 -0
  102. package/dist/policy/DataPolicy.js +254 -0
  103. package/dist/policy/DataPolicyError.d.ts +22 -0
  104. package/dist/policy/DataPolicyError.js +25 -0
  105. package/dist/policy/Deduplication.d.ts +13 -0
  106. package/dist/policy/Deduplication.js +44 -0
  107. package/dist/policy/DefectEnvelope.d.ts +25 -0
  108. package/dist/policy/DefectEnvelope.js +52 -0
  109. package/dist/policy/MetricLabelPolicy.d.ts +5 -0
  110. package/dist/policy/MetricLabelPolicy.js +65 -0
  111. package/dist/policy/PolicyTransform.d.ts +16 -0
  112. package/dist/policy/PolicyTransform.js +136 -0
  113. package/dist/policy/PolicyVocabulary.d.ts +10 -0
  114. package/dist/policy/PolicyVocabulary.js +84 -0
  115. package/dist/policy/ResourceAttributePolicy.d.ts +9 -0
  116. package/dist/policy/ResourceAttributePolicy.js +33 -0
  117. package/dist/policy/entrypoint.d.ts +2 -0
  118. package/dist/policy/entrypoint.js +2 -0
  119. package/dist/policy/index.d.ts +17 -0
  120. package/dist/policy/index.js +9 -0
  121. package/dist/profile/DataPolicy.d.ts +2 -0
  122. package/dist/profile/DataPolicy.js +1 -0
  123. package/dist/profile/EnvironmentPolicy.d.ts +22 -0
  124. package/dist/profile/EnvironmentPolicy.js +41 -0
  125. package/dist/profile/LifecycleRegistry.d.ts +24 -0
  126. package/dist/profile/LifecycleRegistry.js +240 -0
  127. package/dist/profile/ObservabilityAdapter.d.ts +115 -0
  128. package/dist/profile/ObservabilityAdapter.js +50 -0
  129. package/dist/profile/ObservabilityConfig.d.ts +68 -0
  130. package/dist/profile/ObservabilityConfig.js +125 -0
  131. package/dist/profile/ObservabilityConfigError.d.ts +23 -0
  132. package/dist/profile/ObservabilityConfigError.js +39 -0
  133. package/dist/profile/ObservabilityProfile.d.ts +49 -0
  134. package/dist/profile/ObservabilityProfile.js +121 -0
  135. package/dist/profile/ReactWebProfile.d.ts +8 -0
  136. package/dist/profile/ReactWebProfile.js +7 -0
  137. package/dist/profile/index.d.ts +5 -0
  138. package/dist/profile/index.js +5 -0
  139. package/dist/testing/conformance/ConformanceAssertions.d.ts +5 -0
  140. package/dist/testing/conformance/ConformanceAssertions.js +31 -0
  141. package/dist/testing/conformance/ConformanceFailure.d.ts +35 -0
  142. package/dist/testing/conformance/ConformanceFailure.js +56 -0
  143. package/dist/testing/conformance/ConformanceModel.d.ts +97 -0
  144. package/dist/testing/conformance/ConformanceModel.js +8 -0
  145. package/dist/testing/conformance/ConformanceRegistry.d.ts +13 -0
  146. package/dist/testing/conformance/ConformanceRegistry.js +190 -0
  147. package/dist/testing/conformance/ConformanceRunner.d.ts +5 -0
  148. package/dist/testing/conformance/ConformanceRunner.js +198 -0
  149. package/dist/testing/conformance/TelemetryEvidence.d.ts +91 -0
  150. package/dist/testing/conformance/TelemetryEvidence.js +370 -0
  151. package/dist/testing/conformance/index.d.ts +10 -0
  152. package/dist/testing/conformance/index.js +6 -0
  153. package/dist/testing/contract.d.ts +81 -0
  154. package/dist/testing/contract.js +93 -0
  155. package/dist/testing/deployedCanary.d.ts +1 -0
  156. package/dist/testing/deployedCanary.js +1 -0
  157. package/dist/testing/index.d.ts +40 -3
  158. package/dist/testing/index.js +122 -5
  159. package/dist/trace/HttpServerOtlpTracer.d.ts +42 -0
  160. package/dist/trace/HttpServerOtlpTracer.js +301 -0
  161. package/package.json +16 -29
  162. package/dist/WideEvent.d.ts +0 -5
  163. package/dist/WideEvent.js +0 -6
  164. package/dist/nestjs/BrowserEventsController.d.ts +0 -21
  165. package/dist/nestjs/BrowserEventsController.js +0 -52
  166. package/dist/nestjs/HttpRoutePolicy.d.ts +0 -23
  167. package/dist/nestjs/HttpRoutePolicy.js +0 -179
  168. package/dist/nestjs/HttpServerOtlpTracer.d.ts +0 -15
  169. package/dist/nestjs/HttpServerOtlpTracer.js +0 -154
  170. package/dist/nestjs/RequestWideEventTraceCorrelation.d.ts +0 -15
  171. package/dist/nestjs/RequestWideEventTraceCorrelation.js +0 -13
  172. package/dist/nestjs/TelemetryInterceptor.d.ts +0 -31
  173. package/dist/nestjs/TelemetryInterceptor.js +0 -268
  174. package/dist/nestjs/TelemetryModule.d.ts +0 -53
  175. package/dist/nestjs/TelemetryModule.js +0 -428
  176. package/dist/nestjs/index.d.ts +0 -5
  177. package/dist/nestjs/index.js +0 -4
@@ -0,0 +1,209 @@
1
+ import { Clock, Context, DateTime, Effect, Predicate, Random, Schema } from "effect";
2
+ import { AuditContext, AuditOutcome } from "../audit/AuditRecord.js";
3
+ import { CorrelationContext, CurrentCorrelation } from "../Correlation.js";
4
+ import { telemetryContractProvenance, validateContractEvent, } from "./TelemetryContract.js";
5
+ import { ErrorContext, EventDuration, EventOutcome, EventSeverity, EventTimestamp, HttpContext, } from "./TelemetryEvent.js";
6
+ import { CurrentDataPolicy } from "../policy/DataPolicy.js";
7
+ import { transformSignalFields } from "../policy/PolicyTransform.js";
8
+ import { sensitiveFieldReplacement } from "../policy/PolicyVocabulary.js";
9
+ import { InvalidTelemetryEvent } from "./TelemetryContractError.js";
10
+ export class TelemetryEventSink extends Context.Service()("@equipe-tech/observability/TelemetryEventSink") {
11
+ }
12
+ const eventError = (code, message, context) => new InvalidTelemetryEvent({ code, message, ...context });
13
+ const parseEventPayload = (definition, payload) => {
14
+ if (!Predicate.isObject(payload)) {
15
+ return eventError("OBS_EVENT_INVALID_FIELD", `Event "${definition.name}" has an invalid payload. Use an event payload object with declared fields.`, { eventName: definition.name, attributeName: "payload" });
16
+ }
17
+ return payload;
18
+ };
19
+ const parseAttributes = (policy, contract, definition, attributes) => {
20
+ if (!Predicate.isObject(attributes)) {
21
+ return eventError("OBS_EVENT_INVALID_FIELD", `Event "${definition.name}" has invalid attributes. Use a declared scalar attribute object.`, { eventName: definition.name, attributeName: "attributes" });
22
+ }
23
+ const parsed = {};
24
+ const contractRedactions = [];
25
+ for (const [attributeName, value] of Object.entries(attributes)) {
26
+ const attribute = definition.attributes.get(attributeName);
27
+ if (value === undefined ||
28
+ (!Predicate.isString(value) &&
29
+ !Predicate.isBoolean(value) &&
30
+ (!Predicate.isNumber(value) || !Number.isFinite(value)))) {
31
+ return eventError("OBS_EVENT_INVALID_FIELD", `Event "${definition.name}" has a non-scalar or non-finite value for "${attributeName}". Use a string, finite number, or boolean.`, { eventName: definition.name, attributeName });
32
+ }
33
+ if (attribute === undefined) {
34
+ parsed[attributeName] = value;
35
+ continue;
36
+ }
37
+ if (attribute.classification === "forbidden") {
38
+ return eventError("OBS_EVENT_RESTRICTED_ATTRIBUTE", `Event "${definition.name}" cannot emit forbidden attribute "${attributeName}". Remove the attribute before emitting.`, { eventName: definition.name, attributeName });
39
+ }
40
+ if (attribute.classification === "sensitive" && attribute.metricLabel) {
41
+ return eventError("OBS_EVENT_SENSITIVE_METRIC_LABEL", `Event "${definition.name}" cannot use sensitive attribute "${attributeName}" as a metric label. Remove the metric label declaration.`, { eventName: definition.name, attributeName });
42
+ }
43
+ parsed[attributeName] =
44
+ attribute.classification === "sensitive" ? sensitiveFieldReplacement : value;
45
+ if (attribute.classification === "sensitive" &&
46
+ policy.classify(attributeName) !== "sensitive") {
47
+ contractRedactions.push({ rule: "classification", action: "masked", surface: "event" });
48
+ }
49
+ }
50
+ const validation = validateContractEvent(contract, definition.name, parsed);
51
+ if (validation instanceof InvalidTelemetryEvent) {
52
+ return validation;
53
+ }
54
+ const decision = transformSignalFields(policy, "event", parsed);
55
+ return {
56
+ attributes: decision.value,
57
+ redactions: [...contractRedactions, ...decision.redactions],
58
+ admission: { policyDroppedAttributes: decision.dropped },
59
+ };
60
+ };
61
+ const decodeSeverity = Schema.decodeUnknownEffect(EventSeverity);
62
+ const decodeOutcome = Schema.decodeUnknownEffect(EventOutcome);
63
+ const decodeAuditOutcome = Schema.decodeUnknownEffect(AuditOutcome);
64
+ const decodeTimestamp = Schema.decodeUnknownEffect(EventTimestamp);
65
+ const decodeDuration = Schema.decodeUnknownEffect(EventDuration);
66
+ const decodeHttp = Schema.decodeUnknownEffect(HttpContext);
67
+ const decodeError = Schema.decodeUnknownEffect(ErrorContext);
68
+ const decodeAudit = Schema.decodeUnknownEffect(AuditContext);
69
+ const decodeCorrelation = Schema.decodeUnknownEffect(CorrelationContext);
70
+ const parseTimestamp = (eventName, timestamp) => decodeTimestamp(timestamp).pipe(Effect.mapError(() => eventError("OBS_EVENT_INVALID_FIELD", `Event "${eventName}" has an invalid timestamp. Use a real RFC 3339 UTC timestamp ending in Z.`, { eventName, attributeName: "event.timestamp" })));
71
+ const parseDuration = (eventName, durationMs) => decodeDuration(durationMs).pipe(Effect.mapError(() => eventError("OBS_EVENT_INVALID_FIELD", `Event "${eventName}" has an invalid durationMs. Use a finite non-negative number.`, { eventName, attributeName: "event.duration_ms" })));
72
+ const parseHttp = (eventName, http) => decodeHttp(http).pipe(Effect.mapError(() => eventError("OBS_EVENT_INVALID_FIELD", `Event "${eventName}" has invalid HTTP context. Use a non-empty method and route with an integer status code from 100 to 599.`, { eventName, attributeName: "http" })));
73
+ const parseError = (eventName, error) => decodeError(error).pipe(Effect.mapError(() => eventError("OBS_EVENT_INVALID_FIELD", `Event "${eventName}" has invalid error context. Use a non-empty type, a message, and a boolean retryable value.`, { eventName, attributeName: "error" })));
74
+ const parseAudit = (eventName, audit) => decodeAudit(audit).pipe(Effect.mapError(() => eventError("OBS_EVENT_INVALID_FIELD", `Event "${eventName}" has invalid audit context. Use a declared action, actor, resource type, and resource id.`, { eventName, attributeName: "audit" })));
75
+ const parseCorrelation = (eventName, correlation) => {
76
+ if (correlation === undefined) {
77
+ return CurrentCorrelation;
78
+ }
79
+ return decodeCorrelation(correlation).pipe(Effect.mapError(() => eventError("OBS_EVENT_INVALID_FIELD", `Event "${eventName}" has invalid correlation context. Use traced or untraced linkage with bounded request and run identifiers.`, { eventName, attributeName: "correlation" })));
80
+ };
81
+ const parseOutcome = (definition, outcome) => decodeOutcome(outcome).pipe(Effect.mapError(() => eventError("OBS_EVENT_INVALID_OUTCOME", `Event "${definition.name}" has an invalid outcome. Use success, failure, or cancelled.`, { eventName: definition.name, attributeName: "event.outcome" })));
82
+ const parseSeverity = (eventName, severity) => decodeSeverity(severity).pipe(Effect.mapError(() => eventError("OBS_EVENT_INVALID_FIELD", `Event "${eventName}" has an invalid severity. Use debug, info, warn, error, or fatal.`, { eventName, attributeName: "event.severity" })));
83
+ const shouldRecord = Effect.fn("shouldRecord")(function* (definition, outcome) {
84
+ if (definition.mandatory ||
85
+ definition.kind === "audit" ||
86
+ definition.kind === "defect" ||
87
+ outcome === "failure" ||
88
+ definition.sampling.kind === "always") {
89
+ return true;
90
+ }
91
+ const draw = yield* Random.next;
92
+ return draw < definition.sampling.rate;
93
+ });
94
+ const buildEvent = Effect.fn("buildEvent")(function* (definition, contract, attributes, payload) {
95
+ const rawTimestamp = payload.timestamp ?? DateTime.formatIso(DateTime.makeUnsafe(yield* Clock.currentTimeMillis));
96
+ const timestamp = yield* parseTimestamp(definition.name, rawTimestamp);
97
+ const severity = yield* parseSeverity(definition.name, payload.severity ?? definition.defaultSeverity);
98
+ const correlation = yield* parseCorrelation(definition.name, payload.correlation);
99
+ const base = {
100
+ timestamp,
101
+ name: definition.name,
102
+ severity,
103
+ correlation,
104
+ attributes,
105
+ };
106
+ switch (definition.kind) {
107
+ case "request": {
108
+ if (payload.durationMs === undefined ||
109
+ payload.http === undefined ||
110
+ payload.outcome === undefined) {
111
+ return yield* eventError("OBS_EVENT_INVALID_FIELD", `Request event "${definition.name}" requires outcome, durationMs, and http fields. Add every required field.`, { eventName: definition.name });
112
+ }
113
+ const outcome = yield* parseOutcome(definition, payload.outcome);
114
+ const durationMs = yield* parseDuration(definition.name, payload.durationMs);
115
+ const http = yield* parseHttp(definition.name, payload.http);
116
+ return { ...base, kind: "request", outcome, durationMs, http };
117
+ }
118
+ case "operation": {
119
+ if (payload.durationMs === undefined || payload.outcome === undefined) {
120
+ return yield* eventError("OBS_EVENT_INVALID_FIELD", `Operation event "${definition.name}" requires outcome and durationMs. Add both fields.`, { eventName: definition.name });
121
+ }
122
+ const outcome = yield* parseOutcome(definition, payload.outcome);
123
+ const durationMs = yield* parseDuration(definition.name, payload.durationMs);
124
+ return { ...base, kind: "operation", outcome, durationMs };
125
+ }
126
+ case "domain": {
127
+ if (payload.outcome === undefined) {
128
+ return yield* eventError("OBS_EVENT_INVALID_FIELD", `Domain event "${definition.name}" requires an outcome. Add the required field.`, { eventName: definition.name });
129
+ }
130
+ const outcome = yield* parseOutcome(definition, payload.outcome);
131
+ return { ...base, kind: "domain", outcome };
132
+ }
133
+ case "defect": {
134
+ if (payload.error === undefined) {
135
+ return yield* eventError("OBS_EVENT_INVALID_FIELD", `Defect event "${definition.name}" requires error context. Add error type, message, and retryable fields.`, { eventName: definition.name });
136
+ }
137
+ if (payload.outcome !== undefined) {
138
+ return yield* eventError("OBS_EVENT_INVALID_OUTCOME", `Defect event "${definition.name}" has an invalid outcome. Defect outcomes are always failure.`, { eventName: definition.name, attributeName: "event.outcome" });
139
+ }
140
+ const error = yield* parseError(definition.name, payload.error);
141
+ return { ...base, kind: "defect", outcome: "failure", error };
142
+ }
143
+ case "audit": {
144
+ if (payload.audit === undefined || payload.outcome === undefined) {
145
+ return yield* eventError("OBS_EVENT_INVALID_FIELD", `Audit event "${definition.name}" requires outcome and audit context. Add both fields.`, { eventName: definition.name });
146
+ }
147
+ const outcome = yield* decodeAuditOutcome(payload.outcome).pipe(Effect.mapError(() => eventError("OBS_EVENT_INVALID_AUDIT_OUTCOME", `Audit event "${definition.name}" has an invalid outcome. Use success, failure, cancelled, or denied.`, { eventName: definition.name, attributeName: "event.outcome" })));
148
+ const audit = yield* parseAudit(definition.name, payload.audit);
149
+ const action = contract.auditActionByName.get(audit.action);
150
+ if (action === undefined) {
151
+ return yield* eventError("OBS_EVENT_UNKNOWN_AUDIT_ACTION", `Audit event "${definition.name}" uses undeclared action "${audit.action}". Use a contract audit action.`, { eventName: definition.name, attributeName: "audit.action" });
152
+ }
153
+ if (action.resourceType !== audit.resourceType) {
154
+ return yield* eventError("OBS_EVENT_INVALID_AUDIT_RESOURCE", `Audit action "${audit.action}" requires resource type "${action.resourceType}". Use the declared resource type.`, { eventName: definition.name, attributeName: "audit.resource.type" });
155
+ }
156
+ if (!action.allowedOutcomes.includes(outcome)) {
157
+ return yield* eventError("OBS_EVENT_INVALID_AUDIT_OUTCOME", `Audit action "${audit.action}" does not allow outcome "${outcome}". Use one of its declared outcomes.`, { eventName: definition.name, attributeName: "event.outcome" });
158
+ }
159
+ if (audit.reasonCode !== undefined && !action.reasonCodes.includes(audit.reasonCode)) {
160
+ return yield* eventError("OBS_EVENT_INVALID_AUDIT_REASON", `Audit action "${audit.action}" does not declare reason code "${audit.reasonCode}". Use a declared reason code or omit it.`, { eventName: definition.name, attributeName: "audit.reason_code" });
161
+ }
162
+ return { ...base, kind: "audit", outcome, audit };
163
+ }
164
+ }
165
+ });
166
+ export const admitEvent = Effect.fn("admitEvent")(function* (contract, definition, payload) {
167
+ const policy = yield* CurrentDataPolicy;
168
+ const parsedAttributes = parseAttributes(policy, contract, definition, payload.attributes);
169
+ if (parsedAttributes instanceof InvalidTelemetryEvent)
170
+ return yield* parsedAttributes;
171
+ const event = yield* buildEvent(definition, contract, parsedAttributes.attributes, payload);
172
+ if (!(yield* shouldRecord(definition, event.outcome))) {
173
+ return { decision: "sampled_out", name: definition.name };
174
+ }
175
+ return {
176
+ decision: "recorded",
177
+ event,
178
+ redactions: parsedAttributes.redactions,
179
+ admission: parsedAttributes.admission,
180
+ };
181
+ });
182
+ export const makeEventProducer = (contract) => {
183
+ const contractProvenance = telemetryContractProvenance(contract);
184
+ return {
185
+ emit: Effect.fn("EventProducer.emit")(function* (alias, payload) {
186
+ const definition = contract.eventByAlias.get(alias);
187
+ if (definition === undefined) {
188
+ return yield* eventError("OBS_EVENT_UNKNOWN_NAME", `Event alias "${alias}" is not declared by this telemetry contract. Use one of the contract aliases.`, { eventAlias: alias });
189
+ }
190
+ const parsedPayload = parseEventPayload(definition, payload);
191
+ if (parsedPayload instanceof InvalidTelemetryEvent) {
192
+ return yield* parsedPayload;
193
+ }
194
+ const admitted = yield* admitEvent(contract, definition, parsedPayload);
195
+ if (admitted.decision === "sampled_out") {
196
+ return { ...admitted, contractProvenance };
197
+ }
198
+ const sink = yield* TelemetryEventSink;
199
+ yield* sink.record(admitted.event, admitted.admission);
200
+ return {
201
+ decision: "recorded",
202
+ event: admitted.event,
203
+ redactions: admitted.redactions,
204
+ admission: admitted.admission,
205
+ contractProvenance,
206
+ };
207
+ }),
208
+ };
209
+ };
@@ -0,0 +1,19 @@
1
+ export type MetricMeasurementErrorCode = "OBS_METRIC_UNKNOWN_ALIAS" | "OBS_METRIC_KIND_MISMATCH" | "OBS_METRIC_UNDECLARED_ATTRIBUTE" | "OBS_METRIC_MISSING_ATTRIBUTE" | "OBS_METRIC_VALUE_NOT_ALLOWED" | "OBS_METRIC_CARDINALITY_EXCEEDED" | "OBS_METRIC_INVALID_VALUE";
2
+ type InvalidMetricMeasurementOptions = {
3
+ readonly code: MetricMeasurementErrorCode;
4
+ readonly operation: string;
5
+ readonly message: string;
6
+ readonly metricAlias: string;
7
+ readonly metricName?: string;
8
+ readonly attributeName?: string;
9
+ };
10
+ export declare class InvalidMetricMeasurement extends Error {
11
+ readonly code: MetricMeasurementErrorCode;
12
+ readonly operation: string;
13
+ readonly metricAlias: string;
14
+ readonly metricName?: string;
15
+ readonly attributeName?: string;
16
+ readonly retryable = false;
17
+ constructor(options: InvalidMetricMeasurementOptions);
18
+ }
19
+ export {};
@@ -0,0 +1,19 @@
1
+ export class InvalidMetricMeasurement extends Error {
2
+ code;
3
+ operation;
4
+ metricAlias;
5
+ metricName;
6
+ attributeName;
7
+ retryable = false;
8
+ constructor(options) {
9
+ super(options.message);
10
+ this.name = "InvalidMetricMeasurement";
11
+ this.code = options.code;
12
+ this.operation = options.operation;
13
+ this.metricAlias = options.metricAlias;
14
+ if (options.metricName !== undefined)
15
+ this.metricName = options.metricName;
16
+ if (options.attributeName !== undefined)
17
+ this.attributeName = options.attributeName;
18
+ }
19
+ }
@@ -0,0 +1,61 @@
1
+ export type MetricKind = "counter" | "histogram" | "observable_gauge";
2
+ export type MetricAttributeScalar = string | number | boolean;
3
+ export type MetricAttributeDefinitionInput = {
4
+ readonly classification: "public" | "internal";
5
+ readonly maximumCardinality: number;
6
+ readonly allowedValues?: readonly [
7
+ MetricAttributeScalar,
8
+ ...ReadonlyArray<MetricAttributeScalar>
9
+ ];
10
+ };
11
+ export type MetricAttributeDefinitionsInput = {
12
+ readonly [attributeName: string]: MetricAttributeDefinitionInput;
13
+ };
14
+ type MetricDefinitionBase = {
15
+ readonly name: string;
16
+ readonly description: string;
17
+ readonly unit: string;
18
+ readonly attributes: MetricAttributeDefinitionsInput;
19
+ };
20
+ export type CounterMetricDefinitionInput = MetricDefinitionBase & {
21
+ readonly kind: "counter";
22
+ readonly boundaries?: never;
23
+ };
24
+ export type HistogramMetricDefinitionInput = MetricDefinitionBase & {
25
+ readonly kind: "histogram";
26
+ readonly boundaries: ReadonlyArray<number>;
27
+ };
28
+ export type ObservableGaugeMetricDefinitionInput = MetricDefinitionBase & {
29
+ readonly kind: "observable_gauge";
30
+ readonly boundaries?: never;
31
+ };
32
+ export type MetricDefinitionInput = CounterMetricDefinitionInput | HistogramMetricDefinitionInput | ObservableGaugeMetricDefinitionInput;
33
+ export type MetricDefinitionsInput = {
34
+ readonly [alias: string]: MetricDefinitionInput;
35
+ };
36
+ type CompiledMetricDefinitionBase = {
37
+ readonly alias: string;
38
+ readonly name: string;
39
+ readonly description: string;
40
+ readonly unit: string;
41
+ readonly attributes: ReadonlyMap<string, MetricAttributeDefinitionInput>;
42
+ };
43
+ export type CompiledMetricDefinition = (CompiledMetricDefinitionBase & {
44
+ readonly kind: "counter";
45
+ readonly boundaries?: never;
46
+ }) | (CompiledMetricDefinitionBase & {
47
+ readonly kind: "histogram";
48
+ readonly boundaries: ReadonlyArray<number>;
49
+ }) | (CompiledMetricDefinitionBase & {
50
+ readonly kind: "observable_gauge";
51
+ readonly boundaries?: never;
52
+ });
53
+ export declare const isValidMetricName: (name: string) => boolean;
54
+ export declare const isValidMetricDescription: (description: string) => boolean;
55
+ export declare const isValidMetricUnit: (unit: string) => boolean;
56
+ export declare const isValidHistogramBoundaries: (boundaries: ReadonlyArray<number>) => boolean;
57
+ export declare const isValidMetricAttributeName: (name: string) => boolean;
58
+ export declare const isValidMetricAttributeCount: (count: number) => boolean;
59
+ export declare const isValidMetricCardinality: (value: number) => boolean;
60
+ export declare const validAllowedValues: (values: ReadonlyArray<MetricAttributeScalar> | undefined, maximumCardinality: number) => boolean;
61
+ export {};
@@ -0,0 +1,91 @@
1
+ import { isReservedEventNamePart, isValidAttributeName } from "./EventName.js";
2
+ import { isForbiddenMetricAttributeName } from "../policy/MetricLabelPolicy.js";
3
+ import { isFiniteMetricScalar, metricScalarIdentity } from "../metrics/MetricScalar.js";
4
+ import { containsControlCharacter, instrumentNamePattern, maximumMetricAttributeCardinality, maximumMetricAttributes, unitPattern, } from "../metrics/InstrumentGrammar.js";
5
+ const metricNamePattern = /^[a-z][a-z0-9_]*\.[a-z][a-z0-9_]*$/;
6
+ const reservedMetricParts = new Set([
7
+ "production",
8
+ "prod",
9
+ "staging",
10
+ "stage",
11
+ "development",
12
+ "dev",
13
+ "test",
14
+ "local",
15
+ "sandbox",
16
+ "qa",
17
+ "preview",
18
+ "debug",
19
+ "info",
20
+ "warn",
21
+ "warning",
22
+ "fatal",
23
+ "critical",
24
+ "trace",
25
+ "verbose",
26
+ "severity",
27
+ "success",
28
+ "succeeded",
29
+ "failure",
30
+ "failed",
31
+ "cancelled",
32
+ "canceled",
33
+ "ok",
34
+ "outcome",
35
+ "errored",
36
+ "error",
37
+ "service",
38
+ "environment",
39
+ "instance",
40
+ "host",
41
+ "pod",
42
+ "node",
43
+ "region",
44
+ "zone",
45
+ "cluster",
46
+ "namespace",
47
+ "ms",
48
+ "seconds",
49
+ "bytes",
50
+ "count",
51
+ "percent",
52
+ "ratio",
53
+ "unit",
54
+ ]);
55
+ export const isValidMetricName = (name) => name.length <= 128 &&
56
+ metricNamePattern.test(name) &&
57
+ instrumentNamePattern.test(name) &&
58
+ name.split(".").every((part) => !isReservedEventNamePart(part) && !reservedMetricParts.has(part));
59
+ export const isValidMetricDescription = (description) => description.length > 0 && description.length <= 1_024 && !containsControlCharacter(description);
60
+ export const isValidMetricUnit = (unit) => unit.length <= 63 && unitPattern.test(unit);
61
+ export const isValidHistogramBoundaries = (boundaries) => {
62
+ if (boundaries.length === 0 || boundaries.length > 50)
63
+ return false;
64
+ let previous = Number.NEGATIVE_INFINITY;
65
+ for (const boundary of boundaries) {
66
+ if (!Number.isFinite(boundary) || boundary <= previous)
67
+ return false;
68
+ previous = boundary;
69
+ }
70
+ return true;
71
+ };
72
+ export const isValidMetricAttributeName = (name) => isValidAttributeName(name) && !isForbiddenMetricAttributeName(name);
73
+ export const isValidMetricAttributeCount = (count) => count <= maximumMetricAttributes;
74
+ export const isValidMetricCardinality = (value) => Number.isInteger(value) && value >= 1 && value <= maximumMetricAttributeCardinality;
75
+ export const validAllowedValues = (values, maximumCardinality) => {
76
+ if (values === undefined)
77
+ return true;
78
+ if (values.length === 0 || values.length > maximumCardinality)
79
+ return false;
80
+ const identities = new Set();
81
+ for (const value of values) {
82
+ if (!isFiniteMetricScalar(value)) {
83
+ return false;
84
+ }
85
+ const identity = metricScalarIdentity(value);
86
+ if (identities.has(identity))
87
+ return false;
88
+ identities.add(identity);
89
+ }
90
+ return true;
91
+ };
@@ -0,0 +1,42 @@
1
+ import type { MetricAttributeValue, Metrics, ObservableGaugeRegistration } from "../Metrics.js";
2
+ import type { TelemetryContract, TelemetryContractInput } from "./TelemetryContract.js";
3
+ import type { CompiledMetricDefinition, MetricAttributeDefinitionInput, MetricDefinitionInput } from "./MetricDefinition.js";
4
+ export type MetricAttributeValueOf<Definition extends MetricAttributeDefinitionInput> = Definition["allowedValues"] extends ReadonlyArray<infer Value extends MetricAttributeValue> ? Value : MetricAttributeValue;
5
+ export type MetricAttributesOf<Definition extends MetricDefinitionInput> = {
6
+ readonly [Name in keyof Definition["attributes"]]: MetricAttributeValueOf<Definition["attributes"][Name]>;
7
+ };
8
+ type AliasesOfKind<Definition extends TelemetryContractInput, Kind extends string> = {
9
+ readonly [Alias in keyof Definition["metrics"]]: Definition["metrics"][Alias]["kind"] extends Kind ? Alias : never;
10
+ }[keyof Definition["metrics"]] & string;
11
+ export type ContractCounter<Definition extends TelemetryContractInput, Alias extends AliasesOfKind<Definition, "counter">> = {
12
+ readonly add: (value: number, attributes: MetricAttributesOf<Definition["metrics"][Alias]>) => void;
13
+ };
14
+ export type ContractHistogram<Definition extends TelemetryContractInput, Alias extends AliasesOfKind<Definition, "histogram">> = {
15
+ readonly record: (value: number, attributes: MetricAttributesOf<Definition["metrics"][Alias]>) => void;
16
+ };
17
+ export type ContractGaugeObservation<Definition extends MetricDefinitionInput> = {
18
+ readonly value: number;
19
+ readonly attributes: MetricAttributesOf<Definition>;
20
+ };
21
+ export type MetricProducer<Definition extends TelemetryContractInput> = {
22
+ readonly counter: <Alias extends AliasesOfKind<Definition, "counter">>(alias: Alias) => ContractCounter<Definition, Alias>;
23
+ readonly histogram: <Alias extends AliasesOfKind<Definition, "histogram">>(alias: Alias) => ContractHistogram<Definition, Alias>;
24
+ readonly observableGauge: <Alias extends AliasesOfKind<Definition, "observable_gauge">>(alias: Alias, callback: () => ReadonlyArray<ContractGaugeObservation<Definition["metrics"][Alias]>>) => ObservableGaugeRegistration;
25
+ };
26
+ type NamedContractCounterMeasurement = {
27
+ readonly name: string;
28
+ readonly value: number;
29
+ readonly attributes: {
30
+ readonly [name: string]: MetricAttributeValue;
31
+ };
32
+ };
33
+ export declare const prepareContractCounterBatchByName: (contract: {
34
+ readonly metricByName: ReadonlyMap<string, CompiledMetricDefinition>;
35
+ }, metrics: Metrics, measurements: ReadonlyArray<NamedContractCounterMeasurement>) => (() => void);
36
+ export declare const recordContractCounterByName: (contract: {
37
+ readonly metricByName: ReadonlyMap<string, CompiledMetricDefinition>;
38
+ }, metrics: Metrics, name: string, value: number, attributes: {
39
+ readonly [name: string]: MetricAttributeValue;
40
+ }) => void;
41
+ export declare const makeMetricProducer: <const Definition extends TelemetryContractInput>(contract: TelemetryContract<Definition>, metrics: Metrics) => MetricProducer<Definition>;
42
+ export {};
@@ -0,0 +1,123 @@
1
+ import { prepareContractMetricAttributes, registerContractGaugeObservation, reserveMetricCounterBatch, } from "../MetricsRuntime.js";
2
+ import { InvalidMetricMeasurement } from "./MetricContractError.js";
3
+ import { isFiniteMetricScalar, metricScalarIdentity } from "../metrics/MetricScalar.js";
4
+ const measurementError = (code, operation, alias, metricName, message, attributeName) => {
5
+ const options = { code, operation, message, metricAlias: alias };
6
+ if (metricName !== undefined)
7
+ options.metricName = metricName;
8
+ if (attributeName !== undefined)
9
+ options.attributeName = attributeName;
10
+ return new InvalidMetricMeasurement(options);
11
+ };
12
+ function resolveDefinition(contract, alias, kind) {
13
+ const definition = contract.metricByAlias.get(alias);
14
+ if (definition === undefined) {
15
+ throw measurementError("OBS_METRIC_UNKNOWN_ALIAS", kind, alias, undefined, `Metric alias "${alias}" is not declared by the telemetry contract. Use a declared alias.`);
16
+ }
17
+ if (definition.kind !== kind) {
18
+ throw measurementError("OBS_METRIC_KIND_MISMATCH", kind, alias, definition.name, `Metric alias "${alias}" has kind "${definition.kind}", not "${kind}". Use the matching producer method.`);
19
+ }
20
+ return definition;
21
+ }
22
+ const parseContractAttributes = (alias, definition, input) => {
23
+ const attributes = [];
24
+ for (const attributeName of definition.attributes.keys()) {
25
+ if (!Object.hasOwn(input, attributeName)) {
26
+ throw measurementError("OBS_METRIC_MISSING_ATTRIBUTE", "record", alias, definition.name, `Metric "${definition.name}" is missing declared attribute "${attributeName}". Provide every declared attribute.`, attributeName);
27
+ }
28
+ }
29
+ for (const [attributeName, value] of Object.entries(input)) {
30
+ const attributeDefinition = definition.attributes.get(attributeName);
31
+ if (attributeDefinition === undefined) {
32
+ throw measurementError("OBS_METRIC_UNDECLARED_ATTRIBUTE", "record", alias, definition.name, `Metric "${definition.name}" does not declare attribute "${attributeName}". Remove it or add it to the contract.`, attributeName);
33
+ }
34
+ if (!isFiniteMetricScalar(value)) {
35
+ throw measurementError("OBS_METRIC_INVALID_VALUE", "record", alias, definition.name, `Metric "${definition.name}" attribute "${attributeName}" must be a finite scalar value.`, attributeName);
36
+ }
37
+ const allowedValues = attributeDefinition.allowedValues;
38
+ if (allowedValues !== undefined &&
39
+ !allowedValues.some((allowed) => metricScalarIdentity(allowed) === metricScalarIdentity(value))) {
40
+ throw measurementError("OBS_METRIC_VALUE_NOT_ALLOWED", "record", alias, definition.name, `Metric "${definition.name}" attribute "${attributeName}" is outside its declared allowed values. Use a declared value.`, attributeName);
41
+ }
42
+ attributes.push({ key: attributeName, value });
43
+ }
44
+ return attributes;
45
+ };
46
+ const cardinalityLimits = (definition) => Array.from(definition.attributes, ([attributeName, attribute]) => ({
47
+ attributeName,
48
+ maximumCardinality: attribute.maximumCardinality,
49
+ }));
50
+ const counterHandle = (metrics, alias, definition) => {
51
+ const counter = metrics.counter(definition);
52
+ return {
53
+ add: (value, input) => {
54
+ const attributes = parseContractAttributes(alias, definition, input);
55
+ const commit = prepareContractMetricAttributes(metrics, alias, definition.name, definition, attributes, cardinalityLimits(definition));
56
+ counter.add(value, attributes);
57
+ commit();
58
+ },
59
+ };
60
+ };
61
+ const histogramHandle = (metrics, alias, definition) => {
62
+ const histogram = metrics.histogram(definition);
63
+ return {
64
+ record: (value, input) => {
65
+ const attributes = parseContractAttributes(alias, definition, input);
66
+ const commit = prepareContractMetricAttributes(metrics, alias, definition.name, definition, attributes, cardinalityLimits(definition));
67
+ histogram.record(value, attributes);
68
+ commit();
69
+ },
70
+ };
71
+ };
72
+ const resolveContractCounterByName = (contract, name) => {
73
+ const definition = contract.metricByName.get(name);
74
+ if (definition === undefined) {
75
+ throw measurementError("OBS_METRIC_UNKNOWN_ALIAS", "counter", name, name, `Metric "${name}" is not declared by the telemetry contract. Use a declared canonical metric name.`);
76
+ }
77
+ if (definition.kind !== "counter") {
78
+ throw measurementError("OBS_METRIC_KIND_MISMATCH", "counter", definition.alias, definition.name, `Metric "${name}" has kind "${definition.kind}", not "counter". Use a declared counter.`);
79
+ }
80
+ return definition;
81
+ };
82
+ export const prepareContractCounterBatchByName = (contract, metrics, measurements) => {
83
+ const batch = [];
84
+ for (const measurement of measurements) {
85
+ const definition = resolveContractCounterByName(contract, measurement.name);
86
+ const attributes = parseContractAttributes(definition.alias, definition, measurement.attributes);
87
+ const admission = {
88
+ metrics,
89
+ metricAlias: definition.alias,
90
+ metricName: definition.name,
91
+ definitionIdentity: definition,
92
+ attributes,
93
+ limits: cardinalityLimits(definition),
94
+ };
95
+ batch.push({ definition, value: measurement.value, attributes, admission });
96
+ }
97
+ return reserveMetricCounterBatch(metrics, batch);
98
+ };
99
+ export const recordContractCounterByName = (contract, metrics, name, value, attributes) => prepareContractCounterBatchByName(contract, metrics, [{ name, value, attributes }])();
100
+ export const makeMetricProducer = (contract, metrics) => ({
101
+ counter: (alias) => {
102
+ const definition = resolveDefinition(contract, alias, "counter");
103
+ return counterHandle(metrics, alias, definition);
104
+ },
105
+ histogram: (alias) => {
106
+ const definition = resolveDefinition(contract, alias, "histogram");
107
+ return histogramHandle(metrics, alias, definition);
108
+ },
109
+ observableGauge: (alias, callback) => {
110
+ const definition = resolveDefinition(contract, alias, "observable_gauge");
111
+ return metrics.observableGauge(definition, () => callback().map((observation) => {
112
+ const attributes = parseContractAttributes(alias, definition, observation.attributes);
113
+ return registerContractGaugeObservation({ value: observation.value, attributes }, {
114
+ metrics,
115
+ metricAlias: alias,
116
+ metricName: definition.name,
117
+ definitionIdentity: definition,
118
+ attributes,
119
+ limits: cardinalityLimits(definition),
120
+ });
121
+ }));
122
+ },
123
+ });