@equipe-tech/observability 0.2.0 → 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.
- package/README.md +11 -9
- package/dist/BrowserEvents.d.ts +89 -12
- package/dist/BrowserEvents.js +67 -13
- package/dist/Correlation.d.ts +51 -0
- package/dist/Correlation.js +95 -0
- package/dist/Metrics.d.ts +19 -3
- package/dist/Metrics.js +16 -0
- package/dist/MetricsRuntime.d.ts +45 -3
- package/dist/MetricsRuntime.js +478 -120
- package/dist/PolicyOtlpLogger.d.ts +18 -0
- package/dist/PolicyOtlpLogger.js +142 -0
- package/dist/RedactionPolicy.d.ts +2 -9
- package/dist/RedactionPolicy.js +2 -246
- package/dist/ResourceIdentity.d.ts +47 -0
- package/dist/ResourceIdentity.js +82 -0
- package/dist/Telemetry.d.ts +18 -3
- package/dist/Telemetry.js +43 -25
- package/dist/TelemetryConfig.d.ts +11 -4
- package/dist/TelemetryConfig.js +38 -10
- package/dist/audit/AuditCommitDocument.d.ts +41 -0
- package/dist/audit/AuditCommitDocument.js +58 -0
- package/dist/audit/AuditDigest.d.ts +11 -0
- package/dist/audit/AuditDigest.js +25 -0
- package/dist/audit/AuditOutbox.d.ts +75 -0
- package/dist/audit/AuditOutbox.js +104 -0
- package/dist/audit/AuditPublisher.d.ts +49 -0
- package/dist/audit/AuditPublisher.js +60 -0
- package/dist/audit/AuditRecord.d.ts +128 -0
- package/dist/audit/AuditRecord.js +172 -0
- package/dist/audit/CommittedAuditRecordInternal.d.ts +11 -0
- package/dist/audit/CommittedAuditRecordInternal.js +13 -0
- package/dist/audit/InvalidAuditRecord.d.ts +12 -0
- package/dist/audit/InvalidAuditRecord.js +16 -0
- package/dist/audit/index.d.ts +7 -0
- package/dist/audit/index.js +7 -0
- package/dist/browser/BrowserClient.d.ts +82 -3
- package/dist/browser/BrowserClient.js +322 -28
- package/dist/browser/BrowserEventLimits.d.ts +8 -0
- package/dist/browser/BrowserEventLimits.js +8 -0
- package/dist/browser/BrowserMetricRecorder.d.ts +17 -0
- package/dist/browser/BrowserMetricRecorder.js +35 -0
- package/dist/browser/BrowserTelemetry.d.ts +40 -0
- package/dist/browser/BrowserTelemetry.js +105 -0
- package/dist/browser/ClientPolicy.d.ts +3 -0
- package/dist/browser/ClientPolicy.js +52 -0
- package/dist/browser/client.d.ts +3 -2
- package/dist/browser/client.js +2 -1
- package/dist/browser/index.d.ts +4 -37
- package/dist/browser/index.js +3 -95
- package/dist/contract/ContractCompatibility.d.ts +31 -0
- package/dist/contract/ContractCompatibility.js +274 -0
- package/dist/contract/ContractIndex.d.ts +54 -0
- package/dist/contract/ContractIndex.js +178 -0
- package/dist/contract/ContractSurface.d.ts +111 -0
- package/dist/contract/ContractSurface.js +116 -0
- package/dist/contract/EventName.d.ts +6 -0
- package/dist/contract/EventName.js +55 -0
- package/dist/contract/EventProducer.d.ts +123 -0
- package/dist/contract/EventProducer.js +209 -0
- package/dist/contract/MetricContractError.d.ts +19 -0
- package/dist/contract/MetricContractError.js +19 -0
- package/dist/contract/MetricDefinition.d.ts +61 -0
- package/dist/contract/MetricDefinition.js +91 -0
- package/dist/contract/MetricProducer.d.ts +42 -0
- package/dist/contract/MetricProducer.js +123 -0
- package/dist/contract/OrganizationEvents.d.ts +165 -0
- package/dist/contract/OrganizationEvents.js +97 -0
- package/dist/contract/TelemetryContract.d.ts +95 -0
- package/dist/contract/TelemetryContract.js +451 -0
- package/dist/contract/TelemetryContractError.d.ts +44 -0
- package/dist/contract/TelemetryContractError.js +64 -0
- package/dist/contract/TelemetryEvent.d.ts +59 -0
- package/dist/contract/TelemetryEvent.js +54 -0
- package/dist/contract/index.d.ts +11 -0
- package/dist/contract/index.js +11 -0
- package/dist/effect/WideEvent.d.ts +3 -0
- package/dist/effect/WideEvent.js +20 -0
- package/dist/effect/WideEventSink.d.ts +3 -0
- package/dist/effect/WideEventSink.js +79 -0
- package/dist/effect/index.d.ts +2 -0
- package/dist/effect/index.js +2 -0
- package/dist/index.d.ts +17 -2
- package/dist/index.js +11 -2
- package/dist/metrics/InstrumentGrammar.d.ts +5 -0
- package/dist/metrics/InstrumentGrammar.js +12 -0
- package/dist/metrics/MetricScalar.d.ts +3 -0
- package/dist/metrics/MetricScalar.js +5 -0
- package/dist/node/AuditDigest.d.ts +3 -0
- package/dist/node/AuditDigest.js +6 -0
- package/dist/node/BrowserEventIngest.d.ts +7 -2
- package/dist/node/BrowserEventIngest.js +111 -19
- package/dist/node/LocalCollector.d.ts +20 -0
- package/dist/node/LocalCollector.js +188 -0
- package/dist/node/Observability.d.ts +49 -0
- package/dist/node/Observability.js +190 -0
- package/dist/node/Runtime.d.ts +4 -1
- package/dist/node/index.d.ts +4 -0
- package/dist/node/index.js +3 -0
- package/dist/policy/BrowserFieldPolicy.d.ts +7 -0
- package/dist/policy/BrowserFieldPolicy.js +298 -0
- package/dist/policy/DataPolicy.d.ts +19 -0
- package/dist/policy/DataPolicy.js +254 -0
- package/dist/policy/DataPolicyError.d.ts +22 -0
- package/dist/policy/DataPolicyError.js +25 -0
- package/dist/policy/Deduplication.d.ts +13 -0
- package/dist/policy/Deduplication.js +44 -0
- package/dist/policy/DefectEnvelope.d.ts +25 -0
- package/dist/policy/DefectEnvelope.js +52 -0
- package/dist/policy/MetricLabelPolicy.d.ts +5 -0
- package/dist/policy/MetricLabelPolicy.js +65 -0
- package/dist/policy/PolicyTransform.d.ts +16 -0
- package/dist/policy/PolicyTransform.js +136 -0
- package/dist/policy/PolicyVocabulary.d.ts +10 -0
- package/dist/policy/PolicyVocabulary.js +84 -0
- package/dist/policy/ResourceAttributePolicy.d.ts +9 -0
- package/dist/policy/ResourceAttributePolicy.js +33 -0
- package/dist/policy/entrypoint.d.ts +2 -0
- package/dist/policy/entrypoint.js +2 -0
- package/dist/policy/index.d.ts +17 -0
- package/dist/policy/index.js +9 -0
- package/dist/profile/DataPolicy.d.ts +2 -0
- package/dist/profile/DataPolicy.js +1 -0
- package/dist/profile/EnvironmentPolicy.d.ts +22 -0
- package/dist/profile/EnvironmentPolicy.js +41 -0
- package/dist/profile/LifecycleRegistry.d.ts +24 -0
- package/dist/profile/LifecycleRegistry.js +240 -0
- package/dist/profile/ObservabilityAdapter.d.ts +115 -0
- package/dist/profile/ObservabilityAdapter.js +50 -0
- package/dist/profile/ObservabilityConfig.d.ts +68 -0
- package/dist/profile/ObservabilityConfig.js +125 -0
- package/dist/profile/ObservabilityConfigError.d.ts +23 -0
- package/dist/profile/ObservabilityConfigError.js +39 -0
- package/dist/profile/ObservabilityProfile.d.ts +49 -0
- package/dist/profile/ObservabilityProfile.js +121 -0
- package/dist/profile/ReactWebProfile.d.ts +8 -0
- package/dist/profile/ReactWebProfile.js +7 -0
- package/dist/profile/index.d.ts +5 -0
- package/dist/profile/index.js +5 -0
- package/dist/testing/conformance/ConformanceAssertions.d.ts +5 -0
- package/dist/testing/conformance/ConformanceAssertions.js +31 -0
- package/dist/testing/conformance/ConformanceFailure.d.ts +35 -0
- package/dist/testing/conformance/ConformanceFailure.js +56 -0
- package/dist/testing/conformance/ConformanceModel.d.ts +97 -0
- package/dist/testing/conformance/ConformanceModel.js +8 -0
- package/dist/testing/conformance/ConformanceRegistry.d.ts +13 -0
- package/dist/testing/conformance/ConformanceRegistry.js +190 -0
- package/dist/testing/conformance/ConformanceRunner.d.ts +5 -0
- package/dist/testing/conformance/ConformanceRunner.js +198 -0
- package/dist/testing/conformance/TelemetryEvidence.d.ts +91 -0
- package/dist/testing/conformance/TelemetryEvidence.js +370 -0
- package/dist/testing/conformance/index.d.ts +10 -0
- package/dist/testing/conformance/index.js +6 -0
- package/dist/testing/contract.d.ts +81 -0
- package/dist/testing/contract.js +93 -0
- package/dist/testing/deployedCanary.d.ts +1 -0
- package/dist/testing/deployedCanary.js +1 -0
- package/dist/testing/index.d.ts +40 -3
- package/dist/testing/index.js +122 -5
- package/dist/trace/HttpServerOtlpTracer.d.ts +42 -0
- package/dist/trace/HttpServerOtlpTracer.js +301 -0
- package/package.json +16 -29
- package/dist/WideEvent.d.ts +0 -5
- package/dist/WideEvent.js +0 -6
- package/dist/nestjs/BrowserEventsController.d.ts +0 -21
- package/dist/nestjs/BrowserEventsController.js +0 -52
- package/dist/nestjs/HttpRoutePolicy.d.ts +0 -23
- package/dist/nestjs/HttpRoutePolicy.js +0 -179
- package/dist/nestjs/HttpServerOtlpTracer.d.ts +0 -15
- package/dist/nestjs/HttpServerOtlpTracer.js +0 -154
- package/dist/nestjs/RequestWideEventTraceCorrelation.d.ts +0 -15
- package/dist/nestjs/RequestWideEventTraceCorrelation.js +0 -13
- package/dist/nestjs/TelemetryInterceptor.d.ts +0 -31
- package/dist/nestjs/TelemetryInterceptor.js +0 -268
- package/dist/nestjs/TelemetryModule.d.ts +0 -53
- package/dist/nestjs/TelemetryModule.js +0 -428
- package/dist/nestjs/index.d.ts +0 -5
- package/dist/nestjs/index.js +0 -4
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { Effect, Schema } from "effect";
|
|
2
|
+
import { EnvironmentAliasPolicy, InvalidResourceIdentity, ResourceIdentity } from "./ResourceIdentity.js";
|
|
3
|
+
import type { DuplicateReleaseVariable } from "./profile/ObservabilityConfigError.js";
|
|
2
4
|
export declare const OtlpEndpoint: Schema.URLFromString;
|
|
5
|
+
export interface TelemetryConfigInput {
|
|
6
|
+
readonly identity: ResourceIdentity;
|
|
7
|
+
readonly environmentAlias?: EnvironmentAliasPolicy | undefined;
|
|
8
|
+
readonly otlpEndpoint: URL;
|
|
9
|
+
}
|
|
3
10
|
declare const TelemetryConfig_base: Schema.Class<TelemetryConfig, Schema.Struct<{
|
|
4
|
-
readonly
|
|
5
|
-
readonly
|
|
6
|
-
readonly environment: Schema.NonEmptyString;
|
|
11
|
+
readonly identity: typeof ResourceIdentity;
|
|
12
|
+
readonly environmentAlias: Schema.withConstructorDefault<Schema.Literals<readonly ["emitted", "omitted"]>>;
|
|
7
13
|
readonly otlpEndpoint: Schema.URLFromString;
|
|
8
14
|
}>, {}>;
|
|
9
15
|
export declare class TelemetryConfig extends TelemetryConfig_base {
|
|
16
|
+
constructor(input: TelemetryConfigInput);
|
|
10
17
|
}
|
|
11
18
|
declare const InvalidTelemetryEnvironment_base: Schema.Class<InvalidTelemetryEnvironment, Schema.TaggedStruct<"InvalidTelemetryEnvironment", {
|
|
12
19
|
readonly code: Schema.Literal<"OBS_TELEMETRY_INVALID_ENVIRONMENT">;
|
|
@@ -18,5 +25,5 @@ export declare class InvalidTelemetryEnvironment extends InvalidTelemetryEnviron
|
|
|
18
25
|
export type EnvironmentVariables = {
|
|
19
26
|
readonly [name: string]: string | undefined;
|
|
20
27
|
};
|
|
21
|
-
export declare const telemetryConfigFromEnv: (env: EnvironmentVariables) => Effect.Effect<TelemetryConfig, InvalidTelemetryEnvironment, never>;
|
|
28
|
+
export declare const telemetryConfigFromEnv: (env: EnvironmentVariables) => Effect.Effect<TelemetryConfig, DuplicateReleaseVariable | InvalidResourceIdentity | InvalidTelemetryEnvironment, never>;
|
|
22
29
|
export {};
|
package/dist/TelemetryConfig.js
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
|
-
import { Effect, Schema } from "effect";
|
|
1
|
+
import { Effect, Option, Schema } from "effect";
|
|
2
|
+
import { EnvironmentAliasPolicy, parseResourceIdentity, ResourceIdentity, } from "./ResourceIdentity.js";
|
|
3
|
+
import { rejectSecondReleaseVariables, resolveEnvironmentPolicy, } from "./profile/EnvironmentPolicy.js";
|
|
2
4
|
export const OtlpEndpoint = Schema.URLFromString.check(Schema.makeFilter((url) => (url.protocol === "http:" || url.protocol === "https:") &&
|
|
3
5
|
url.username === "" &&
|
|
4
6
|
url.password === "", { expected: "an HTTP or HTTPS URL without credentials" }));
|
|
5
7
|
export class TelemetryConfig extends Schema.Class("@equipe-tech/observability/TelemetryConfig")({
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
environment: Schema.NonEmptyString,
|
|
8
|
+
identity: ResourceIdentity,
|
|
9
|
+
environmentAlias: EnvironmentAliasPolicy.pipe(Schema.withConstructorDefault(Effect.succeed("omitted"))),
|
|
9
10
|
otlpEndpoint: OtlpEndpoint,
|
|
10
11
|
}) {
|
|
12
|
+
constructor(input) {
|
|
13
|
+
super({
|
|
14
|
+
identity: input.identity,
|
|
15
|
+
environmentAlias: input.environmentAlias ?? "omitted",
|
|
16
|
+
otlpEndpoint: input.otlpEndpoint,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
11
19
|
}
|
|
12
20
|
export class InvalidTelemetryEnvironment extends Schema.TaggedError()("InvalidTelemetryEnvironment", {
|
|
13
21
|
code: Schema.Literal("OBS_TELEMETRY_INVALID_ENVIRONMENT"),
|
|
@@ -17,21 +25,41 @@ export class InvalidTelemetryEnvironment extends Schema.TaggedError()("InvalidTe
|
|
|
17
25
|
}
|
|
18
26
|
const TelemetryEnvironment = Schema.Struct({
|
|
19
27
|
OTEL_SERVICE_NAME: Schema.NonEmptyString,
|
|
20
|
-
OTEL_SERVICE_VERSION: Schema.NonEmptyString.pipe(Schema.
|
|
21
|
-
OTEL_DEPLOYMENT_ENVIRONMENT: Schema.NonEmptyString.pipe(Schema.
|
|
22
|
-
|
|
28
|
+
OTEL_SERVICE_VERSION: Schema.Union([Schema.NonEmptyString, Schema.Undefined]).pipe(Schema.optionalKey),
|
|
29
|
+
OTEL_DEPLOYMENT_ENVIRONMENT: Schema.Union([Schema.NonEmptyString, Schema.Undefined]).pipe(Schema.optionalKey),
|
|
30
|
+
OTEL_SERVICE_INSTANCE_ID: Schema.Union([Schema.String, Schema.Undefined]).pipe(Schema.optionalKey),
|
|
31
|
+
OTEL_EXPORTER_OTLP_ENDPOINT: Schema.Union([OtlpEndpoint, Schema.Undefined]).pipe(Schema.optionalKey),
|
|
23
32
|
});
|
|
24
33
|
const decodeTelemetryEnvironment = Schema.decodeUnknownEffect(TelemetryEnvironment);
|
|
25
34
|
export const telemetryConfigFromEnv = Effect.fn("telemetryConfigFromEnv")(function* (env) {
|
|
35
|
+
yield* rejectSecondReleaseVariables(env);
|
|
26
36
|
const variables = yield* decodeTelemetryEnvironment(env).pipe(Effect.mapError((cause) => new InvalidTelemetryEnvironment({
|
|
27
37
|
code: "OBS_TELEMETRY_INVALID_ENVIRONMENT",
|
|
28
38
|
message: "Telemetry environment is invalid. Set OTEL_SERVICE_NAME and use valid values for the remaining OTEL variables.",
|
|
29
39
|
cause,
|
|
30
40
|
})));
|
|
31
|
-
|
|
32
|
-
|
|
41
|
+
const resolution = resolveEnvironmentPolicy({
|
|
42
|
+
endpoint: variables.OTEL_EXPORTER_OTLP_ENDPOINT,
|
|
33
43
|
serviceVersion: variables.OTEL_SERVICE_VERSION,
|
|
34
44
|
environment: variables.OTEL_DEPLOYMENT_ENVIRONMENT,
|
|
35
|
-
|
|
45
|
+
});
|
|
46
|
+
if (resolution.kind === "missing-remote-identity") {
|
|
47
|
+
return yield* new InvalidTelemetryEnvironment({
|
|
48
|
+
code: "OBS_TELEMETRY_INVALID_ENVIRONMENT",
|
|
49
|
+
message: "A remote OTLP endpoint requires OTEL_SERVICE_VERSION and OTEL_DEPLOYMENT_ENVIRONMENT. Set both canonical identity variables.",
|
|
50
|
+
cause: resolution.missing,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
const identity = yield* parseResourceIdentity({
|
|
54
|
+
serviceName: variables.OTEL_SERVICE_NAME,
|
|
55
|
+
serviceVersion: resolution.serviceVersion,
|
|
56
|
+
environment: resolution.environment,
|
|
57
|
+
instance: variables.OTEL_SERVICE_INSTANCE_ID === ""
|
|
58
|
+
? Option.none()
|
|
59
|
+
: Option.fromNullishOr(variables.OTEL_SERVICE_INSTANCE_ID),
|
|
60
|
+
});
|
|
61
|
+
return new TelemetryConfig({
|
|
62
|
+
identity,
|
|
63
|
+
otlpEndpoint: resolution.endpoint,
|
|
36
64
|
});
|
|
37
65
|
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { AuditHash } from "./AuditDigest.js";
|
|
3
|
+
import { AuditOccurredAt, type AuditRecord } from "./AuditRecord.js";
|
|
4
|
+
export declare const AuditCommitDocument: Schema.Struct<{
|
|
5
|
+
readonly schemaVersion: Schema.Literal<1>;
|
|
6
|
+
readonly recordId: Schema.brand<Schema.NonEmptyString, "AuditRecordId">;
|
|
7
|
+
readonly action: Schema.brand<Schema.NonEmptyString, "AuditAction">;
|
|
8
|
+
readonly actor: Schema.Union<readonly [Schema.Struct<{
|
|
9
|
+
readonly kind: Schema.Literal<"user">;
|
|
10
|
+
readonly id: Schema.brand<Schema.NonEmptyString, "AuditActorId">;
|
|
11
|
+
}>, Schema.Struct<{
|
|
12
|
+
readonly kind: Schema.Literal<"service">;
|
|
13
|
+
readonly id: Schema.brand<Schema.NonEmptyString, "AuditActorId">;
|
|
14
|
+
}>, Schema.Struct<{
|
|
15
|
+
readonly kind: Schema.Literal<"system">;
|
|
16
|
+
}>]>;
|
|
17
|
+
readonly resource: Schema.Struct<{
|
|
18
|
+
readonly type: Schema.brand<Schema.NonEmptyString, "AuditResourceType">;
|
|
19
|
+
readonly id: Schema.brand<Schema.NonEmptyString, "AuditResourceId">;
|
|
20
|
+
}>;
|
|
21
|
+
readonly outcome: Schema.Literals<readonly ["success", "failure", "cancelled", "denied"]>;
|
|
22
|
+
readonly reasonCode: Schema.NullOr<Schema.brand<Schema.NonEmptyString, "AuditReasonCode">>;
|
|
23
|
+
readonly tenantId: Schema.NullOr<Schema.brand<Schema.NonEmptyString, "AuditTenantId">>;
|
|
24
|
+
readonly occurredAt: Schema.brand<Schema.String, "AuditOccurredAt">;
|
|
25
|
+
readonly correlation: Schema.Union<readonly [Schema.Struct<{
|
|
26
|
+
readonly traceId: Schema.Null;
|
|
27
|
+
readonly spanId: Schema.Null;
|
|
28
|
+
readonly requestId: Schema.NullOr<Schema.brand<Schema.NonEmptyString, "RequestId">>;
|
|
29
|
+
readonly runId: Schema.NullOr<Schema.brand<Schema.NonEmptyString, "RunId">>;
|
|
30
|
+
}>, Schema.Struct<{
|
|
31
|
+
readonly traceId: Schema.brand<Schema.String, "TraceId">;
|
|
32
|
+
readonly spanId: Schema.brand<Schema.String, "SpanId">;
|
|
33
|
+
readonly requestId: Schema.NullOr<Schema.brand<Schema.NonEmptyString, "RequestId">>;
|
|
34
|
+
readonly runId: Schema.NullOr<Schema.brand<Schema.NonEmptyString, "RunId">>;
|
|
35
|
+
}>]>;
|
|
36
|
+
readonly committedAt: Schema.brand<Schema.String, "AuditOccurredAt">;
|
|
37
|
+
readonly ledgerHash: Schema.brand<Schema.String, "AuditHash">;
|
|
38
|
+
}>;
|
|
39
|
+
export type AuditCommitDocument = typeof AuditCommitDocument.Type;
|
|
40
|
+
export type AuditCommitDocumentEncoded = typeof AuditCommitDocument.Encoded;
|
|
41
|
+
export declare const auditCommitDocumentFor: (record: AuditRecord, committedAt: AuditOccurredAt, ledgerHash: AuditHash) => AuditCommitDocument;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Option, Schema } from "effect";
|
|
2
|
+
import { RequestId, RunId, SpanId, TraceId } from "../Correlation.js";
|
|
3
|
+
import { AuditHash } from "./AuditDigest.js";
|
|
4
|
+
import { AuditAction, AuditActor, AuditOccurredAt, AuditOutcome, AuditReasonCode, AuditRecordId, AuditResource, AuditTenantId, } from "./AuditRecord.js";
|
|
5
|
+
const AuditCommitCorrelation = Schema.Union([
|
|
6
|
+
Schema.Struct({
|
|
7
|
+
traceId: Schema.Null,
|
|
8
|
+
spanId: Schema.Null,
|
|
9
|
+
requestId: Schema.NullOr(RequestId),
|
|
10
|
+
runId: Schema.NullOr(RunId),
|
|
11
|
+
}),
|
|
12
|
+
Schema.Struct({
|
|
13
|
+
traceId: TraceId,
|
|
14
|
+
spanId: SpanId,
|
|
15
|
+
requestId: Schema.NullOr(RequestId),
|
|
16
|
+
runId: Schema.NullOr(RunId),
|
|
17
|
+
}),
|
|
18
|
+
]);
|
|
19
|
+
export const AuditCommitDocument = Schema.Struct({
|
|
20
|
+
schemaVersion: Schema.Literal(1),
|
|
21
|
+
recordId: AuditRecordId,
|
|
22
|
+
action: AuditAction,
|
|
23
|
+
actor: AuditActor,
|
|
24
|
+
resource: AuditResource,
|
|
25
|
+
outcome: AuditOutcome,
|
|
26
|
+
reasonCode: Schema.NullOr(AuditReasonCode),
|
|
27
|
+
tenantId: Schema.NullOr(AuditTenantId),
|
|
28
|
+
occurredAt: AuditOccurredAt,
|
|
29
|
+
correlation: AuditCommitCorrelation,
|
|
30
|
+
committedAt: AuditOccurredAt,
|
|
31
|
+
ledgerHash: AuditHash,
|
|
32
|
+
});
|
|
33
|
+
export const auditCommitDocumentFor = (record, committedAt, ledgerHash) => ({
|
|
34
|
+
schemaVersion: record.schemaVersion,
|
|
35
|
+
recordId: record.recordId,
|
|
36
|
+
action: record.action,
|
|
37
|
+
actor: record.actor,
|
|
38
|
+
resource: record.resource,
|
|
39
|
+
outcome: record.outcome,
|
|
40
|
+
reasonCode: Option.getOrNull(record.reasonCode),
|
|
41
|
+
tenantId: Option.getOrNull(record.tenantId),
|
|
42
|
+
occurredAt: record.occurredAt,
|
|
43
|
+
correlation: record.correlation.trace._tag === "Untraced"
|
|
44
|
+
? {
|
|
45
|
+
traceId: null,
|
|
46
|
+
spanId: null,
|
|
47
|
+
requestId: Option.getOrNull(record.correlation.requestId),
|
|
48
|
+
runId: Option.getOrNull(record.correlation.runId),
|
|
49
|
+
}
|
|
50
|
+
: {
|
|
51
|
+
traceId: record.correlation.trace.traceId,
|
|
52
|
+
spanId: record.correlation.trace.spanId,
|
|
53
|
+
requestId: Option.getOrNull(record.correlation.requestId),
|
|
54
|
+
runId: Option.getOrNull(record.correlation.runId),
|
|
55
|
+
},
|
|
56
|
+
committedAt,
|
|
57
|
+
ledgerHash,
|
|
58
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Context, Effect, Schema } from "effect";
|
|
2
|
+
import type { AuditOccurredAt, AuditRecord } from "./AuditRecord.js";
|
|
3
|
+
export declare const AuditHash: Schema.brand<Schema.String, "AuditHash">;
|
|
4
|
+
export type AuditHash = typeof AuditHash.Type;
|
|
5
|
+
export declare const canonicalAuditPayload: (record: AuditRecord, committedAt: AuditOccurredAt) => string;
|
|
6
|
+
declare const AuditDigest_base: Context.ServiceClass<AuditDigest, "@equipe-tech/observability/AuditDigest", {
|
|
7
|
+
readonly hash: (payload: string) => Effect.Effect<AuditHash>;
|
|
8
|
+
}>;
|
|
9
|
+
export declare class AuditDigest extends AuditDigest_base {
|
|
10
|
+
}
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Context, Schema } from "effect";
|
|
2
|
+
import { Option } from "effect";
|
|
3
|
+
export const AuditHash = Schema.String.check(Schema.isPattern(/^[0-9a-f]{64}$/)).pipe(Schema.brand("AuditHash"));
|
|
4
|
+
export const canonicalAuditPayload = (record, committedAt) => JSON.stringify({
|
|
5
|
+
action: record.action,
|
|
6
|
+
actor: record.actor.kind === "system"
|
|
7
|
+
? { kind: record.actor.kind }
|
|
8
|
+
: { id: record.actor.id, kind: record.actor.kind },
|
|
9
|
+
committedAt,
|
|
10
|
+
correlation: {
|
|
11
|
+
requestId: Option.getOrNull(record.correlation.requestId),
|
|
12
|
+
runId: Option.getOrNull(record.correlation.runId),
|
|
13
|
+
spanId: Option.getOrNull(record.correlation.spanId),
|
|
14
|
+
traceId: Option.getOrNull(record.correlation.traceId),
|
|
15
|
+
},
|
|
16
|
+
occurredAt: record.occurredAt,
|
|
17
|
+
outcome: record.outcome,
|
|
18
|
+
reasonCode: Option.getOrNull(record.reasonCode),
|
|
19
|
+
recordId: record.recordId,
|
|
20
|
+
resource: { id: record.resource.id, type: record.resource.type },
|
|
21
|
+
schemaVersion: record.schemaVersion,
|
|
22
|
+
tenantId: Option.getOrNull(record.tenantId),
|
|
23
|
+
});
|
|
24
|
+
export class AuditDigest extends Context.Service()("@equipe-tech/observability/AuditDigest") {
|
|
25
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Context, Effect, Schema } from "effect";
|
|
2
|
+
import { AuditDigest } from "./AuditDigest.js";
|
|
3
|
+
import { AuditPublisher, type AuditPublishReceipt } from "./AuditPublisher.js";
|
|
4
|
+
declare const AuditOutboxFailure_base: Schema.Class<AuditOutboxFailure, Schema.TaggedStruct<"AuditOutboxFailure", {
|
|
5
|
+
readonly code: Schema.Literal<"OBS_AUDIT_OUTBOX_FAILED">;
|
|
6
|
+
readonly message: Schema.String;
|
|
7
|
+
readonly operation: Schema.Literals<readonly ["claim", "parse", "publish", "settle"]>;
|
|
8
|
+
readonly cause: Schema.Defect;
|
|
9
|
+
}>, import("effect/Cause").YieldableError>;
|
|
10
|
+
export declare class AuditOutboxFailure extends AuditOutboxFailure_base {
|
|
11
|
+
}
|
|
12
|
+
export declare const AuditOutboxDocument: Schema.Struct<{
|
|
13
|
+
readonly schemaVersion: Schema.Literal<1>;
|
|
14
|
+
readonly recordId: Schema.brand<Schema.NonEmptyString, "AuditRecordId">;
|
|
15
|
+
readonly action: Schema.brand<Schema.NonEmptyString, "AuditAction">;
|
|
16
|
+
readonly actor: Schema.Union<readonly [Schema.Struct<{
|
|
17
|
+
readonly kind: Schema.Literal<"user">;
|
|
18
|
+
readonly id: Schema.brand<Schema.NonEmptyString, "AuditActorId">;
|
|
19
|
+
}>, Schema.Struct<{
|
|
20
|
+
readonly kind: Schema.Literal<"service">;
|
|
21
|
+
readonly id: Schema.brand<Schema.NonEmptyString, "AuditActorId">;
|
|
22
|
+
}>, Schema.Struct<{
|
|
23
|
+
readonly kind: Schema.Literal<"system">;
|
|
24
|
+
}>]>;
|
|
25
|
+
readonly resource: Schema.Struct<{
|
|
26
|
+
readonly type: Schema.brand<Schema.NonEmptyString, "AuditResourceType">;
|
|
27
|
+
readonly id: Schema.brand<Schema.NonEmptyString, "AuditResourceId">;
|
|
28
|
+
}>;
|
|
29
|
+
readonly outcome: Schema.Literals<readonly ["success", "failure", "cancelled", "denied"]>;
|
|
30
|
+
readonly reasonCode: Schema.NullOr<Schema.brand<Schema.NonEmptyString, "AuditReasonCode">>;
|
|
31
|
+
readonly tenantId: Schema.NullOr<Schema.brand<Schema.NonEmptyString, "AuditTenantId">>;
|
|
32
|
+
readonly occurredAt: Schema.brand<Schema.String, "AuditOccurredAt">;
|
|
33
|
+
readonly correlation: Schema.Union<readonly [Schema.Struct<{
|
|
34
|
+
readonly traceId: Schema.Null;
|
|
35
|
+
readonly spanId: Schema.Null;
|
|
36
|
+
readonly requestId: Schema.NullOr<Schema.brand<Schema.NonEmptyString, "RequestId">>;
|
|
37
|
+
readonly runId: Schema.NullOr<Schema.brand<Schema.NonEmptyString, "RunId">>;
|
|
38
|
+
}>, Schema.Struct<{
|
|
39
|
+
readonly traceId: Schema.brand<Schema.String, "TraceId">;
|
|
40
|
+
readonly spanId: Schema.brand<Schema.String, "SpanId">;
|
|
41
|
+
readonly requestId: Schema.NullOr<Schema.brand<Schema.NonEmptyString, "RequestId">>;
|
|
42
|
+
readonly runId: Schema.NullOr<Schema.brand<Schema.NonEmptyString, "RunId">>;
|
|
43
|
+
}>]>;
|
|
44
|
+
readonly committedAt: Schema.brand<Schema.String, "AuditOccurredAt">;
|
|
45
|
+
readonly ledgerHash: Schema.brand<Schema.String, "AuditHash">;
|
|
46
|
+
}>;
|
|
47
|
+
export type AuditOutboxDocument = typeof AuditOutboxDocument.Type;
|
|
48
|
+
export declare const AuditOutboxClaimKey: Schema.brand<Schema.NonEmptyString, "AuditOutboxClaimKey">;
|
|
49
|
+
export type AuditOutboxClaimKey = typeof AuditOutboxClaimKey.Type;
|
|
50
|
+
export type AuditOutboxValue = string | number | boolean | null | ReadonlyArray<AuditOutboxValue> | {
|
|
51
|
+
readonly [field: string]: AuditOutboxValue;
|
|
52
|
+
};
|
|
53
|
+
export type AuditOutboxEntry = {
|
|
54
|
+
readonly claimKey: AuditOutboxClaimKey;
|
|
55
|
+
readonly document: AuditOutboxValue;
|
|
56
|
+
};
|
|
57
|
+
export type AuditOutboxSettlement = AuditPublishReceipt | {
|
|
58
|
+
readonly kind: "quarantined";
|
|
59
|
+
readonly reason: "invalid-document";
|
|
60
|
+
};
|
|
61
|
+
declare const AuditOutbox_base: Context.ServiceClass<AuditOutbox, "@equipe-tech/observability/AuditOutbox", {
|
|
62
|
+
readonly claim: (maximum: number) => Effect.Effect<ReadonlyArray<AuditOutboxEntry>, AuditOutboxFailure>;
|
|
63
|
+
readonly settle: (claimKey: AuditOutboxClaimKey, settlement: AuditOutboxSettlement) => Effect.Effect<void, AuditOutboxFailure>;
|
|
64
|
+
}>;
|
|
65
|
+
export declare class AuditOutbox extends AuditOutbox_base {
|
|
66
|
+
}
|
|
67
|
+
export type AuditOutboxDrainReport = {
|
|
68
|
+
readonly claimed: number;
|
|
69
|
+
readonly published: number;
|
|
70
|
+
readonly deduplicated: number;
|
|
71
|
+
readonly dropped: number;
|
|
72
|
+
readonly quarantined: number;
|
|
73
|
+
};
|
|
74
|
+
export declare const drainAuditOutbox: (maximum: number) => Effect.Effect<AuditOutboxDrainReport, AuditOutboxFailure, AuditDigest | AuditOutbox | AuditPublisher>;
|
|
75
|
+
export {};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { Context, Effect, Option, Schema } from "effect";
|
|
2
|
+
import { CorrelationContext } from "../Correlation.js";
|
|
3
|
+
import { AuditCommitDocument as AuditCommitDocumentSchema } from "./AuditCommitDocument.js";
|
|
4
|
+
import { AuditDigest, canonicalAuditPayload } from "./AuditDigest.js";
|
|
5
|
+
import { sealCommittedAuditRecord, } from "./CommittedAuditRecordInternal.js";
|
|
6
|
+
import { reparseAuditRecord } from "./AuditRecord.js";
|
|
7
|
+
import { AuditPublisher } from "./AuditPublisher.js";
|
|
8
|
+
export class AuditOutboxFailure extends Schema.TaggedError()("AuditOutboxFailure", {
|
|
9
|
+
code: Schema.Literal("OBS_AUDIT_OUTBOX_FAILED"),
|
|
10
|
+
message: Schema.String,
|
|
11
|
+
operation: Schema.Literals(["claim", "parse", "publish", "settle"]),
|
|
12
|
+
cause: Schema.Defect(),
|
|
13
|
+
}) {
|
|
14
|
+
}
|
|
15
|
+
export const AuditOutboxDocument = AuditCommitDocumentSchema;
|
|
16
|
+
export const AuditOutboxClaimKey = Schema.NonEmptyString.check(Schema.isMaxLength(256)).pipe(Schema.brand("AuditOutboxClaimKey"));
|
|
17
|
+
export class AuditOutbox extends Context.Service()("@equipe-tech/observability/AuditOutbox") {
|
|
18
|
+
}
|
|
19
|
+
const decodeDocument = Schema.decodeUnknownEffect(AuditOutboxDocument);
|
|
20
|
+
const failure = (operation, message, cause) => new AuditOutboxFailure({
|
|
21
|
+
code: "OBS_AUDIT_OUTBOX_FAILED",
|
|
22
|
+
operation,
|
|
23
|
+
message,
|
|
24
|
+
cause,
|
|
25
|
+
});
|
|
26
|
+
const restore = Effect.fn("restoreAuditOutboxRecord")(function* (document) {
|
|
27
|
+
const decoded = yield* decodeDocument(document).pipe(Effect.mapError(() => failure("parse", "The claimed audit outbox document is invalid.", "invalid document")));
|
|
28
|
+
const correlation = new CorrelationContext({
|
|
29
|
+
trace: decoded.correlation.traceId === null
|
|
30
|
+
? { _tag: "Untraced" }
|
|
31
|
+
: {
|
|
32
|
+
_tag: "Traced",
|
|
33
|
+
traceId: decoded.correlation.traceId,
|
|
34
|
+
spanId: decoded.correlation.spanId,
|
|
35
|
+
},
|
|
36
|
+
requestId: decoded.correlation.requestId === null
|
|
37
|
+
? Option.none()
|
|
38
|
+
: Option.some(decoded.correlation.requestId),
|
|
39
|
+
runId: decoded.correlation.runId === null ? Option.none() : Option.some(decoded.correlation.runId),
|
|
40
|
+
});
|
|
41
|
+
const record = yield* reparseAuditRecord({
|
|
42
|
+
schemaVersion: decoded.schemaVersion,
|
|
43
|
+
recordId: decoded.recordId,
|
|
44
|
+
action: decoded.action,
|
|
45
|
+
actor: decoded.actor,
|
|
46
|
+
resource: decoded.resource,
|
|
47
|
+
outcome: decoded.outcome,
|
|
48
|
+
reasonCode: decoded.reasonCode === null ? Option.none() : Option.some(decoded.reasonCode),
|
|
49
|
+
tenantId: decoded.tenantId === null ? Option.none() : Option.some(decoded.tenantId),
|
|
50
|
+
occurredAt: decoded.occurredAt,
|
|
51
|
+
correlation,
|
|
52
|
+
}).pipe(Effect.orDie);
|
|
53
|
+
const digest = yield* AuditDigest;
|
|
54
|
+
const ledgerHash = yield* digest.hash(canonicalAuditPayload(record, decoded.committedAt));
|
|
55
|
+
if (ledgerHash !== decoded.ledgerHash) {
|
|
56
|
+
return yield* failure("parse", "The claimed audit outbox document does not match its ledger hash.", "digest mismatch");
|
|
57
|
+
}
|
|
58
|
+
return sealCommittedAuditRecord(record, decoded.committedAt, decoded.ledgerHash);
|
|
59
|
+
});
|
|
60
|
+
export const drainAuditOutbox = Effect.fn("drainAuditOutbox")(function* (maximum) {
|
|
61
|
+
const outbox = yield* AuditOutbox;
|
|
62
|
+
const publisher = yield* AuditPublisher;
|
|
63
|
+
const entries = yield* outbox
|
|
64
|
+
.claim(maximum)
|
|
65
|
+
.pipe(Effect.mapError(() => failure("claim", "The audit outbox could not claim pending records.", "audit outbox claim failed")));
|
|
66
|
+
const settle = (claimKey, settlement) => outbox
|
|
67
|
+
.settle(claimKey, settlement)
|
|
68
|
+
.pipe(Effect.mapError(() => failure("settle", "The audit outbox could not settle a claimed record.", "audit outbox settlement failed")));
|
|
69
|
+
let published = 0;
|
|
70
|
+
let deduplicated = 0;
|
|
71
|
+
let dropped = 0;
|
|
72
|
+
let quarantined = 0;
|
|
73
|
+
for (const entry of entries) {
|
|
74
|
+
const restored = yield* restore(entry.document).pipe(Effect.match({ onFailure: () => Option.none(), onSuccess: Option.some }));
|
|
75
|
+
if (Option.isNone(restored)) {
|
|
76
|
+
yield* settle(entry.claimKey, {
|
|
77
|
+
kind: "quarantined",
|
|
78
|
+
reason: "invalid-document",
|
|
79
|
+
});
|
|
80
|
+
quarantined += 1;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const receipt = yield* publisher.publish(restored.value);
|
|
84
|
+
yield* settle(entry.claimKey, receipt);
|
|
85
|
+
switch (receipt.kind) {
|
|
86
|
+
case "published":
|
|
87
|
+
published += 1;
|
|
88
|
+
break;
|
|
89
|
+
case "deduplicated":
|
|
90
|
+
deduplicated += 1;
|
|
91
|
+
break;
|
|
92
|
+
case "dropped":
|
|
93
|
+
dropped += 1;
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
claimed: entries.length,
|
|
99
|
+
published,
|
|
100
|
+
deduplicated,
|
|
101
|
+
dropped,
|
|
102
|
+
quarantined,
|
|
103
|
+
};
|
|
104
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Context, Effect, Option } from "effect";
|
|
2
|
+
import { type AuditCommitDocument } from "./AuditCommitDocument.js";
|
|
3
|
+
import { AuditDigest } from "./AuditDigest.js";
|
|
4
|
+
import { type CommittedAuditRecord } from "./CommittedAuditRecordInternal.js";
|
|
5
|
+
import { type AuditRecord } from "./AuditRecord.js";
|
|
6
|
+
import { InvalidAuditRecord } from "./InvalidAuditRecord.js";
|
|
7
|
+
export type { CommittedAuditRecord } from "./CommittedAuditRecordInternal.js";
|
|
8
|
+
export type AuditDropReason = "unbound" | "closed" | "queue-overflow" | "contract-rejected" | "policy-rejected" | "transport";
|
|
9
|
+
export type AuditPublishReceipt = {
|
|
10
|
+
readonly kind: "published";
|
|
11
|
+
} | {
|
|
12
|
+
readonly kind: "deduplicated";
|
|
13
|
+
} | {
|
|
14
|
+
readonly kind: "dropped";
|
|
15
|
+
readonly reason: AuditDropReason;
|
|
16
|
+
};
|
|
17
|
+
export type AuditPublishReasonCounts = {
|
|
18
|
+
readonly unbound: number;
|
|
19
|
+
readonly closed: number;
|
|
20
|
+
readonly queueOverflow: number;
|
|
21
|
+
readonly contractRejected: number;
|
|
22
|
+
readonly policyRejected: number;
|
|
23
|
+
readonly transport: number;
|
|
24
|
+
};
|
|
25
|
+
export type AuditPublishReport = {
|
|
26
|
+
readonly published: number;
|
|
27
|
+
readonly deduplicated: number;
|
|
28
|
+
readonly dropped: number;
|
|
29
|
+
readonly firstDroppedAt: Option.Option<string>;
|
|
30
|
+
readonly lastDroppedAt: Option.Option<string>;
|
|
31
|
+
readonly reasons: AuditPublishReasonCounts;
|
|
32
|
+
};
|
|
33
|
+
export type AuditPublisherService = {
|
|
34
|
+
readonly publish: (record: CommittedAuditRecord) => Effect.Effect<AuditPublishReceipt>;
|
|
35
|
+
readonly report: () => AuditPublishReport;
|
|
36
|
+
};
|
|
37
|
+
export declare const unboundAuditPublisher: () => AuditPublisherService;
|
|
38
|
+
declare const AuditPublisher_base: Context.ServiceClass<AuditPublisher, "@equipe-tech/observability/AuditPublisher", AuditPublisherService>;
|
|
39
|
+
export declare class AuditPublisher extends AuditPublisher_base {
|
|
40
|
+
}
|
|
41
|
+
export type CommitAuditResult<A> = {
|
|
42
|
+
readonly committed: A;
|
|
43
|
+
readonly record: CommittedAuditRecord;
|
|
44
|
+
};
|
|
45
|
+
export declare const commitAuditRecord: <A, E, R>(record: AuditRecord, durableWrite: (document: AuditCommitDocument) => Effect.Effect<A, E, R>) => Effect.Effect<CommitAuditResult<A>, E | InvalidAuditRecord, R | AuditDigest>;
|
|
46
|
+
export type RecordAuditResult<A> = CommitAuditResult<A> & {
|
|
47
|
+
readonly publish: AuditPublishReceipt;
|
|
48
|
+
};
|
|
49
|
+
export declare const recordAudit: <A, E, R>(record: AuditRecord, durableWrite: (document: AuditCommitDocument) => Effect.Effect<A, E, R>) => Effect.Effect<RecordAuditResult<A>, E | InvalidAuditRecord, R | AuditDigest | AuditPublisher>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Clock, Context, DateTime, Effect, Option, Schema } from "effect";
|
|
2
|
+
import { auditCommitDocumentFor } from "./AuditCommitDocument.js";
|
|
3
|
+
import { AuditDigest, canonicalAuditPayload } from "./AuditDigest.js";
|
|
4
|
+
import { sealCommittedAuditRecord, } from "./CommittedAuditRecordInternal.js";
|
|
5
|
+
import { AuditOccurredAt, reparseAuditRecord } from "./AuditRecord.js";
|
|
6
|
+
const emptyReport = () => ({
|
|
7
|
+
published: 0,
|
|
8
|
+
deduplicated: 0,
|
|
9
|
+
dropped: 0,
|
|
10
|
+
firstDroppedAt: Option.none(),
|
|
11
|
+
lastDroppedAt: Option.none(),
|
|
12
|
+
reasons: {
|
|
13
|
+
unbound: 0,
|
|
14
|
+
closed: 0,
|
|
15
|
+
queueOverflow: 0,
|
|
16
|
+
contractRejected: 0,
|
|
17
|
+
policyRejected: 0,
|
|
18
|
+
transport: 0,
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
export const unboundAuditPublisher = () => {
|
|
22
|
+
let drops = 0;
|
|
23
|
+
let firstDrop = Option.none();
|
|
24
|
+
let lastDrop = Option.none();
|
|
25
|
+
return {
|
|
26
|
+
publish: () => Effect.gen(function* () {
|
|
27
|
+
const droppedAt = DateTime.formatIso(DateTime.makeUnsafe(yield* Clock.currentTimeMillis));
|
|
28
|
+
drops += 1;
|
|
29
|
+
if (Option.isNone(firstDrop))
|
|
30
|
+
firstDrop = Option.some(droppedAt);
|
|
31
|
+
lastDrop = Option.some(droppedAt);
|
|
32
|
+
return { kind: "dropped", reason: "unbound" };
|
|
33
|
+
}),
|
|
34
|
+
report: () => ({
|
|
35
|
+
...emptyReport(),
|
|
36
|
+
dropped: drops,
|
|
37
|
+
firstDroppedAt: firstDrop,
|
|
38
|
+
lastDroppedAt: lastDrop,
|
|
39
|
+
reasons: { ...emptyReport().reasons, unbound: drops },
|
|
40
|
+
}),
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
export class AuditPublisher extends Context.Service()("@equipe-tech/observability/AuditPublisher") {
|
|
44
|
+
}
|
|
45
|
+
export const commitAuditRecord = (record, durableWrite) => Effect.gen(function* () {
|
|
46
|
+
const reparsed = yield* reparseAuditRecord(record);
|
|
47
|
+
const committedAt = Schema.decodeUnknownSync(AuditOccurredAt)(DateTime.formatIso(DateTime.makeUnsafe(yield* Clock.currentTimeMillis)));
|
|
48
|
+
const digest = yield* AuditDigest;
|
|
49
|
+
const ledgerHash = yield* digest.hash(canonicalAuditPayload(reparsed, committedAt));
|
|
50
|
+
const document = auditCommitDocumentFor(reparsed, committedAt, ledgerHash);
|
|
51
|
+
const committed = yield* durableWrite(document);
|
|
52
|
+
const committedRecord = sealCommittedAuditRecord(reparsed, committedAt, ledgerHash);
|
|
53
|
+
return { committed, record: committedRecord };
|
|
54
|
+
});
|
|
55
|
+
export const recordAudit = (record, durableWrite) => Effect.gen(function* () {
|
|
56
|
+
const result = yield* commitAuditRecord(record, durableWrite);
|
|
57
|
+
const publisher = yield* AuditPublisher;
|
|
58
|
+
const publish = yield* publisher.publish(result.record);
|
|
59
|
+
return { ...result, publish };
|
|
60
|
+
});
|