@holonograph/client 0.2.0 → 0.4.0

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 (3) hide show
  1. package/dist/index.d.ts +326 -102
  2. package/dist/index.js +721 -13
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4691,7 +4691,9 @@ var LightSnapshotSchema = external_exports.object({
4691
4691
 
4692
4692
  vendorMetadata: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
4693
4693
 
4694
- rawRequestOmitted: external_exports.boolean().optional()
4694
+ rawRequestOmitted: external_exports.boolean().optional(),
4695
+
4696
+ rawResponsePresence: external_exports.enum(["present", "omitted-by-policy", "pending-attach", "failed-attach"]).optional()
4695
4697
  });
4696
4698
  var DimensionResultSchema = external_exports.object({
4697
4699
  dimensionId: DimensionIdSchema,
@@ -4765,7 +4767,11 @@ var EvaluationEventKindSchema = external_exports.enum([
4765
4767
 
4766
4768
  "analysis.eval-recommendation",
4767
4769
 
4768
- "analysis.conformance-fixture"
4770
+ "analysis.conformance-fixture",
4771
+
4772
+ "analysis.emission-coverage",
4773
+
4774
+ "analysis.delivery-orphan"
4769
4775
  ]);
4770
4776
  var AvailabilityTransitionDirectionSchema = external_exports.enum([
4771
4777
  "reachable->unreachable",
@@ -4941,6 +4947,28 @@ var ConformanceFixtureRecordSchema = external_exports.object({
4941
4947
 
4942
4948
  sourceSurfaceId: external_exports.string().min(1).optional()
4943
4949
  });
4950
+ var EmissionCoverageSchema = external_exports.object({
4951
+
4952
+ streamId: external_exports.string().min(1),
4953
+
4954
+ observedOrdinals: external_exports.array(external_exports.number().int().nonnegative()),
4955
+
4956
+ sentinelHighWater: external_exports.number().int().nonnegative().nullable(),
4957
+
4958
+ finalSeen: external_exports.boolean(),
4959
+
4960
+ observedAtMs: external_exports.number().int().nonnegative()
4961
+ });
4962
+ var DeliveryOrphanSchema = external_exports.object({
4963
+
4964
+ darkEventId: external_exports.string().min(1),
4965
+
4966
+ emissionStreamId: external_exports.string().min(1),
4967
+
4968
+ surfaceId: external_exports.string().min(1),
4969
+
4970
+ witnessedAtMs: external_exports.number().int().nonnegative()
4971
+ });
4944
4972
  var EvaluationEventSchema = external_exports.object({
4945
4973
 
4946
4974
  eventId: external_exports.string().min(1),
@@ -4951,6 +4979,8 @@ var EvaluationEventSchema = external_exports.object({
4951
4979
 
4952
4980
  correlationId: CorrelationIdSchema,
4953
4981
 
4982
+ emissionStreamId: external_exports.string().min(1).optional(),
4983
+
4954
4984
  lensVersion: external_exports.string().min(1),
4955
4985
 
4956
4986
  kind: EvaluationEventKindSchema.default("evaluation"),
@@ -4971,31 +5001,41 @@ var EvaluationEventSchema = external_exports.object({
4971
5001
 
4972
5002
  evalRecommendation: EvalRecommendationSchema.optional(),
4973
5003
 
4974
- conformanceFixtureRecord: ConformanceFixtureRecordSchema.optional()
5004
+ conformanceFixtureRecord: ConformanceFixtureRecordSchema.optional(),
5005
+
5006
+ emissionCoverage: EmissionCoverageSchema.optional(),
5007
+
5008
+ deliveryOrphan: DeliveryOrphanSchema.optional()
4975
5009
  }).refine(
4976
5010
  (e) => {
4977
5011
  if (e.kind === "evaluation") {
4978
- return e.light !== void 0 && e.substrate !== void 0 && e.surface !== void 0 && e.availability === void 0 && e.noiseFloorCalibration === void 0 && e.varianceDecomposition === void 0 && e.lifecycleTransition === void 0 && e.evalRecommendation === void 0 && e.conformanceFixtureRecord === void 0;
5012
+ return e.light !== void 0 && e.substrate !== void 0 && e.surface !== void 0 && e.availability === void 0 && e.noiseFloorCalibration === void 0 && e.varianceDecomposition === void 0 && e.lifecycleTransition === void 0 && e.evalRecommendation === void 0 && e.conformanceFixtureRecord === void 0 && e.emissionCoverage === void 0 && e.deliveryOrphan === void 0;
4979
5013
  }
4980
5014
  if (e.kind === "availability.transition") {
4981
- return e.availability !== void 0 && e.light === void 0 && e.substrate === void 0 && e.surface === void 0 && e.noiseFloorCalibration === void 0 && e.varianceDecomposition === void 0 && e.lifecycleTransition === void 0 && e.evalRecommendation === void 0 && e.conformanceFixtureRecord === void 0;
5015
+ return e.availability !== void 0 && e.light === void 0 && e.substrate === void 0 && e.surface === void 0 && e.noiseFloorCalibration === void 0 && e.varianceDecomposition === void 0 && e.lifecycleTransition === void 0 && e.evalRecommendation === void 0 && e.conformanceFixtureRecord === void 0 && e.emissionCoverage === void 0 && e.deliveryOrphan === void 0;
4982
5016
  }
4983
5017
  if (e.kind === "analysis.noise-floor-calibration") {
4984
- return e.noiseFloorCalibration !== void 0 && e.light === void 0 && e.substrate === void 0 && e.surface === void 0 && e.availability === void 0 && e.varianceDecomposition === void 0 && e.lifecycleTransition === void 0 && e.evalRecommendation === void 0 && e.conformanceFixtureRecord === void 0;
5018
+ return e.noiseFloorCalibration !== void 0 && e.light === void 0 && e.substrate === void 0 && e.surface === void 0 && e.availability === void 0 && e.varianceDecomposition === void 0 && e.lifecycleTransition === void 0 && e.evalRecommendation === void 0 && e.conformanceFixtureRecord === void 0 && e.emissionCoverage === void 0 && e.deliveryOrphan === void 0;
4985
5019
  }
4986
5020
  if (e.kind === "analysis.variance-decomposition") {
4987
- return e.varianceDecomposition !== void 0 && e.light === void 0 && e.substrate === void 0 && e.surface === void 0 && e.availability === void 0 && e.noiseFloorCalibration === void 0 && e.lifecycleTransition === void 0 && e.evalRecommendation === void 0 && e.conformanceFixtureRecord === void 0;
5021
+ return e.varianceDecomposition !== void 0 && e.light === void 0 && e.substrate === void 0 && e.surface === void 0 && e.availability === void 0 && e.noiseFloorCalibration === void 0 && e.lifecycleTransition === void 0 && e.evalRecommendation === void 0 && e.conformanceFixtureRecord === void 0 && e.emissionCoverage === void 0 && e.deliveryOrphan === void 0;
4988
5022
  }
4989
5023
  if (e.kind === "analysis.lifecycle-transition") {
4990
- return e.lifecycleTransition !== void 0 && e.light === void 0 && e.substrate === void 0 && e.surface === void 0 && e.availability === void 0 && e.noiseFloorCalibration === void 0 && e.varianceDecomposition === void 0 && e.evalRecommendation === void 0 && e.conformanceFixtureRecord === void 0;
5024
+ return e.lifecycleTransition !== void 0 && e.light === void 0 && e.substrate === void 0 && e.surface === void 0 && e.availability === void 0 && e.noiseFloorCalibration === void 0 && e.varianceDecomposition === void 0 && e.evalRecommendation === void 0 && e.conformanceFixtureRecord === void 0 && e.emissionCoverage === void 0 && e.deliveryOrphan === void 0;
4991
5025
  }
4992
5026
  if (e.kind === "analysis.eval-recommendation") {
4993
- return e.evalRecommendation !== void 0 && e.light === void 0 && e.substrate === void 0 && e.surface === void 0 && e.availability === void 0 && e.noiseFloorCalibration === void 0 && e.varianceDecomposition === void 0 && e.lifecycleTransition === void 0 && e.conformanceFixtureRecord === void 0;
5027
+ return e.evalRecommendation !== void 0 && e.light === void 0 && e.substrate === void 0 && e.surface === void 0 && e.availability === void 0 && e.noiseFloorCalibration === void 0 && e.varianceDecomposition === void 0 && e.lifecycleTransition === void 0 && e.conformanceFixtureRecord === void 0 && e.emissionCoverage === void 0 && e.deliveryOrphan === void 0;
5028
+ }
5029
+ if (e.kind === "analysis.conformance-fixture") {
5030
+ return e.conformanceFixtureRecord !== void 0 && e.light === void 0 && e.substrate === void 0 && e.surface === void 0 && e.availability === void 0 && e.noiseFloorCalibration === void 0 && e.varianceDecomposition === void 0 && e.lifecycleTransition === void 0 && e.evalRecommendation === void 0 && e.emissionCoverage === void 0 && e.deliveryOrphan === void 0;
5031
+ }
5032
+ if (e.kind === "analysis.emission-coverage") {
5033
+ return e.emissionCoverage !== void 0 && e.light === void 0 && e.substrate === void 0 && e.surface === void 0 && e.availability === void 0 && e.noiseFloorCalibration === void 0 && e.varianceDecomposition === void 0 && e.lifecycleTransition === void 0 && e.evalRecommendation === void 0 && e.conformanceFixtureRecord === void 0 && e.deliveryOrphan === void 0;
4994
5034
  }
4995
- return e.conformanceFixtureRecord !== void 0 && e.light === void 0 && e.substrate === void 0 && e.surface === void 0 && e.availability === void 0 && e.noiseFloorCalibration === void 0 && e.varianceDecomposition === void 0 && e.lifecycleTransition === void 0 && e.evalRecommendation === void 0;
5035
+ return e.deliveryOrphan !== void 0 && e.emissionCoverage === void 0 && e.light === void 0 && e.substrate === void 0 && e.surface === void 0 && e.availability === void 0 && e.noiseFloorCalibration === void 0 && e.varianceDecomposition === void 0 && e.lifecycleTransition === void 0 && e.evalRecommendation === void 0 && e.conformanceFixtureRecord === void 0;
4996
5036
  },
4997
5037
  {
4998
- message: "EvaluationEvent shape invariant: each kind requires exactly its corresponding payload and forbids the others. kind='evaluation' \u2192 light+substrate+surface; kind='availability.transition' \u2192 availability; kind='analysis.noise-floor-calibration' \u2192 noiseFloorCalibration; kind='analysis.variance-decomposition' \u2192 varianceDecomposition; kind='analysis.lifecycle-transition' \u2192 lifecycleTransition; kind='analysis.eval-recommendation' \u2192 evalRecommendation; kind='analysis.conformance-fixture' \u2192 conformanceFixtureRecord."
5038
+ message: "EvaluationEvent shape invariant: each kind requires exactly its corresponding payload and forbids the others. kind='evaluation' \u2192 light+substrate+surface; kind='availability.transition' \u2192 availability; kind='analysis.noise-floor-calibration' \u2192 noiseFloorCalibration; kind='analysis.variance-decomposition' \u2192 varianceDecomposition; kind='analysis.lifecycle-transition' \u2192 lifecycleTransition; kind='analysis.eval-recommendation' \u2192 evalRecommendation; kind='analysis.conformance-fixture' \u2192 conformanceFixtureRecord; kind='analysis.emission-coverage' \u2192 emissionCoverage; kind='analysis.delivery-orphan' \u2192 deliveryOrphan."
4999
5039
  }
5000
5040
  );
5001
5041
  function newEventId(occurredAt = new Date()) {
@@ -5310,6 +5350,602 @@ var InProcessTransport = class {
5310
5350
  }
5311
5351
  };
5312
5352
 
5353
+ var BoundedEmitBuffer = class {
5354
+ capacity;
5355
+ items = [];
5356
+ _offeredTotal = 0;
5357
+ _droppedTotal = 0;
5358
+
5359
+ constructor(capacity) {
5360
+ if (!Number.isInteger(capacity) || capacity < 1) {
5361
+ throw new RangeError(
5362
+ `BoundedEmitBuffer capacity must be a positive integer, got ${String(capacity)}`
5363
+ );
5364
+ }
5365
+ this.capacity = capacity;
5366
+ }
5367
+
5368
+ offer(item) {
5369
+ this._offeredTotal += 1;
5370
+ if (this.items.length >= this.capacity) {
5371
+ this._droppedTotal += 1;
5372
+ return false;
5373
+ }
5374
+ this.items.push(item);
5375
+ return true;
5376
+ }
5377
+
5378
+ drain() {
5379
+ if (this.items.length === 0) return [];
5380
+ const out = this.items;
5381
+ this.items = [];
5382
+ return out;
5383
+ }
5384
+
5385
+ get size() {
5386
+ return this.items.length;
5387
+ }
5388
+
5389
+ get maxSize() {
5390
+ return this.capacity;
5391
+ }
5392
+
5393
+ get offeredTotal() {
5394
+ return this._offeredTotal;
5395
+ }
5396
+
5397
+ get droppedTotal() {
5398
+ return this._droppedTotal;
5399
+ }
5400
+ };
5401
+
5402
+ var CompletenessAccounting = class {
5403
+ constructor(lensVersion, config, hooks = {}) {
5404
+ this.lensVersion = lensVersion;
5405
+ this.config = config;
5406
+ this.hooks = hooks;
5407
+ }
5408
+ lensVersion;
5409
+ config;
5410
+ hooks;
5411
+ windowStartMs = null;
5412
+ offered = 0;
5413
+ dropped = 0;
5414
+ delivered = 0;
5415
+ failed = 0;
5416
+
5417
+ channels = new Map();
5418
+
5419
+ sustainedSinceMs = null;
5420
+
5421
+ alerted = false;
5422
+
5423
+ recordOffer(nowMs, accepted) {
5424
+ this.openWindowIfNeeded(nowMs);
5425
+ this.offered += 1;
5426
+ if (!accepted) this.dropped += 1;
5427
+ }
5428
+
5429
+ recordFlush(nowMs, channel, delivered, failed) {
5430
+ this.openWindowIfNeeded(nowMs);
5431
+ this.delivered += delivered;
5432
+ this.failed += failed;
5433
+ const entry = this.channels.get(channel) ?? { attempted: 0, delivered: 0, failed: 0 };
5434
+ entry.attempted += delivered + failed;
5435
+ entry.delivered += delivered;
5436
+ entry.failed += failed;
5437
+ this.channels.set(channel, entry);
5438
+ }
5439
+
5440
+ maybeRollover(nowMs) {
5441
+ if (this.windowStartMs === null) return;
5442
+ if (nowMs - this.windowStartMs < this.config.windowMs) return;
5443
+ this.finalize(this.windowStartMs + this.config.windowMs);
5444
+ }
5445
+
5446
+ snapshot(nowMs) {
5447
+ if (this.windowStartMs === null) return null;
5448
+ return this.buildWindow(this.windowStartMs, nowMs);
5449
+ }
5450
+
5451
+ flushOpenWindow(nowMs) {
5452
+ if (this.windowStartMs === null) return;
5453
+ this.finalize(Math.max(nowMs, this.windowStartMs));
5454
+ }
5455
+ openWindowIfNeeded(nowMs) {
5456
+ this.windowStartMs ??= nowMs;
5457
+ }
5458
+ finalize(windowEndMs) {
5459
+ const start = this.windowStartMs;
5460
+ if (start === null) return;
5461
+ const window = this.buildWindow(start, windowEndMs);
5462
+ this.windowStartMs = null;
5463
+ this.offered = 0;
5464
+ this.dropped = 0;
5465
+ this.delivered = 0;
5466
+ this.failed = 0;
5467
+ this.channels.clear();
5468
+ this.hooks.onWindow?.(window);
5469
+ this.evaluateSustainedLoad(window);
5470
+ }
5471
+ buildWindow(startMs, endMs) {
5472
+ const completenessLowerBound = this.offered === 0 ? 1 : (this.offered - this.dropped) / this.offered;
5473
+ const channels = {};
5474
+ for (const [channel, entry] of this.channels) {
5475
+ channels[channel] = { ...entry };
5476
+ }
5477
+ return {
5478
+ lensVersion: this.lensVersion,
5479
+ windowStartMs: startMs,
5480
+ windowEndMs: endMs,
5481
+ offered: this.offered,
5482
+ dropped: this.dropped,
5483
+ delivered: this.delivered,
5484
+ failed: this.failed,
5485
+ channels,
5486
+ completenessLowerBound,
5487
+ biasFlag: this.dropped > 0
5488
+ };
5489
+ }
5490
+ evaluateSustainedLoad(window) {
5491
+ const rate = window.offered === 0 ? 0 : window.dropped / window.offered;
5492
+ if (rate >= this.config.dropRateThreshold && window.dropped > 0) {
5493
+ this.sustainedSinceMs ??= window.windowStartMs;
5494
+ const sustainedForMs = window.windowEndMs - this.sustainedSinceMs;
5495
+ if (sustainedForMs >= this.config.sustainedForMs && !this.alerted) {
5496
+ this.alerted = true;
5497
+ this.hooks.onExtremeSustainedLoad?.({
5498
+ lensVersion: this.lensVersion,
5499
+ sinceMs: this.sustainedSinceMs,
5500
+ firedAtMs: window.windowEndMs,
5501
+ sustainedForMs,
5502
+ dropRate: rate,
5503
+ dropRateThreshold: this.config.dropRateThreshold
5504
+ });
5505
+ }
5506
+ } else {
5507
+ this.sustainedSinceMs = null;
5508
+ this.alerted = false;
5509
+ }
5510
+ }
5511
+ };
5512
+
5513
+ var DEFAULT_BUFFER_SIZE = 2048;
5514
+ var DEFAULT_FLUSH_INTERVAL_MS = 1e3;
5515
+ var DEFAULT_WINDOW_MS = 1e4;
5516
+ var DEFAULT_DROP_RATE_THRESHOLD = 0.01;
5517
+ var DEFAULT_SUSTAINED_FOR_MS = 6e4;
5518
+ var DEFAULT_SENTINEL_INTERVAL_MS = 3e4;
5519
+ var streamIdFallbackCounter = 0;
5520
+ function generateStreamId(nowMs) {
5521
+ const cryptoObj = globalThis.crypto;
5522
+ if (typeof cryptoObj?.randomUUID === "function") return cryptoObj.randomUUID();
5523
+ streamIdFallbackCounter += 1;
5524
+ return `stream-${String(nowMs)}-${String(streamIdFallbackCounter)}`;
5525
+ }
5526
+ function defaultScheduleInterval(fn, ms) {
5527
+ const handle = setInterval(fn, ms);
5528
+ handle.unref?.();
5529
+ return () => {
5530
+ clearInterval(handle);
5531
+ };
5532
+ }
5533
+ var EvaluationEventEmitter = class {
5534
+ buffer;
5535
+ flushTargets;
5536
+
5537
+ sentinelTargets;
5538
+ accounting;
5539
+ now;
5540
+ onFlushError;
5541
+ stopInterval;
5542
+ stopSentinelInterval;
5543
+
5544
+ streamId;
5545
+ nextSeq = 0;
5546
+
5547
+ lastSeq = -1;
5548
+
5549
+ emitsSentinels;
5550
+
5551
+ inFlight = null;
5552
+ stopped = false;
5553
+ constructor(opts) {
5554
+ this.buffer = new BoundedEmitBuffer(opts.bufferSize ?? DEFAULT_BUFFER_SIZE);
5555
+ if (opts.flushTargets.length === 0) {
5556
+ throw new Error("EvaluationEventEmitter requires at least one flush target");
5557
+ }
5558
+ this.flushTargets = [...opts.flushTargets];
5559
+ this.sentinelTargets = this.flushTargets.filter((t) => typeof t.emitSentinel === "function");
5560
+ this.now = opts.now ?? Date.now;
5561
+ this.streamId = opts.streamId ?? generateStreamId(this.now());
5562
+ this.emitsSentinels = opts.deployment !== "serverless";
5563
+ this.onFlushError = opts.onFlushError ?? ((error) => {
5564
+ console.warn("[holonograph] emit flush error:", error);
5565
+ });
5566
+ const config = {
5567
+ windowMs: opts.windowMs ?? DEFAULT_WINDOW_MS,
5568
+ dropRateThreshold: opts.extremeSustainedLoad?.dropRateThreshold ?? DEFAULT_DROP_RATE_THRESHOLD,
5569
+ sustainedForMs: opts.extremeSustainedLoad?.sustainedForMs ?? DEFAULT_SUSTAINED_FOR_MS
5570
+ };
5571
+ this.accounting = new CompletenessAccounting(opts.lensVersion, config, {
5572
+ ...opts.onWindow !== void 0 ? { onWindow: opts.onWindow } : {},
5573
+ ...opts.onExtremeSustainedLoad !== void 0 ? { onExtremeSustainedLoad: opts.onExtremeSustainedLoad } : {}
5574
+ });
5575
+ const schedule = opts.scheduleInterval ?? defaultScheduleInterval;
5576
+ this.stopInterval = schedule(() => {
5577
+ this.tick();
5578
+ }, opts.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS);
5579
+ this.stopSentinelInterval = this.emitsSentinels ? schedule(() => {
5580
+ this.sentinelTick();
5581
+ }, opts.sentinelIntervalMs ?? DEFAULT_SENTINEL_INTERVAL_MS) : () => {
5582
+ };
5583
+ }
5584
+
5585
+ enqueue(event) {
5586
+ const nowMs = this.now();
5587
+ const seq = this.nextSeq;
5588
+ this.nextSeq += 1;
5589
+ this.lastSeq = seq;
5590
+ const stamped = event.emissionStreamId === void 0 ? { ...event, emissionStreamId: this.streamId } : event;
5591
+ const accepted = this.buffer.offer({ event: stamped, seq, streamId: this.streamId });
5592
+ this.accounting.recordOffer(nowMs, accepted);
5593
+ this.accounting.maybeRollover(nowMs);
5594
+ return event.eventId;
5595
+ }
5596
+
5597
+ tick() {
5598
+ void this.flushNow();
5599
+ this.accounting.maybeRollover(this.now());
5600
+ }
5601
+
5602
+ sentinelTick() {
5603
+ if (this.lastSeq < 0) return;
5604
+ void this.emitSentinelNow(false);
5605
+ }
5606
+
5607
+ async emitSentinelNow(final) {
5608
+ if (!this.emitsSentinels) return;
5609
+ if (this.sentinelTargets.length === 0) return;
5610
+ const sentinel = { streamId: this.streamId, seq: this.lastSeq, final };
5611
+ await Promise.all(
5612
+ this.sentinelTargets.map(
5613
+ (t) => (t.emitSentinel?.(sentinel) ?? Promise.resolve()).catch((error) => {
5614
+ this.onFlushError(error);
5615
+ })
5616
+ )
5617
+ );
5618
+ }
5619
+
5620
+ flushNow() {
5621
+ if (this.inFlight) return this.inFlight;
5622
+ const batch = this.buffer.drain();
5623
+ if (batch.length === 0) return Promise.resolve();
5624
+ const flush = this.doFlush(batch).finally(() => {
5625
+ this.inFlight = null;
5626
+ });
5627
+ this.inFlight = flush;
5628
+ return flush;
5629
+ }
5630
+ async doFlush(batch) {
5631
+ await Promise.all(this.flushTargets.map((target) => this.flushToTarget(target, batch)));
5632
+ }
5633
+ async flushToTarget(target, batch) {
5634
+ const channel = target.channel;
5635
+ try {
5636
+ const result = await target.flushBatch(batch);
5637
+ this.accounting.recordFlush(this.now(), channel, result.delivered, result.failed);
5638
+ for (const error of result.errors) this.onFlushError(error);
5639
+ } catch (error) {
5640
+ this.accounting.recordFlush(this.now(), channel, 0, batch.length);
5641
+ this.onFlushError(error);
5642
+ }
5643
+ }
5644
+
5645
+ snapshotWindow() {
5646
+ return this.accounting.snapshot(this.now());
5647
+ }
5648
+
5649
+ async shutdown() {
5650
+ if (this.stopped) return;
5651
+ this.stopped = true;
5652
+ this.stopInterval();
5653
+ this.stopSentinelInterval();
5654
+ while (this.buffer.size > 0 || this.inFlight) {
5655
+ await this.flushNow();
5656
+ }
5657
+ if (this.lastSeq >= 0) await this.emitSentinelNow(true);
5658
+ await Promise.all(this.flushTargets.map((t) => t.shutdown?.() ?? Promise.resolve()));
5659
+ this.accounting.flushOpenWindow(this.now());
5660
+ }
5661
+ };
5662
+
5663
+ var HttpEventFlushTarget = class {
5664
+
5665
+ constructor(postEvent) {
5666
+ this.postEvent = postEvent;
5667
+ }
5668
+ postEvent;
5669
+
5670
+ channel = "http";
5671
+ async flushBatch(items) {
5672
+ let delivered = 0;
5673
+ let failed = 0;
5674
+ const errors = [];
5675
+ for (const item of items) {
5676
+ try {
5677
+ await this.postEvent(item.event);
5678
+ delivered += 1;
5679
+ } catch (error) {
5680
+ failed += 1;
5681
+ errors.push(error);
5682
+ }
5683
+ }
5684
+ return { delivered, failed, errors };
5685
+ }
5686
+ };
5687
+
5688
+ var EVALUATION_SPAN_NAME = "holonograph.evaluation";
5689
+ var DIMENSION_EVENT_NAME = "holonograph.dimension";
5690
+ var SENTINEL_SPAN_NAME = "holonograph.session.sentinel";
5691
+ var SPAN_ATTR = {
5692
+ eventId: "holonograph.event.id",
5693
+ correlationId: "holonograph.correlation_id",
5694
+ surfaceId: "holonograph.surface.id",
5695
+ fixtureId: "holonograph.surface.fixture_id",
5696
+ passed: "holonograph.surface.passed",
5697
+ cohortTags: "holonograph.surface.cohort_tags",
5698
+ lensVersion: "holonograph.lens.version",
5699
+ lightSourceId: "holonograph.light_source.id",
5700
+ runMode: "holonograph.run_mode",
5701
+ provenance: "holonograph.provenance",
5702
+ latencyMs: "holonograph.light.latency_ms",
5703
+ tokensInput: "holonograph.light.tokens.input",
5704
+ tokensOutput: "holonograph.light.tokens.output",
5705
+ ok: "holonograph.light.ok",
5706
+
5707
+ rawResponseLocation: "holonograph.raw_response.location",
5708
+
5709
+ substratePrefix: "holonograph.substrate.",
5710
+ dimensionId: "holonograph.dimension.id",
5711
+ dimensionPassed: "holonograph.dimension.passed",
5712
+ dimensionScore: "holonograph.dimension.score",
5713
+
5714
+ emissionSeq: "holonograph.emission.seq",
5715
+ emissionStream: "holonograph.emission.stream",
5716
+
5717
+ emissionFinal: "holonograph.emission.final"
5718
+ };
5719
+ function buildEvaluationSpanData(event, emission, options) {
5720
+ const light = event.light;
5721
+ const surface = event.surface;
5722
+ const substrate = event.substrate;
5723
+ const startTimeMs = Date.parse(event.occurredAt);
5724
+ const latencyMs = typeof light?.latencyMs === "number" ? light.latencyMs : 0;
5725
+ const endTimeMs = startTimeMs + latencyMs;
5726
+ const isError = light?.error !== void 0;
5727
+ const attributes = {
5728
+ [SPAN_ATTR.eventId]: event.eventId,
5729
+ [SPAN_ATTR.correlationId]: event.correlationId,
5730
+ [SPAN_ATTR.surfaceId]: event.surfaceId,
5731
+ [SPAN_ATTR.lensVersion]: event.lensVersion,
5732
+ [SPAN_ATTR.ok]: !isError,
5733
+
5734
+ [SPAN_ATTR.rawResponseLocation]: options?.rawResponseLocation ?? "store"
5735
+ };
5736
+ if (surface !== void 0) {
5737
+ attributes[SPAN_ATTR.fixtureId] = surface.fixtureId;
5738
+ attributes[SPAN_ATTR.passed] = surface.passed;
5739
+ attributes[SPAN_ATTR.cohortTags] = [...surface.cohortTags];
5740
+ }
5741
+ if (substrate !== void 0) {
5742
+ attributes[SPAN_ATTR.lightSourceId] = substrate.lightSourceIdentifier;
5743
+ attributes[SPAN_ATTR.runMode] = substrate.runMode;
5744
+ attributes[SPAN_ATTR.provenance] = substrate.provenance;
5745
+ for (const [key, value] of Object.entries(substrate.columns)) {
5746
+ attributes[`${SPAN_ATTR.substratePrefix}${key}`] = value;
5747
+ }
5748
+ }
5749
+ if (typeof light?.latencyMs === "number") attributes[SPAN_ATTR.latencyMs] = light.latencyMs;
5750
+ if (light?.tokens?.input !== void 0) attributes[SPAN_ATTR.tokensInput] = light.tokens.input;
5751
+ if (light?.tokens?.output !== void 0) attributes[SPAN_ATTR.tokensOutput] = light.tokens.output;
5752
+ if (emission !== void 0) {
5753
+ attributes[SPAN_ATTR.emissionSeq] = emission.seq;
5754
+ attributes[SPAN_ATTR.emissionStream] = emission.streamId;
5755
+ }
5756
+ const dimensionEvents = (surface?.dimensions ?? []).map((d) => {
5757
+ const attrs = {
5758
+ [SPAN_ATTR.dimensionId]: d.dimensionId,
5759
+ [SPAN_ATTR.dimensionPassed]: d.passed
5760
+ };
5761
+ if (d.score !== void 0) attrs[SPAN_ATTR.dimensionScore] = d.score;
5762
+ return { name: DIMENSION_EVENT_NAME, attributes: attrs };
5763
+ });
5764
+ return {
5765
+ name: EVALUATION_SPAN_NAME,
5766
+ startTimeMs,
5767
+ endTimeMs,
5768
+ attributes,
5769
+ dimensionEvents,
5770
+ isError,
5771
+ errorMessage: light?.error?.message
5772
+ };
5773
+ }
5774
+ function writeEvaluationSpan(tracer, event, otel, emission, options) {
5775
+ const data = buildEvaluationSpanData(event, emission, options);
5776
+ const span = tracer.startSpan(data.name, {
5777
+ startTime: data.startTimeMs,
5778
+ kind: otel.SpanKindClient,
5779
+ attributes: data.attributes
5780
+ });
5781
+ for (const ev of data.dimensionEvents) span.addEvent(ev.name, ev.attributes);
5782
+ if (data.isError) {
5783
+ span.setStatus(
5784
+ data.errorMessage !== void 0 ? { code: otel.statusError, message: data.errorMessage } : { code: otel.statusError }
5785
+ );
5786
+ } else {
5787
+ span.setStatus({ code: otel.statusOk });
5788
+ }
5789
+ span.end(data.endTimeMs);
5790
+ }
5791
+ function writeSentinelSpan(tracer, sentinel, otel) {
5792
+ const span = tracer.startSpan(SENTINEL_SPAN_NAME, {
5793
+ kind: otel.SpanKindClient,
5794
+ attributes: {
5795
+ [SPAN_ATTR.emissionStream]: sentinel.streamId,
5796
+ [SPAN_ATTR.emissionSeq]: sentinel.seq,
5797
+ [SPAN_ATTR.emissionFinal]: sentinel.final
5798
+ }
5799
+ });
5800
+ span.setStatus({ code: otel.statusOk });
5801
+ span.end();
5802
+ }
5803
+
5804
+ function reconcileBatchDelivery(batchSize, delivered, exportFailed) {
5805
+ const shed = Math.max(0, batchSize - delivered - exportFailed);
5806
+ return { delivered, failed: exportFailed + shed };
5807
+ }
5808
+ var CountingExporter = class {
5809
+ constructor(inner, tally, successCode) {
5810
+ this.inner = inner;
5811
+ this.tally = tally;
5812
+ this.successCode = successCode;
5813
+ }
5814
+ inner;
5815
+ tally;
5816
+ successCode;
5817
+ export(spans, resultCallback) {
5818
+ this.inner.export(spans, (result) => {
5819
+ if (result.code === this.successCode) {
5820
+ this.tally.delivered += spans.length;
5821
+ } else {
5822
+ this.tally.failed += spans.length;
5823
+ if (result.error) this.tally.errors.push(result.error);
5824
+ }
5825
+ resultCallback(result);
5826
+ });
5827
+ }
5828
+ shutdown() {
5829
+ return this.inner.shutdown();
5830
+ }
5831
+ forceFlush() {
5832
+ return this.inner.forceFlush?.() ?? Promise.resolve();
5833
+ }
5834
+ };
5835
+ var OtlpEventFlushTarget = class {
5836
+ channel = "otlp";
5837
+ options;
5838
+ tally = { delivered: 0, failed: 0, errors: [] };
5839
+ provider;
5840
+ tracer;
5841
+ otel;
5842
+ initPromise;
5843
+
5844
+ opLock = Promise.resolve();
5845
+ constructor(options) {
5846
+ this.options = options;
5847
+ }
5848
+
5849
+ serialize(fn) {
5850
+ const run = this.opLock.then(fn, fn);
5851
+ this.opLock = run.then(
5852
+ () => void 0,
5853
+ () => void 0
5854
+ );
5855
+ return run;
5856
+ }
5857
+ flushBatch(items) {
5858
+ return this.serialize(() => this.doFlushBatch(items));
5859
+ }
5860
+ async doFlushBatch(items) {
5861
+ await this.ensureInit();
5862
+ const tracer = this.tracer;
5863
+ const otel = this.otel;
5864
+ const provider = this.provider;
5865
+ if (!tracer || !otel || !provider) {
5866
+ return {
5867
+ delivered: 0,
5868
+ failed: items.length,
5869
+ errors: [new Error("OTLP target not initialized")]
5870
+ };
5871
+ }
5872
+ const beforeDelivered = this.tally.delivered;
5873
+ const beforeFailed = this.tally.failed;
5874
+ const beforeErrors = this.tally.errors.length;
5875
+ const spanOptions = {
5876
+ rawResponseLocation: this.options.rawResponseLocation ?? "store"
5877
+ };
5878
+ for (const item of items) {
5879
+ writeEvaluationSpan(
5880
+ tracer,
5881
+ item.event,
5882
+ otel,
5883
+ { seq: item.seq, streamId: item.streamId },
5884
+ spanOptions
5885
+ );
5886
+ }
5887
+ await provider.forceFlush();
5888
+ const delivered = this.tally.delivered - beforeDelivered;
5889
+ const exportFailed = this.tally.failed - beforeFailed;
5890
+ const reconciled = reconcileBatchDelivery(items.length, delivered, exportFailed);
5891
+ return { ...reconciled, errors: this.tally.errors.slice(beforeErrors) };
5892
+ }
5893
+
5894
+ emitSentinel(sentinel) {
5895
+ return this.serialize(async () => {
5896
+ await this.ensureInit();
5897
+ if (!this.tracer || !this.otel || !this.provider) return;
5898
+ writeSentinelSpan(this.tracer, sentinel, this.otel);
5899
+ await this.provider.forceFlush();
5900
+ });
5901
+ }
5902
+ async shutdown() {
5903
+ if (this.provider) await this.provider.shutdown();
5904
+ }
5905
+
5906
+ ensureInit() {
5907
+ this.initPromise ??= this.doInit();
5908
+ return this.initPromise;
5909
+ }
5910
+ async doInit() {
5911
+ const [api, sdk, resources, core] = await Promise.all([
5912
+ import('@opentelemetry/api'),
5913
+ import('@opentelemetry/sdk-trace-base'),
5914
+ import('@opentelemetry/resources'),
5915
+ import('@opentelemetry/core')
5916
+ ]);
5917
+ let exporter;
5918
+ if (this.options.exporter) {
5919
+ exporter = this.options.exporter;
5920
+ } else {
5921
+ const otlp = await import('@opentelemetry/exporter-trace-otlp-http');
5922
+ const exporterConfig = {};
5923
+ if (this.options.endpoint !== void 0) exporterConfig.url = this.options.endpoint;
5924
+ if (this.options.headers !== void 0) exporterConfig.headers = this.options.headers;
5925
+ exporter = new otlp.OTLPTraceExporter(exporterConfig);
5926
+ }
5927
+ const counting = new CountingExporter(exporter, this.tally, core.ExportResultCode.SUCCESS);
5928
+ const resource = resources.resourceFromAttributes({
5929
+ "service.name": "holonograph-client",
5930
+ "holonograph.lens.version": this.options.lensVersion
5931
+ });
5932
+ this.provider = new sdk.BasicTracerProvider({
5933
+ resource,
5934
+ spanProcessors: [
5935
+ new sdk.BatchSpanProcessor(counting, {
5936
+ maxQueueSize: this.options.maxQueueSize ?? 4096
5937
+ })
5938
+ ]
5939
+ });
5940
+ this.tracer = this.provider.getTracer("holonograph-client");
5941
+ this.otel = {
5942
+ SpanKindClient: api.SpanKind.CLIENT,
5943
+ statusOk: api.SpanStatusCode.OK,
5944
+ statusError: api.SpanStatusCode.ERROR
5945
+ };
5946
+ }
5947
+ };
5948
+
5313
5949
  var HolonographHttpError = class extends Error {
5314
5950
  constructor(status, code, message, details) {
5315
5951
  super(message);
@@ -5327,11 +5963,53 @@ var HttpTransport = class {
5327
5963
  token;
5328
5964
  fetchImpl;
5329
5965
  runMode;
5966
+
5967
+ emitter;
5330
5968
  constructor(opts) {
5331
5969
  this.endpoint = opts.endpoint.replace(/\/$/, "");
5332
5970
  this.token = opts.token;
5333
5971
  this.fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);
5334
5972
  this.runMode = opts.runMode;
5973
+ const e = opts.emit;
5974
+ const mode = e?.mode;
5975
+ if (e !== void 0 && (mode === "buffered" || mode === "otlp" || mode === "dual")) {
5976
+ const lensVersion = opts.lensVersion ?? "unknown";
5977
+ const flushTargets = [];
5978
+ if (mode === "buffered" || mode === "dual") {
5979
+ flushTargets.push(new HttpEventFlushTarget((event) => this.postEvent(event)));
5980
+ }
5981
+ if (mode === "otlp" || mode === "dual") {
5982
+ const otlpOpts = { lensVersion };
5983
+ if (e.otlp?.endpoint !== void 0) otlpOpts.endpoint = e.otlp.endpoint;
5984
+ if (e.otlp?.headers !== void 0) otlpOpts.headers = e.otlp.headers;
5985
+ otlpOpts.maxQueueSize = e.otlp?.maxQueueSize ?? (e.bufferSize ?? 2048) * 2;
5986
+ otlpOpts.rawResponseLocation = mode === "dual" ? "http-channel" : "store";
5987
+ flushTargets.push(new OtlpEventFlushTarget(otlpOpts));
5988
+ }
5989
+ if (e.deployment === "serverless" && e.streamId === void 0) {
5990
+ throw new Error(
5991
+ "emit.deployment 'serverless' requires a stable emit.streamId (e.g. the function name) \u2014 a per-request id would make streamCount track calls, not emitters. Completeness for a serverless emitter comes from reconciliation over that stable stream."
5992
+ );
5993
+ }
5994
+ const emitterOpts = { flushTargets, lensVersion };
5995
+ if (e.deployment !== void 0) emitterOpts.deployment = e.deployment;
5996
+ if (e.streamId !== void 0) emitterOpts.streamId = e.streamId;
5997
+ if (e.bufferSize !== void 0) emitterOpts.bufferSize = e.bufferSize;
5998
+ if (e.flushIntervalMs !== void 0) emitterOpts.flushIntervalMs = e.flushIntervalMs;
5999
+ if (e.sentinelIntervalMs !== void 0) emitterOpts.sentinelIntervalMs = e.sentinelIntervalMs;
6000
+ if (e.windowMs !== void 0) emitterOpts.windowMs = e.windowMs;
6001
+ if (e.extremeSustainedLoad !== void 0) {
6002
+ emitterOpts.extremeSustainedLoad = e.extremeSustainedLoad;
6003
+ }
6004
+ if (e.onWindow !== void 0) emitterOpts.onWindow = e.onWindow;
6005
+ if (e.onExtremeSustainedLoad !== void 0) {
6006
+ emitterOpts.onExtremeSustainedLoad = e.onExtremeSustainedLoad;
6007
+ }
6008
+ if (e.onFlushError !== void 0) emitterOpts.onFlushError = e.onFlushError;
6009
+ this.emitter = new EvaluationEventEmitter(emitterOpts);
6010
+ } else {
6011
+ this.emitter = void 0;
6012
+ }
5335
6013
  }
5336
6014
  async dispatch(req) {
5337
6015
  const wire = await this.post(
@@ -5366,9 +6044,24 @@ var HttpTransport = class {
5366
6044
  return result;
5367
6045
  }
5368
6046
  async appendEvent(event) {
6047
+ if (this.emitter) {
6048
+ return this.emitter.enqueue(event);
6049
+ }
5369
6050
  const result = await this.post("/lens/events", event, event.correlationId);
5370
6051
  return result.eventId;
5371
6052
  }
6053
+
6054
+ async postEvent(event) {
6055
+ await this.post("/lens/events", event, event.correlationId);
6056
+ }
6057
+
6058
+ completenessWindow() {
6059
+ return this.emitter?.snapshotWindow() ?? null;
6060
+ }
6061
+
6062
+ async shutdown() {
6063
+ if (this.emitter) await this.emitter.shutdown();
6064
+ }
5372
6065
  async postContract(contract) {
5373
6066
  return this.post("/holonograph/contract", contract);
5374
6067
  }
@@ -5494,6 +6187,7 @@ function flattenSystemBlocks(blocks) {
5494
6187
  var HolonographClient = class {
5495
6188
  transport;
5496
6189
  inProcessTransport;
6190
+ httpTransport;
5497
6191
  lensVersion;
5498
6192
  substrate;
5499
6193
 
@@ -5513,12 +6207,17 @@ var HolonographClient = class {
5513
6207
  constructor(options) {
5514
6208
  if (isHttpOptions(options)) {
5515
6209
  const httpOpts = {
5516
- endpoint: options.endpoint
6210
+ endpoint: options.endpoint,
6211
+
6212
+ lensVersion: options.lensVersion
5517
6213
  };
5518
6214
  if (options.token !== void 0) httpOpts.token = options.token;
5519
6215
  if (options.fetch !== void 0) httpOpts.fetch = options.fetch;
5520
6216
  if (options.runMode !== void 0) httpOpts.runMode = options.runMode;
5521
- this.transport = new HttpTransport(httpOpts);
6217
+ if (options.emit !== void 0) httpOpts.emit = options.emit;
6218
+ const t = new HttpTransport(httpOpts);
6219
+ this.transport = t;
6220
+ this.httpTransport = t;
5522
6221
  this.inProcessTransport = void 0;
5523
6222
  } else {
5524
6223
  const inProcOpts = {
@@ -5529,6 +6228,7 @@ var HolonographClient = class {
5529
6228
  const t = new InProcessTransport(inProcOpts);
5530
6229
  this.transport = t;
5531
6230
  this.inProcessTransport = t;
6231
+ this.httpTransport = void 0;
5532
6232
  }
5533
6233
  this.lensVersion = options.lensVersion;
5534
6234
  this.substrate = options.substrate;
@@ -5588,6 +6288,14 @@ var HolonographClient = class {
5588
6288
  return this.inProcessTransport ? this.inProcessTransport.topology : null;
5589
6289
  }
5590
6290
 
6291
+ completenessWindow() {
6292
+ return this.httpTransport?.completenessWindow() ?? null;
6293
+ }
6294
+
6295
+ async shutdown() {
6296
+ if (this.httpTransport) await this.httpTransport.shutdown();
6297
+ }
6298
+
5591
6299
  fetchDeclaredColumns(force = false) {
5592
6300
  const getContract = this.transport.getContract?.bind(this.transport);
5593
6301
  if (getContract === void 0) return Promise.resolve(null);