fix: centralize lineage durability in AUD0 client boundary
This commit is contained in:
@@ -1,18 +1,13 @@
|
||||
/**
|
||||
* 0pt OpenLineage emitter.
|
||||
*
|
||||
* AUD0 is canonical evidence. When 0pt has a repository DATABASE_URL the
|
||||
* emitter first persists the exact RunEvent to the local AUD0 outbox and
|
||||
* returns `queued:true`; the dispatcher is then the only delivery path. This
|
||||
* preserves the historic never-throw-after-domain-work contract while removing
|
||||
* AUD0 availability as an evidence-loss window. In env-less/unit runtimes the
|
||||
* emitter keeps the direct client behavior.
|
||||
* Durability belongs to the AUD0 client boundary, not here. The default
|
||||
* createAud0Client() persists to the local outbox when DATABASE_URL exists;
|
||||
* the raw dispatcher client (durable:false) performs the actual HTTP delivery.
|
||||
* This emitter therefore builds one canonical RunEvent and records it exactly
|
||||
* once through the injected client.
|
||||
*/
|
||||
import type { Aud0Client } from "../clients/aud0-client";
|
||||
import {
|
||||
createAud0OutboxRepositoryFromEnv,
|
||||
type Aud0OutboxRepository,
|
||||
} from "../evidence/aud0-outbox";
|
||||
import {
|
||||
OPT_OPENLINEAGE_PRODUCER,
|
||||
type OpenLineageDatasetRef,
|
||||
@@ -41,9 +36,9 @@ export interface LineageDatasetInput {
|
||||
}
|
||||
|
||||
export interface LineageEmitResult {
|
||||
/** True only when AUD0 acknowledged the event synchronously. */
|
||||
/** True only when AUD0 itself acknowledged the event synchronously. */
|
||||
emitted: boolean;
|
||||
/** True when the exact event is durably persisted for at-least-once delivery. */
|
||||
/** True when the event is durably queued for at-least-once AUD0 delivery. */
|
||||
queued: boolean;
|
||||
run_id: string;
|
||||
outbox_id?: string;
|
||||
@@ -68,16 +63,7 @@ function safeWarning(error: unknown): string {
|
||||
}
|
||||
|
||||
export class LineageEmitter {
|
||||
private readonly outbox?: Aud0OutboxRepository;
|
||||
|
||||
constructor(
|
||||
private readonly aud0: Aud0Client,
|
||||
opts?: { outbox?: Aud0OutboxRepository | null },
|
||||
) {
|
||||
this.outbox = opts && "outbox" in opts
|
||||
? opts.outbox ?? undefined
|
||||
: createAud0OutboxRepositoryFromEnv();
|
||||
}
|
||||
constructor(private readonly aud0: Aud0Client) {}
|
||||
|
||||
buildRunEvent(input: LineageEmitInput): OpenLineageRunEvent {
|
||||
return {
|
||||
@@ -99,55 +85,32 @@ export class LineageEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Record one run event without throwing after domain work has completed.
|
||||
* Record one run event. Never throws after domain work has completed.
|
||||
*
|
||||
* With an outbox: enqueue only; the dispatcher owns delivery and retry.
|
||||
* Without an outbox: direct AUD0 call for local/tests/backwards compatibility.
|
||||
* If durable enqueue itself fails, a direct AUD0 attempt is still made so an
|
||||
* available AUD0 can preserve evidence; the warning makes the durability
|
||||
* degradation explicit rather than silently green.
|
||||
* A DurableAud0Client returns an acknowledgement whose event_id is the
|
||||
* immutable `outbox:<id>` handle. A raw HTTP client returns AUD0's event id.
|
||||
* This lets callers distinguish durable queueing from live acknowledgement
|
||||
* without duplicating the outbox implementation in this layer.
|
||||
*/
|
||||
async emit(input: LineageEmitInput): Promise<LineageEmitResult> {
|
||||
const event = this.buildRunEvent(input);
|
||||
|
||||
if (this.outbox) {
|
||||
try {
|
||||
const row = await this.outbox.enqueueOpenLineage(input.tenant_id, APP_ID, event);
|
||||
return {
|
||||
emitted: row.status === "delivered",
|
||||
queued: row.status === "pending",
|
||||
run_id: input.run_id,
|
||||
outbox_id: row.outbox_id,
|
||||
};
|
||||
} catch (queueError) {
|
||||
try {
|
||||
await this.aud0.ingestOpenLineageEvent(input.tenant_id, APP_ID, event);
|
||||
return {
|
||||
emitted: true,
|
||||
queued: false,
|
||||
run_id: input.run_id,
|
||||
warning: `durable outbox enqueue failed (${safeWarning(queueError)}); AUD0 accepted direct fallback`,
|
||||
};
|
||||
} catch (aud0Error) {
|
||||
return {
|
||||
emitted: false,
|
||||
queued: false,
|
||||
run_id: input.run_id,
|
||||
warning: `durable outbox enqueue failed (${safeWarning(queueError)}); direct AUD0 delivery failed (${safeWarning(aud0Error)})`,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await this.aud0.ingestOpenLineageEvent(input.tenant_id, APP_ID, event);
|
||||
const ack = await this.aud0.ingestOpenLineageEvent(input.tenant_id, APP_ID, event);
|
||||
if (ack.event_id.startsWith("outbox:")) {
|
||||
return {
|
||||
emitted: false,
|
||||
queued: true,
|
||||
run_id: input.run_id,
|
||||
outbox_id: ack.event_id.slice("outbox:".length),
|
||||
};
|
||||
}
|
||||
return { emitted: true, queued: false, run_id: input.run_id };
|
||||
} catch (error) {
|
||||
return {
|
||||
emitted: false,
|
||||
queued: false,
|
||||
run_id: input.run_id,
|
||||
warning: `openlineage emission failed: ${safeWarning(error)}`,
|
||||
warning: `openlineage recording failed: ${safeWarning(error)}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user