1import type * as Effect from "effect/Effect";
2import type * as Stream from "effect/Stream";
3import {
4 consumeBusEvents,
5 type EventRecord,
6 type EventRouteProps,
7} from "../EventBridge/EventSource.ts";
8
9/** The category of a Macie finding. */
10export type FindingCategory = "CLASSIFICATION" | "POLICY";
11
12/** The qualitative severity of a Macie finding. */
13export type FindingSeverityDescription = "Low" | "Medium" | "High";
14
15/**
16 * The `detail` payload Macie delivers to EventBridge for a finding — the full
17 * finding document. Only the commonly-matched fields are typed; the schema
18 * grows over time.
19 */
20export interface FindingEventDetail {
21 /** The finding id. */
22 id?: string;
23 /** The finding type, e.g. `SensitiveData:S3Object/Personal`. */
24 type?: string;
25 /** Whether the finding is a sensitive-data or policy finding. */
26 category?: FindingCategory;
27 /** The finding severity (score 1-3 plus a qualitative description). */
28 severity?: { score?: number; description?: FindingSeverityDescription };
29 /** The account the finding was generated in. */
30 accountId?: string;
31 /** The region the finding was generated in. */
32 region?: string;
33 /** The finding title. */
34 title?: string;
35 /** The finding description. */
36 description?: string;
37 /** The affected resources document (S3 bucket + object details). */
38 resourcesAffected?: Record<string, unknown>;
39 /** For classification findings, the sensitive-data detection details. */
40 classificationDetails?: Record<string, unknown>;
41 /** Additional finding fields (the schema grows over time). */
42 [key: string]: unknown;
43}
44
45/** A Macie finding EventBridge event delivered to the handler. */
46export type FindingEvent = EventRecord<FindingEventDetail>;
47
48export interface FindingEventSourceProps extends EventRouteProps {
49 /**
50 * Logical id for the backing EventBridge rule.
51 * @default "MacieFindings"
52 */
53 id?: string;
54 /**
55 * Restrict to specific finding types (matched against `detail.type`),
56 * e.g. `["SensitiveData:S3Object/Personal"]`.
57 */
58 types?: readonly string[];
59 /**
60 * Restrict to a finding category: `CLASSIFICATION` (sensitive data found in
61 * an object) or `POLICY` (a bucket policy/configuration issue).
62 */
63 categories?: readonly FindingCategory[];
64 /**
65 * Restrict to qualitative severities (matched against
66 * `detail.severity.description`), e.g. `["High"]`.
67 */
68 severities?: readonly FindingSeverityDescription[];
69}
70
71/**
72 * Event source connecting Amazon Macie findings to the hosting compute.
73 * Macie publishes every new finding (and periodic updates for recurring ones,
74 * at the session's `findingPublishingFrequency`) to the account's default
75 * EventBridge bus (source `aws.macie`, detail-type `Macie Finding`); this
76 * subscribes the host Function to those events so it can triage, notify, or
77 * quarantine the affected S3 objects.
78 *
79 * Macie publishes to EventBridge automatically — no additional resource is
80 * created besides the EventBridge rule targeting the host. Provide the
81 * host-specific implementation layer (e.g. `AWS.Lambda.EventSource`) on the
82 * Function effect.
83 *
84 * ### Consuming Findings
85 * **Example:** Alert on High-Severity Sensitive Data
86 * ```typescript
87 * import * as AWS from "alchemy/AWS";
88 *
89 * export default AlertFunction.make(
90 * { main: import.meta.url },
91 * Effect.gen(function* () {
92 * yield* AWS.Macie2.consumeFindings(
93 * { categories: ["CLASSIFICATION"], severities: ["High"] },
94 * (events) =>
95 * Stream.runForEach(events, (event) =>
96 * Effect.logError(
97 * `Macie: ${event.detail.type} in ${event.detail.accountId}`,
98 * ),
99 * ),
100 * );
101 * return {};
102 * }).pipe(Effect.provide(AWS.Lambda.EventSource)),
103 * );
104 * ```
105 */
106export const consumeFindings = <StreamReq = never, Req = never>(
107 props: FindingEventSourceProps,
108 process: (
109 events: Stream.Stream<FindingEvent, never, StreamReq>,
110 ) => Effect.Effect<void, never, Req>,
111) =>
112 consumeBusEvents(
113 props.id ?? "MacieFindings",
114 {
115 source: ["aws.macie"],
116 "detail-type": ["Macie Finding"],
117 ...(props.types !== undefined ||
118 props.categories !== undefined ||
119 props.severities !== undefined
120 ? {
121 detail: {
122 ...(props.types !== undefined ? { type: [...props.types] } : {}),
123 ...(props.categories !== undefined
124 ? { category: [...props.categories] }
125 : {}),
126 ...(props.severities !== undefined
127 ? { severity: { description: [...props.severities] } }
128 : {}),
129 },
130 }
131 : {}),
132 },
133 { description: props.description, state: props.state },
134 process,
135 );