@senpi/runtime 3.0.103 → 3.0.107
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/dist/actions/action-events.d.ts +7 -21
- package/dist/actions/action-events.d.ts.map +1 -1
- package/dist/actions/action-events.js +25 -56
- package/dist/actions/action-events.js.map +1 -1
- package/dist/actions/action-runtime-module.d.ts.map +1 -1
- package/dist/actions/action-runtime-module.js +1 -18
- package/dist/actions/action-runtime-module.js.map +1 -1
- package/dist/actions/close-position/close-position.action.d.ts.map +1 -1
- package/dist/actions/close-position/close-position.action.js +17 -89
- package/dist/actions/close-position/close-position.action.js.map +1 -1
- package/dist/actions/event-derivation.d.ts +2 -40
- package/dist/actions/event-derivation.d.ts.map +1 -1
- package/dist/actions/event-derivation.js +9 -189
- package/dist/actions/event-derivation.js.map +1 -1
- package/dist/actions/open-position/open-position.action.d.ts +33 -0
- package/dist/actions/open-position/open-position.action.d.ts.map +1 -1
- package/dist/actions/open-position/open-position.action.js +223 -170
- package/dist/actions/open-position/open-position.action.js.map +1 -1
- package/dist/actions/position-tracker/position-tracker.action.d.ts +1 -2
- package/dist/actions/position-tracker/position-tracker.action.d.ts.map +1 -1
- package/dist/actions/position-tracker/position-tracker.action.js +15 -50
- package/dist/actions/position-tracker/position-tracker.action.js.map +1 -1
- package/dist/dsl/engine-client/event-mapping.d.ts.map +1 -1
- package/dist/dsl/engine-client/event-mapping.js +0 -15
- package/dist/dsl/engine-client/event-mapping.js.map +1 -1
- package/dist/dsl/events/build-closed-event.d.ts +1 -16
- package/dist/dsl/events/build-closed-event.d.ts.map +1 -1
- package/dist/dsl/events/build-closed-event.js +0 -5
- package/dist/dsl/events/build-closed-event.js.map +1 -1
- package/dist/dsl/events/handlers.d.ts +0 -7
- package/dist/dsl/events/handlers.d.ts.map +1 -1
- package/dist/dsl/events/handlers.js +7 -49
- package/dist/dsl/events/handlers.js.map +1 -1
- package/dist/dsl/monitor/create-monitor.d.ts.map +1 -1
- package/dist/dsl/monitor/create-monitor.js +0 -7
- package/dist/dsl/monitor/create-monitor.js.map +1 -1
- package/dist/dsl/monitor/dsl-failure-events.d.ts +0 -2
- package/dist/dsl/monitor/dsl-failure-events.d.ts.map +1 -1
- package/dist/dsl/monitor/dsl-failure-events.js +1 -2
- package/dist/dsl/monitor/dsl-failure-events.js.map +1 -1
- package/dist/dsl/monitor/process-one-position.d.ts +0 -6
- package/dist/dsl/monitor/process-one-position.d.ts.map +1 -1
- package/dist/dsl/monitor/process-one-position.js +21 -55
- package/dist/dsl/monitor/process-one-position.js.map +1 -1
- package/dist/health/types.d.ts +0 -6
- package/dist/health/types.d.ts.map +1 -1
- package/dist/health/types.js.map +1 -1
- package/dist/index.d.ts +4 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +47 -245
- package/dist/index.js.map +1 -1
- package/dist/runtime/__tests__/helpers/runtime-gateway-harness.d.ts +6 -0
- package/dist/runtime/__tests__/helpers/runtime-gateway-harness.d.ts.map +1 -1
- package/dist/runtime/__tests__/helpers/runtime-gateway-harness.js +25 -0
- package/dist/runtime/__tests__/helpers/runtime-gateway-harness.js.map +1 -1
- package/dist/runtime/create-runtime-context.d.ts.map +1 -1
- package/dist/runtime/create-runtime-context.js +5 -0
- package/dist/runtime/create-runtime-context.js.map +1 -1
- package/dist/runtime/domain-events.d.ts.map +1 -1
- package/dist/runtime/domain-events.js +0 -7
- package/dist/runtime/domain-events.js.map +1 -1
- package/dist/runtime/lifecycle-events.d.ts +1 -9
- package/dist/runtime/lifecycle-events.d.ts.map +1 -1
- package/dist/runtime/lifecycle-events.js +2 -11
- package/dist/runtime/lifecycle-events.js.map +1 -1
- package/dist/runtime/pending-opens-ledger.d.ts +93 -0
- package/dist/runtime/pending-opens-ledger.d.ts.map +1 -0
- package/dist/runtime/pending-opens-ledger.js +118 -0
- package/dist/runtime/pending-opens-ledger.js.map +1 -0
- package/dist/runtime/reconcile-run.d.ts +0 -2
- package/dist/runtime/reconcile-run.d.ts.map +1 -1
- package/dist/runtime/reconcile-run.js +1 -2
- package/dist/runtime/reconcile-run.js.map +1 -1
- package/dist/runtime/reconcile-state.d.ts +0 -8
- package/dist/runtime/reconcile-state.d.ts.map +1 -1
- package/dist/runtime/reconcile-state.js +1 -3
- package/dist/runtime/reconcile-state.js.map +1 -1
- package/dist/runtime/runtime.d.ts +5 -5
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +20 -44
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/strategy-registry.d.ts +1 -0
- package/dist/runtime/strategy-registry.d.ts.map +1 -1
- package/dist/runtime/strategy-registry.js +19 -0
- package/dist/runtime/strategy-registry.js.map +1 -1
- package/dist/runtime/supervisor/long-running-supervisor.d.ts +0 -8
- package/dist/runtime/supervisor/long-running-supervisor.d.ts.map +1 -1
- package/dist/runtime/supervisor/long-running-supervisor.js +0 -1
- package/dist/runtime/supervisor/long-running-supervisor.js.map +1 -1
- package/dist/runtime/update-apply-deps.d.ts +6 -0
- package/dist/runtime/update-apply-deps.d.ts.map +1 -1
- package/dist/runtime/update-apply-deps.js +8 -19
- package/dist/runtime/update-apply-deps.js.map +1 -1
- package/dist/runtime-api/intake/intake-events.d.ts +3 -3
- package/dist/runtime-api/intake/intake-events.d.ts.map +1 -1
- package/dist/runtime-api/intake/intake-events.js +3 -21
- package/dist/runtime-api/intake/intake-events.js.map +1 -1
- package/dist/runtime-api/intake/intake-mount.d.ts +2 -0
- package/dist/runtime-api/intake/intake-mount.d.ts.map +1 -1
- package/dist/runtime-api/intake/intake-mount.js +10 -57
- package/dist/runtime-api/intake/intake-mount.js.map +1 -1
- package/dist/runtime-api/intake/process-signal-post.d.ts +0 -11
- package/dist/runtime-api/intake/process-signal-post.d.ts.map +1 -1
- package/dist/runtime-api/intake/process-signal-post.js +8 -39
- package/dist/runtime-api/intake/process-signal-post.js.map +1 -1
- package/dist/runtime-api/intake/scaffold-error-router.d.ts +43 -0
- package/dist/runtime-api/intake/scaffold-error-router.d.ts.map +1 -0
- package/dist/runtime-api/intake/scaffold-error-router.js +63 -0
- package/dist/runtime-api/intake/scaffold-error-router.js.map +1 -0
- package/dist/runtime-api/intake/scanner-launcher.d.ts.map +1 -1
- package/dist/runtime-api/intake/scanner-launcher.js +0 -12
- package/dist/runtime-api/intake/scanner-launcher.js.map +1 -1
- package/dist/runtime-api/intake/types.d.ts +1 -22
- package/dist/runtime-api/intake/types.d.ts.map +1 -1
- package/dist/runtime-api/routes/signals.d.ts.map +1 -1
- package/dist/runtime-api/routes/signals.js +1 -7
- package/dist/runtime-api/routes/signals.js.map +1 -1
- package/dist/runtime-api/routes/signals.schema.d.ts +0 -1
- package/dist/runtime-api/routes/signals.schema.d.ts.map +1 -1
- package/dist/runtime-api/routes/signals.schema.js +0 -6
- package/dist/runtime-api/routes/signals.schema.js.map +1 -1
- package/dist/scanners/engine/engine.d.ts +0 -5
- package/dist/scanners/engine/engine.d.ts.map +1 -1
- package/dist/scanners/engine/engine.js +2 -30
- package/dist/scanners/engine/engine.js.map +1 -1
- package/dist/scanners/external/scaffold/delivery.py +12 -79
- package/dist/scanners/external/scaffold/scaffold.py +9 -63
- package/dist/scanners/external-scanner-receiver.d.ts +0 -11
- package/dist/scanners/external-scanner-receiver.d.ts.map +1 -1
- package/dist/scanners/external-scanner-receiver.js.map +1 -1
- package/dist/scanners/runtime-module.d.ts.map +1 -1
- package/dist/scanners/runtime-module.js +0 -5
- package/dist/scanners/runtime-module.js.map +1 -1
- package/dist/senpi/client.d.ts.map +1 -1
- package/dist/senpi/client.js +4 -12
- package/dist/senpi/client.js.map +1 -1
- package/dist/senpi/types.d.ts +0 -5
- package/dist/senpi/types.d.ts.map +1 -1
- package/dist/skills-manager/manifest.d.ts.map +1 -1
- package/dist/skills-manager/manifest.js +3 -12
- package/dist/skills-manager/manifest.js.map +1 -1
- package/dist/skills-manager/skills-manifest.json +3 -3
- package/dist/strategy/strategy-state.d.ts +26 -0
- package/dist/strategy/strategy-state.d.ts.map +1 -1
- package/dist/strategy/strategy-state.js +48 -20
- package/dist/strategy/strategy-state.js.map +1 -1
- package/dist/telemetry/event-builders.d.ts +6 -74
- package/dist/telemetry/event-builders.d.ts.map +1 -1
- package/dist/telemetry/event-builders.js +11 -108
- package/dist/telemetry/event-builders.js.map +1 -1
- package/dist/telemetry/event-store.d.ts +0 -6
- package/dist/telemetry/event-store.d.ts.map +1 -1
- package/dist/telemetry/event-store.js +6 -6
- package/dist/telemetry/event-store.js.map +1 -1
- package/dist/telemetry/metrics.d.ts +2 -14
- package/dist/telemetry/metrics.d.ts.map +1 -1
- package/dist/telemetry/metrics.js +4 -19
- package/dist/telemetry/metrics.js.map +1 -1
- package/dist/telemetry/tracing.d.ts +0 -9
- package/dist/telemetry/tracing.d.ts.map +1 -1
- package/dist/telemetry/tracing.js +0 -36
- package/dist/telemetry/tracing.js.map +1 -1
- package/dist/types/dsl/index.d.ts +0 -11
- package/dist/types/dsl/index.d.ts.map +1 -1
- package/dist/types/runtime.d.ts +9 -0
- package/dist/types/runtime.d.ts.map +1 -1
- package/dist/types/scanner.d.ts +0 -9
- package/dist/types/scanner.d.ts.map +1 -1
- package/dist/types/strategy.d.ts +6 -0
- package/dist/types/strategy.d.ts.map +1 -1
- package/dist/utils/event-catalog.d.ts +0 -81
- package/dist/utils/event-catalog.d.ts.map +1 -1
- package/dist/utils/event-catalog.js +0 -1
- package/dist/utils/event-catalog.js.map +1 -1
- package/dist/utils/keyed-mutex.d.ts +1 -3
- package/dist/utils/keyed-mutex.d.ts.map +1 -1
- package/dist/utils/keyed-mutex.js +1 -3
- package/dist/utils/keyed-mutex.js.map +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/dist/actions/skipped-signal-results.d.ts +0 -16
- package/dist/actions/skipped-signal-results.d.ts.map +0 -1
- package/dist/actions/skipped-signal-results.js +0 -47
- package/dist/actions/skipped-signal-results.js.map +0 -1
- package/dist/scanners/external/scaffold/ulid.py +0 -58
- package/dist/telemetry/live-telemetry.d.ts +0 -158
- package/dist/telemetry/live-telemetry.d.ts.map +0 -1
- package/dist/telemetry/live-telemetry.js +0 -581
- package/dist/telemetry/live-telemetry.js.map +0 -1
- package/dist/telemetry/scanner-stats.d.ts +0 -136
- package/dist/telemetry/scanner-stats.d.ts.map +0 -1
- package/dist/telemetry/scanner-stats.js +0 -304
- package/dist/telemetry/scanner-stats.js.map +0 -1
- package/dist/telemetry/strategy-topology.d.ts +0 -99
- package/dist/telemetry/strategy-topology.d.ts.map +0 -1
- package/dist/telemetry/strategy-topology.js +0 -93
- package/dist/telemetry/strategy-topology.js.map +0 -1
- package/dist/telemetry/telemetry-publisher.d.ts +0 -63
- package/dist/telemetry/telemetry-publisher.d.ts.map +0 -1
- package/dist/telemetry/telemetry-publisher.js +0 -229
- package/dist/telemetry/telemetry-publisher.js.map +0 -1
- package/dist/telemetry/timeline-buffer.d.ts +0 -95
- package/dist/telemetry/timeline-buffer.d.ts.map +0 -1
- package/dist/telemetry/timeline-buffer.js +0 -237
- package/dist/telemetry/timeline-buffer.js.map +0 -1
- package/dist/utils/ulid.d.ts +0 -9
- package/dist/utils/ulid.d.ts.map +0 -1
- package/dist/utils/ulid.js +0 -43
- package/dist/utils/ulid.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"strategy-topology.js","sourceRoot":"","sources":["../../src/telemetry/strategy-topology.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The producer tap for the live telemetry stream: a second {@link EventSink} that copies selected
|
|
3
|
-
* `logger.event` records into the in-memory {@link TimelineBuffer} while the on-disk ring keeps
|
|
4
|
-
* receiving every one of them. It sits on the serial trading path, so it resolves the scanner id,
|
|
5
|
-
* picks a fixed set of fields, appends, and returns — no I/O, no await, no counting, no serialization.
|
|
6
|
-
*
|
|
7
|
-
* Two lists govern the stream, and this module owns only the first:
|
|
8
|
-
* - ADMISSION ({@link ADMITTED_EVENT_NAMES}) — what enters the buffer. Every name the timeline
|
|
9
|
-
* displays PLUS every name a counter is derived from, because a counting source that never enters
|
|
10
|
-
* the buffer makes the stats read zero with nothing logged anywhere.
|
|
11
|
-
* - PUBLICATION — what leaves on the timeline event. Applied at flush time, not here. Admission is
|
|
12
|
-
* deliberately the wider of the two: filtering for display at append time is unrecoverable.
|
|
13
|
-
*/
|
|
14
|
-
import { type EventName } from "../utils/event-catalog.js";
|
|
15
|
-
import { type EventSink } from "./event-store.js";
|
|
16
|
-
import { type TimelineBuffer } from "./timeline-buffer.js";
|
|
17
|
-
/**
|
|
18
|
-
* The names that may enter the buffer. Volume control for a bounded ring, so it is an allow-list of
|
|
19
|
-
* the strategy's own narrative: what its scanners did, what each signal became, what was traded, and
|
|
20
|
-
* why the strategy's status changed. It excludes the box-operations and agent-plane families —
|
|
21
|
-
* `auto_update.*`, `tool.*`, `tool_approval.*`, the installer and state/mcp faults — and the
|
|
22
|
-
* mechanical, poll-frequency records (`dsl.sl_updated`, `dsl.close_pending`, `dsl.close_attempt`,
|
|
23
|
-
* `dsl.price_fetch_stale`), which would evict a strategy's recent window without adding a line
|
|
24
|
-
* anyone reads.
|
|
25
|
-
*
|
|
26
|
-
* Typed as `EventName`, so renaming a catalog entry out from under this list is a compile error
|
|
27
|
-
* rather than a silently empty counter.
|
|
28
|
-
*/
|
|
29
|
-
export declare const ADMITTED_EVENT_NAMES: ReadonlySet<EventName>;
|
|
30
|
-
/**
|
|
31
|
-
* What the tap cost, in nanoseconds, since the last read. Two plain numbers and a maximum — no span,
|
|
32
|
-
* no histogram, no allocation: a span per event is exactly the blocking cost the buffer exists to
|
|
33
|
-
* avoid, and this sits on the same serial trading path the records describe.
|
|
34
|
-
*/
|
|
35
|
-
export interface TapCounters {
|
|
36
|
-
/** Tap invocations, admitted or not: the admission check is cost the trading path already paid. */
|
|
37
|
-
calls: number;
|
|
38
|
-
nsTotal: number;
|
|
39
|
-
/** The longest single call. An average hides the one append that had to evict. */
|
|
40
|
-
nsMax: number;
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Take the window and zero it. The reader owns the window: a counter that is never reset reports a
|
|
44
|
-
* lifetime total forever, which sizes nothing. Process-wide, like the buffer the tap feeds.
|
|
45
|
-
*/
|
|
46
|
-
export declare function readAndResetTapCounters(): TapCounters;
|
|
47
|
-
export interface TelemetryPublisherOptions {
|
|
48
|
-
/** Buffer to feed. Defaults to the process buffer — one per process, shared by every address. */
|
|
49
|
-
buffer?: TimelineBuffer;
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* A sink that copies admitted records into the timeline buffer under `address`. One instance per
|
|
53
|
-
* registered address; every instance writes into the same process buffer, which is what makes the
|
|
54
|
-
* publisher process-wide and address-partitioned rather than per-runtime.
|
|
55
|
-
*/
|
|
56
|
-
export declare function createTelemetryPublisherSink(address: string, options?: TelemetryPublisherOptions): EventSink;
|
|
57
|
-
/**
|
|
58
|
-
* One sink that feeds several. `registerEventSink` keys a single sink per address, so this is how
|
|
59
|
-
* the publisher rides alongside the on-disk ring instead of replacing it. Each branch already
|
|
60
|
-
* swallows its own failures; the loop keeps one branch's throw from starving the rest regardless.
|
|
61
|
-
*/
|
|
62
|
-
export declare function createFanOutEventSink(sinks: readonly EventSink[]): EventSink;
|
|
63
|
-
//# sourceMappingURL=telemetry-publisher.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"telemetry-publisher.d.ts","sourceRoot":"","sources":["../../src/telemetry/telemetry-publisher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAA8B,KAAK,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAEvF,OAAO,EAA4B,KAAK,SAAS,EAAoB,MAAM,kBAAkB,CAAC;AAC9F,OAAO,EAAqB,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAS9E;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB,EAAE,WAAW,CAAC,SAAS,CAqDtD,CAAC;AAEH;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,mGAAmG;IACnG,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,kFAAkF;IAClF,KAAK,EAAE,MAAM,CAAC;CACf;AAMD;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,WAAW,CAMrD;AASD,MAAM,WAAW,yBAAyB;IACxC,iGAAiG;IACjG,MAAM,CAAC,EAAE,cAAc,CAAC;CACzB;AAqCD;;;;GAIG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,yBAA8B,GACtC,SAAS,CAkDX;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,SAAS,CAuB5E"}
|
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The producer tap for the live telemetry stream: a second {@link EventSink} that copies selected
|
|
3
|
-
* `logger.event` records into the in-memory {@link TimelineBuffer} while the on-disk ring keeps
|
|
4
|
-
* receiving every one of them. It sits on the serial trading path, so it resolves the scanner id,
|
|
5
|
-
* picks a fixed set of fields, appends, and returns — no I/O, no await, no counting, no serialization.
|
|
6
|
-
*
|
|
7
|
-
* Two lists govern the stream, and this module owns only the first:
|
|
8
|
-
* - ADMISSION ({@link ADMITTED_EVENT_NAMES}) — what enters the buffer. Every name the timeline
|
|
9
|
-
* displays PLUS every name a counter is derived from, because a counting source that never enters
|
|
10
|
-
* the buffer makes the stats read zero with nothing logged anywhere.
|
|
11
|
-
* - PUBLICATION — what leaves on the timeline event. Applied at flush time, not here. Admission is
|
|
12
|
-
* deliberately the wider of the two: filtering for display at append time is unrecoverable.
|
|
13
|
-
*/
|
|
14
|
-
import { EVENT_NAME } from "../utils/event-catalog.js";
|
|
15
|
-
import { createLogger } from "../utils/logger.js";
|
|
16
|
-
import { normalizeStrategyAddress } from "./event-store.js";
|
|
17
|
-
import { getTimelineBuffer } from "./timeline-buffer.js";
|
|
18
|
-
const logger = createLogger("[senpi-core][telemetry-publisher]");
|
|
19
|
-
/** The scanner's STABLE id. Carried by every event whose `senpi.scanner.id` is ephemeral. */
|
|
20
|
-
const SCANNER_NAME_ATTR = "senpi.scanner.name";
|
|
21
|
-
/** Stable on `signal.outcome` and `scanner.summary`; the ephemeral per-launch id everywhere else. */
|
|
22
|
-
const SCANNER_ID_ATTR = "senpi.scanner.id";
|
|
23
|
-
/**
|
|
24
|
-
* The names that may enter the buffer. Volume control for a bounded ring, so it is an allow-list of
|
|
25
|
-
* the strategy's own narrative: what its scanners did, what each signal became, what was traded, and
|
|
26
|
-
* why the strategy's status changed. It excludes the box-operations and agent-plane families —
|
|
27
|
-
* `auto_update.*`, `tool.*`, `tool_approval.*`, the installer and state/mcp faults — and the
|
|
28
|
-
* mechanical, poll-frequency records (`dsl.sl_updated`, `dsl.close_pending`, `dsl.close_attempt`,
|
|
29
|
-
* `dsl.price_fetch_stale`), which would evict a strategy's recent window without adding a line
|
|
30
|
-
* anyone reads.
|
|
31
|
-
*
|
|
32
|
-
* Typed as `EventName`, so renaming a catalog entry out from under this list is a compile error
|
|
33
|
-
* rather than a silently empty counter.
|
|
34
|
-
*/
|
|
35
|
-
export const ADMITTED_EVENT_NAMES = new Set([
|
|
36
|
-
// Counting sources. `opens` and `skips` come from `signal.outcome`, `errors` from the two
|
|
37
|
-
// `scaffold.tick_*` names. These four are load-bearing, not display-driven.
|
|
38
|
-
EVENT_NAME.signalOutcome,
|
|
39
|
-
EVENT_NAME.decisionMade,
|
|
40
|
-
EVENT_NAME.scaffoldTickError,
|
|
41
|
-
EVENT_NAME.scaffoldTickTimeout,
|
|
42
|
-
// Scanner run and health: the per-run heartbeat plus every supervisor transition behind a
|
|
43
|
-
// scanner's health, degraded reason and restart count.
|
|
44
|
-
EVENT_NAME.scannerSummary,
|
|
45
|
-
EVENT_NAME.scannerSpawned,
|
|
46
|
-
EVENT_NAME.scannerExited,
|
|
47
|
-
EVENT_NAME.scannerRestarted,
|
|
48
|
-
EVENT_NAME.scannerDegraded,
|
|
49
|
-
EVENT_NAME.scannerRecovered,
|
|
50
|
-
EVENT_NAME.scannerSilenceKilled,
|
|
51
|
-
EVENT_NAME.scannerLaunchFailed,
|
|
52
|
-
// Signal intake: what arrived and what was dropped before an action ever evaluated it.
|
|
53
|
-
EVENT_NAME.signalAccepted,
|
|
54
|
-
EVENT_NAME.signalRejected,
|
|
55
|
-
EVENT_NAME.signalExpired,
|
|
56
|
-
EVENT_NAME.signalSuperseded,
|
|
57
|
-
// Execution: the order and position story behind an accepted signal.
|
|
58
|
-
EVENT_NAME.orderPlaced,
|
|
59
|
-
EVENT_NAME.orderFilled,
|
|
60
|
-
EVENT_NAME.orderFailed,
|
|
61
|
-
EVENT_NAME.positionOpened,
|
|
62
|
-
EVENT_NAME.positionClosed,
|
|
63
|
-
EVENT_NAME.positionIncreased,
|
|
64
|
-
EVENT_NAME.positionDecreased,
|
|
65
|
-
EVENT_NAME.positionFlipped,
|
|
66
|
-
EVENT_NAME.positionReconciled,
|
|
67
|
-
// Exit engine: the state changes of an armed exit, not its per-tick floor arithmetic.
|
|
68
|
-
EVENT_NAME.dslCreated,
|
|
69
|
-
EVENT_NAME.dslPhaseChanged,
|
|
70
|
-
EVENT_NAME.dslTierAdvanced,
|
|
71
|
-
EVENT_NAME.dslClosed,
|
|
72
|
-
EVENT_NAME.dslDeleted,
|
|
73
|
-
EVENT_NAME.dslSlSyncFailed,
|
|
74
|
-
EVENT_NAME.dslHandoffFailed,
|
|
75
|
-
// Strategy status, and the two faults that explain a strategy that is up but not trading.
|
|
76
|
-
EVENT_NAME.runtimeStarted,
|
|
77
|
-
EVENT_NAME.runtimeStopped,
|
|
78
|
-
EVENT_NAME.runtimePaused,
|
|
79
|
-
EVENT_NAME.runtimeResumed,
|
|
80
|
-
EVENT_NAME.runtimeError,
|
|
81
|
-
EVENT_NAME.runtimeQueueBackpressure,
|
|
82
|
-
EVENT_NAME.runtimeScannersUnwired,
|
|
83
|
-
]);
|
|
84
|
-
let tapCalls = 0;
|
|
85
|
-
let tapNsTotal = 0;
|
|
86
|
-
let tapNsMax = 0;
|
|
87
|
-
/**
|
|
88
|
-
* Take the window and zero it. The reader owns the window: a counter that is never reset reports a
|
|
89
|
-
* lifetime total forever, which sizes nothing. Process-wide, like the buffer the tap feeds.
|
|
90
|
-
*/
|
|
91
|
-
export function readAndResetTapCounters() {
|
|
92
|
-
const window = { calls: tapCalls, nsTotal: tapNsTotal, nsMax: tapNsMax };
|
|
93
|
-
tapCalls = 0;
|
|
94
|
-
tapNsTotal = 0;
|
|
95
|
-
tapNsMax = 0;
|
|
96
|
-
return window;
|
|
97
|
-
}
|
|
98
|
-
function noteTapCall(startedAt) {
|
|
99
|
-
const ns = Number(process.hrtime.bigint() - startedAt);
|
|
100
|
-
tapCalls += 1;
|
|
101
|
-
tapNsTotal += ns;
|
|
102
|
-
if (ns > tapNsMax)
|
|
103
|
-
tapNsMax = ns;
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* The scanner this record belongs to. The name attribute wins: `senpi.scanner.id` carries a
|
|
107
|
-
* per-launch id on the intake, scaffold and supervisor events, so reading it unqualified files one
|
|
108
|
-
* scanner under a new key on every external-scanner restart. Absent — never empty — when the record
|
|
109
|
-
* has no scanner at all; the record is still buffered and the client buckets it as unattributed.
|
|
110
|
-
*/
|
|
111
|
-
function resolveScannerId(attrs) {
|
|
112
|
-
const name = attrs[SCANNER_NAME_ATTR];
|
|
113
|
-
if (typeof name === "string" && name !== "")
|
|
114
|
-
return name;
|
|
115
|
-
const id = attrs[SCANNER_ID_ATTR];
|
|
116
|
-
return typeof id === "string" && id !== "" ? id : undefined;
|
|
117
|
-
}
|
|
118
|
-
function isScalar(value) {
|
|
119
|
-
const t = typeof value;
|
|
120
|
-
return t === "string" || t === "number" || t === "boolean";
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* The bag minus any non-scalar value. Unlike the disk ring — which serializes and forgets — the
|
|
124
|
-
* buffer holds this reference for the whole retention window and then puts it on the wire, so a
|
|
125
|
-
* nested object would pin an arbitrary graph in memory and break the payload's scalar contract.
|
|
126
|
-
* The common case allocates nothing: a copy is made only once a bad value is actually seen.
|
|
127
|
-
*/
|
|
128
|
-
function scalarAttrs(attrs, onDropped) {
|
|
129
|
-
let clean = attrs;
|
|
130
|
-
for (const key of Object.keys(attrs)) {
|
|
131
|
-
if (isScalar(attrs[key]))
|
|
132
|
-
continue;
|
|
133
|
-
if (clean === attrs)
|
|
134
|
-
clean = { ...attrs };
|
|
135
|
-
delete clean[key];
|
|
136
|
-
onDropped();
|
|
137
|
-
}
|
|
138
|
-
return clean;
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* A sink that copies admitted records into the timeline buffer under `address`. One instance per
|
|
142
|
-
* registered address; every instance writes into the same process buffer, which is what makes the
|
|
143
|
-
* publisher process-wide and address-partitioned rather than per-runtime.
|
|
144
|
-
*/
|
|
145
|
-
export function createTelemetryPublisherSink(address, options = {}) {
|
|
146
|
-
const bufferAddress = normalizeStrategyAddress(address);
|
|
147
|
-
// Reset on a later success so a transient failure can warn again if it recurs, but a persistently
|
|
148
|
-
// wedged buffer cannot spam a per-event warn on the trading path.
|
|
149
|
-
let warnedAppendFailure = false;
|
|
150
|
-
let warnedNonScalarAttr = false;
|
|
151
|
-
function noteNonScalarAttr(name) {
|
|
152
|
-
if (warnedNonScalarAttr)
|
|
153
|
-
return;
|
|
154
|
-
warnedNonScalarAttr = true;
|
|
155
|
-
logger.warn("telemetry publisher dropped a non-scalar event attribute", {
|
|
156
|
-
"senpi.event.name": name,
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
return {
|
|
160
|
-
append(event) {
|
|
161
|
-
// Measured in a `finally` so the rejected and the failed paths count too: they are the cheapest
|
|
162
|
-
// and the most expensive calls the trading path makes, and an average of neither is honest.
|
|
163
|
-
const startedAt = process.hrtime.bigint();
|
|
164
|
-
try {
|
|
165
|
-
if (!ADMITTED_EVENT_NAMES.has(event.name))
|
|
166
|
-
return;
|
|
167
|
-
const attrs = event.attrs ?? {};
|
|
168
|
-
// Named fields only: whatever else rides the record — a redact slot, resource attributes —
|
|
169
|
-
// is not copied because it is never read.
|
|
170
|
-
(options.buffer ?? getTimelineBuffer()).append({
|
|
171
|
-
ts: event.ts,
|
|
172
|
-
name: event.name,
|
|
173
|
-
level: event.level,
|
|
174
|
-
address: bufferAddress,
|
|
175
|
-
scannerId: resolveScannerId(attrs),
|
|
176
|
-
body: event.body,
|
|
177
|
-
attrs: scalarAttrs(attrs, () => noteNonScalarAttr(event.name)),
|
|
178
|
-
});
|
|
179
|
-
warnedAppendFailure = false;
|
|
180
|
-
}
|
|
181
|
-
catch (err) {
|
|
182
|
-
// The tap must never disturb the logging caller, and a wedged tap must not be silent.
|
|
183
|
-
if (!warnedAppendFailure) {
|
|
184
|
-
warnedAppendFailure = true;
|
|
185
|
-
logger.warn("telemetry publisher append failed; the live stream may be incomplete", {
|
|
186
|
-
address: bufferAddress,
|
|
187
|
-
err: err instanceof Error ? err.message : String(err),
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
finally {
|
|
192
|
-
noteTapCall(startedAt);
|
|
193
|
-
}
|
|
194
|
-
},
|
|
195
|
-
flush: () => Promise.resolve(),
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
/**
|
|
199
|
-
* One sink that feeds several. `registerEventSink` keys a single sink per address, so this is how
|
|
200
|
-
* the publisher rides alongside the on-disk ring instead of replacing it. Each branch already
|
|
201
|
-
* swallows its own failures; the loop keeps one branch's throw from starving the rest regardless.
|
|
202
|
-
*/
|
|
203
|
-
export function createFanOutEventSink(sinks) {
|
|
204
|
-
return {
|
|
205
|
-
append(event) {
|
|
206
|
-
for (const sink of sinks) {
|
|
207
|
-
try {
|
|
208
|
-
sink.append(event);
|
|
209
|
-
}
|
|
210
|
-
catch {
|
|
211
|
-
// A branch that breaks its own no-throw contract must not cost the others their record.
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
},
|
|
215
|
-
async flush() {
|
|
216
|
-
// Sequential and individually guarded: a graceful stop awaits this, so one branch's rejection
|
|
217
|
-
// must not skip another's drain or surface as a failed stop.
|
|
218
|
-
for (const sink of sinks) {
|
|
219
|
-
try {
|
|
220
|
-
await sink.flush();
|
|
221
|
-
}
|
|
222
|
-
catch {
|
|
223
|
-
// A branch that cannot drain has already reported it; the stop path carries on.
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
},
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
//# sourceMappingURL=telemetry-publisher.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"telemetry-publisher.js","sourceRoot":"","sources":["../../src/telemetry/telemetry-publisher.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* In-memory recent-window buffer for the live telemetry stream: the records a client replays on
|
|
3
|
-
* subscribe and the records the stats view is computed from. One buffer per process, partitioned by
|
|
4
|
-
* strategy address — the same shape as the disk ring's process-global sink map, because the tap that
|
|
5
|
-
* feeds it (`appendDomainEvent`) is process-global too.
|
|
6
|
-
*
|
|
7
|
-
* Three things make a client's state recoverable across a reconnect:
|
|
8
|
-
* - `epoch`, a ULID minted when the buffer is created. A change means the plugin restarted, not that
|
|
9
|
-
* frames were lost.
|
|
10
|
-
* - `seq`, monotonic per epoch across every address, so `(epoch, seq)` is a dedupe and resume key and
|
|
11
|
-
* a single `afterSeq` cursor is unambiguous. It never rewinds while the epoch holds — not on
|
|
12
|
-
* teardown, not on eviction.
|
|
13
|
-
* - {@link TimelineBuffer.oldestTs}, the stamp of the oldest record still held. A client whose cursor
|
|
14
|
-
* predates it knows it has a gap that no replay can fill.
|
|
15
|
-
*
|
|
16
|
-
* The write path is on the serial trading path: it takes one bounded slot write and returns. It does
|
|
17
|
-
* no I/O, awaits nothing, computes nothing, and never throws.
|
|
18
|
-
*/
|
|
19
|
-
import type { AttrValue } from "../utils/event-catalog.js";
|
|
20
|
-
import { type LogLevel } from "../utils/logger.js";
|
|
21
|
-
/**
|
|
22
|
-
* Records held per address before the oldest is overwritten. The bound is a record COUNT, per
|
|
23
|
-
* address: a fixed-size ring makes eviction a single slot write (no scan, no shift) and makes the
|
|
24
|
-
* oldest stamp an O(1) read, both of which a byte or age bound would cost the write path. Per address
|
|
25
|
-
* rather than process-wide so one chatty strategy cannot evict a quiet one's window.
|
|
26
|
-
*
|
|
27
|
-
* This is the number to change when the retention window is measured — see also the
|
|
28
|
-
* `maxRecordsPerAddress` option, which overrides it per buffer.
|
|
29
|
-
*/
|
|
30
|
-
export declare const DEFAULT_MAX_RECORDS_PER_ADDRESS = 2000;
|
|
31
|
-
/** The seq of the first record of an epoch. Records start at 1, so `afterSeq: 0` means "everything". */
|
|
32
|
-
export declare const FIRST_SEQ = 1;
|
|
33
|
-
/** One record as the tap hands it over, before the buffer stamps a seq. */
|
|
34
|
-
export interface TimelineRecordInput {
|
|
35
|
-
/** Call-time epoch ms, stamped by `logger.event`. */
|
|
36
|
-
ts: number;
|
|
37
|
-
name: string;
|
|
38
|
-
level: LogLevel;
|
|
39
|
-
address: string;
|
|
40
|
-
/** The engine scanner id. Absent — never empty — on a record that has no scanner. */
|
|
41
|
-
scannerId?: string;
|
|
42
|
-
body: string;
|
|
43
|
-
attrs: Record<string, AttrValue>;
|
|
44
|
-
}
|
|
45
|
-
/** A buffered record: the tap's input plus its position in the epoch. */
|
|
46
|
-
export interface TimelineRecord extends TimelineRecordInput {
|
|
47
|
-
seq: number;
|
|
48
|
-
}
|
|
49
|
-
/** Filters for {@link TimelineBuffer.read}; all optional, all combinable. Time bounds are inclusive. */
|
|
50
|
-
export interface TimelineQuery {
|
|
51
|
-
/** Exclusive lower bound on seq — the client's resume cursor. */
|
|
52
|
-
afterSeq?: number;
|
|
53
|
-
address?: string;
|
|
54
|
-
scannerId?: string;
|
|
55
|
-
sinceMs?: number;
|
|
56
|
-
untilMs?: number;
|
|
57
|
-
/** Keep the last N after filtering (newest tail). */
|
|
58
|
-
limit?: number;
|
|
59
|
-
}
|
|
60
|
-
export interface TimelineBufferOptions {
|
|
61
|
-
/** Overrides {@link DEFAULT_MAX_RECORDS_PER_ADDRESS}. Clamped to at least one record. */
|
|
62
|
-
maxRecordsPerAddress?: number;
|
|
63
|
-
}
|
|
64
|
-
export interface TimelineBuffer {
|
|
65
|
-
/** ULID minted with this buffer. Rides every published payload. */
|
|
66
|
-
readonly epoch: string;
|
|
67
|
-
/** Copy one record in and return. Never blocks, never throws. */
|
|
68
|
-
append(input: TimelineRecordInput): void;
|
|
69
|
-
/** Records matching `query`, oldest first. */
|
|
70
|
-
read(query?: TimelineQuery): TimelineRecord[];
|
|
71
|
-
/** Stamp of the oldest record still held across every address; undefined when nothing is held. */
|
|
72
|
-
oldestTs(): number | undefined;
|
|
73
|
-
/**
|
|
74
|
-
* The largest seq this buffer has thrown away, or zero when it has thrown away nothing. A client
|
|
75
|
-
* resuming from a cursor BELOW this lost at least one record; a cursor at or above it lost none.
|
|
76
|
-
*
|
|
77
|
-
* It is not the oldest seq still held. The rings are per address, so a quiet strategy keeps a low
|
|
78
|
-
* seq alive long after a chatty one has evicted everything above it, and the oldest record held
|
|
79
|
-
* then says nothing about what a resuming client is missing.
|
|
80
|
-
*/
|
|
81
|
-
highestDroppedSeq(): number;
|
|
82
|
-
/** Records appended since the last drain, oldest first. Reads the publish frontier — does not evict. */
|
|
83
|
-
drain(): TimelineRecord[];
|
|
84
|
-
/** Drop every record. The seq counter is deliberately not rewound. */
|
|
85
|
-
teardown(): void;
|
|
86
|
-
}
|
|
87
|
-
export declare function createTimelineBuffer(options?: TimelineBufferOptions): TimelineBuffer;
|
|
88
|
-
/** The process's telemetry buffer, minted (with its epoch) on first use. */
|
|
89
|
-
export declare function getTimelineBuffer(): TimelineBuffer;
|
|
90
|
-
/**
|
|
91
|
-
* Drop the process buffer on plugin reload or shutdown. The next {@link getTimelineBuffer} mints a
|
|
92
|
-
* fresh epoch, so the restarted seq run cannot collide with the one a client already holds.
|
|
93
|
-
*/
|
|
94
|
-
export declare function teardownTimelineBuffer(): void;
|
|
95
|
-
//# sourceMappingURL=timeline-buffer.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"timeline-buffer.d.ts","sourceRoot":"","sources":["../../src/telemetry/timeline-buffer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAgB,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAKjE;;;;;;;;GAQG;AACH,eAAO,MAAM,+BAA+B,OAAO,CAAC;AAEpD,wGAAwG;AACxG,eAAO,MAAM,SAAS,IAAI,CAAC;AAE3B,2EAA2E;AAC3E,MAAM,WAAW,mBAAmB;IAClC,qDAAqD;IACrD,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,QAAQ,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,qFAAqF;IACrF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;CAClC;AAED,yEAAyE;AACzE,MAAM,WAAW,cAAe,SAAQ,mBAAmB;IACzD,GAAG,EAAE,MAAM,CAAC;CACb;AAED,wGAAwG;AACxG,MAAM,WAAW,aAAa;IAC5B,iEAAiE;IACjE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,qBAAqB;IACpC,yFAAyF;IACzF,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,cAAc;IAC7B,mEAAmE;IACnE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,iEAAiE;IACjE,MAAM,CAAC,KAAK,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACzC,8CAA8C;IAC9C,IAAI,CAAC,KAAK,CAAC,EAAE,aAAa,GAAG,cAAc,EAAE,CAAC;IAC9C,kGAAkG;IAClG,QAAQ,IAAI,MAAM,GAAG,SAAS,CAAC;IAC/B;;;;;;;OAOG;IACH,iBAAiB,IAAI,MAAM,CAAC;IAC5B,wGAAwG;IACxG,KAAK,IAAI,cAAc,EAAE,CAAC;IAC1B,sEAAsE;IACtE,QAAQ,IAAI,IAAI,CAAC;CAClB;AAkDD,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,qBAA0B,GAAG,cAAc,CA8IxF;AAMD,4EAA4E;AAC5E,wBAAgB,iBAAiB,IAAI,cAAc,CAGlD;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,IAAI,CAG7C"}
|
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* In-memory recent-window buffer for the live telemetry stream: the records a client replays on
|
|
3
|
-
* subscribe and the records the stats view is computed from. One buffer per process, partitioned by
|
|
4
|
-
* strategy address — the same shape as the disk ring's process-global sink map, because the tap that
|
|
5
|
-
* feeds it (`appendDomainEvent`) is process-global too.
|
|
6
|
-
*
|
|
7
|
-
* Three things make a client's state recoverable across a reconnect:
|
|
8
|
-
* - `epoch`, a ULID minted when the buffer is created. A change means the plugin restarted, not that
|
|
9
|
-
* frames were lost.
|
|
10
|
-
* - `seq`, monotonic per epoch across every address, so `(epoch, seq)` is a dedupe and resume key and
|
|
11
|
-
* a single `afterSeq` cursor is unambiguous. It never rewinds while the epoch holds — not on
|
|
12
|
-
* teardown, not on eviction.
|
|
13
|
-
* - {@link TimelineBuffer.oldestTs}, the stamp of the oldest record still held. A client whose cursor
|
|
14
|
-
* predates it knows it has a gap that no replay can fill.
|
|
15
|
-
*
|
|
16
|
-
* The write path is on the serial trading path: it takes one bounded slot write and returns. It does
|
|
17
|
-
* no I/O, awaits nothing, computes nothing, and never throws.
|
|
18
|
-
*/
|
|
19
|
-
import { newUlid } from "../utils/ulid.js";
|
|
20
|
-
import { createLogger } from "../utils/logger.js";
|
|
21
|
-
import { normalizeStrategyAddress } from "./event-store.js";
|
|
22
|
-
const logger = createLogger("[senpi-core][timeline-buffer]");
|
|
23
|
-
/**
|
|
24
|
-
* Records held per address before the oldest is overwritten. The bound is a record COUNT, per
|
|
25
|
-
* address: a fixed-size ring makes eviction a single slot write (no scan, no shift) and makes the
|
|
26
|
-
* oldest stamp an O(1) read, both of which a byte or age bound would cost the write path. Per address
|
|
27
|
-
* rather than process-wide so one chatty strategy cannot evict a quiet one's window.
|
|
28
|
-
*
|
|
29
|
-
* This is the number to change when the retention window is measured — see also the
|
|
30
|
-
* `maxRecordsPerAddress` option, which overrides it per buffer.
|
|
31
|
-
*/
|
|
32
|
-
export const DEFAULT_MAX_RECORDS_PER_ADDRESS = 2000;
|
|
33
|
-
/** The seq of the first record of an epoch. Records start at 1, so `afterSeq: 0` means "everything". */
|
|
34
|
-
export const FIRST_SEQ = 1;
|
|
35
|
-
/**
|
|
36
|
-
* A fixed-size ring of one address's records. Appending past capacity overwrites the oldest slot, so
|
|
37
|
-
* a write is one index assignment regardless of how full the ring is.
|
|
38
|
-
*/
|
|
39
|
-
class RecordRing {
|
|
40
|
-
capacity;
|
|
41
|
-
slots;
|
|
42
|
-
head = 0;
|
|
43
|
-
count = 0;
|
|
44
|
-
constructor(capacity) {
|
|
45
|
-
this.capacity = capacity;
|
|
46
|
-
this.slots = new Array(capacity);
|
|
47
|
-
}
|
|
48
|
-
/** The seq of the record this write overwrote, or undefined while the ring still has room. */
|
|
49
|
-
push(record) {
|
|
50
|
-
const evicted = this.count === this.capacity ? this.slots[this.head]?.seq : undefined;
|
|
51
|
-
this.slots[(this.head + this.count) % this.capacity] = record;
|
|
52
|
-
if (this.count === this.capacity) {
|
|
53
|
-
this.head = (this.head + 1) % this.capacity;
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
this.count++;
|
|
57
|
-
}
|
|
58
|
-
return evicted;
|
|
59
|
-
}
|
|
60
|
-
oldest() {
|
|
61
|
-
return this.count === 0 ? undefined : this.slots[this.head];
|
|
62
|
-
}
|
|
63
|
-
/** Every held record, oldest first. */
|
|
64
|
-
toArray() {
|
|
65
|
-
const out = [];
|
|
66
|
-
for (let i = 0; i < this.count; i++) {
|
|
67
|
-
const record = this.slots[(this.head + i) % this.capacity];
|
|
68
|
-
if (record !== undefined)
|
|
69
|
-
out.push(record);
|
|
70
|
-
}
|
|
71
|
-
return out;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
function matches(record, query) {
|
|
75
|
-
if (query.afterSeq !== undefined && record.seq <= query.afterSeq)
|
|
76
|
-
return false;
|
|
77
|
-
if (query.scannerId !== undefined && record.scannerId !== query.scannerId)
|
|
78
|
-
return false;
|
|
79
|
-
if (query.sinceMs !== undefined && record.ts < query.sinceMs)
|
|
80
|
-
return false;
|
|
81
|
-
if (query.untilMs !== undefined && record.ts > query.untilMs)
|
|
82
|
-
return false;
|
|
83
|
-
return true;
|
|
84
|
-
}
|
|
85
|
-
export function createTimelineBuffer(options = {}) {
|
|
86
|
-
const requested = options.maxRecordsPerAddress ?? DEFAULT_MAX_RECORDS_PER_ADDRESS;
|
|
87
|
-
// A ring of zero slots would divide by zero on every write; a caller asking for less than one
|
|
88
|
-
// record gets the smallest ring that still works rather than a wedged buffer.
|
|
89
|
-
const capacity = Number.isFinite(requested) ? Math.max(1, Math.floor(requested)) : DEFAULT_MAX_RECORDS_PER_ADDRESS;
|
|
90
|
-
const epoch = newUlid();
|
|
91
|
-
const rings = new Map();
|
|
92
|
-
let nextSeq = FIRST_SEQ;
|
|
93
|
-
let drainCursor = FIRST_SEQ - 1;
|
|
94
|
-
// The eviction frontier, tracked on the write path because it cannot be recovered on the read
|
|
95
|
-
// path: once a record is overwritten, nothing left in the buffer records that it existed.
|
|
96
|
-
let highestDropped = 0;
|
|
97
|
-
// One-shot per buffer, not reset on a later success: bad and good records interleave on this path,
|
|
98
|
-
// so resetting on success would turn the guard into a warn-per-record on a hot loop. Warning once
|
|
99
|
-
// is enough to make the upstream bug findable.
|
|
100
|
-
let warnedNoAddress = false;
|
|
101
|
-
let warnedBadTimestamp = false;
|
|
102
|
-
let warnedAppendFailure = false;
|
|
103
|
-
function ringFor(address) {
|
|
104
|
-
let ring = rings.get(address);
|
|
105
|
-
if (ring === undefined) {
|
|
106
|
-
ring = new RecordRing(capacity);
|
|
107
|
-
rings.set(address, ring);
|
|
108
|
-
}
|
|
109
|
-
return ring;
|
|
110
|
-
}
|
|
111
|
-
function collect(query) {
|
|
112
|
-
const address = query.address === undefined ? undefined : normalizeStrategyAddress(query.address);
|
|
113
|
-
// An address filter selects the partition rather than filtering records, so one address's read
|
|
114
|
-
// cannot reach another's ring at all.
|
|
115
|
-
const searched = address === undefined ? [...rings.values()] : [rings.get(address)];
|
|
116
|
-
const out = [];
|
|
117
|
-
for (const ring of searched) {
|
|
118
|
-
if (ring === undefined)
|
|
119
|
-
continue;
|
|
120
|
-
for (const record of ring.toArray()) {
|
|
121
|
-
if (matches(record, query))
|
|
122
|
-
out.push(record);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
// Each ring is already in seq order; the sort merges partitions into the one order a client
|
|
126
|
-
// resumes on. Read side only — the write path never sorts.
|
|
127
|
-
out.sort((a, b) => a.seq - b.seq);
|
|
128
|
-
return query.limit !== undefined && query.limit > 0 ? out.slice(-query.limit) : out;
|
|
129
|
-
}
|
|
130
|
-
return {
|
|
131
|
-
epoch,
|
|
132
|
-
append(input) {
|
|
133
|
-
try {
|
|
134
|
-
const raw = input?.address;
|
|
135
|
-
const address = typeof raw === "string" ? normalizeStrategyAddress(raw) : "";
|
|
136
|
-
if (address === "") {
|
|
137
|
-
// The tap resolves the address and early-returns without one, so a record arriving here
|
|
138
|
-
// unattributed is an upstream break — it cannot be filed under any strategy.
|
|
139
|
-
if (!warnedNoAddress) {
|
|
140
|
-
warnedNoAddress = true;
|
|
141
|
-
logger.warn("timeline buffer dropped a record with no strategy address", {
|
|
142
|
-
"senpi.event.name": typeof input?.name === "string" ? input.name : "unknown",
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
// `since` is load-bearing — a client reads it to decide whether it has an unfillable gap — so
|
|
148
|
-
// an unusable stamp is replaced with the arrival time rather than poisoning the window.
|
|
149
|
-
let ts = input.ts;
|
|
150
|
-
if (typeof ts !== "number" || !Number.isFinite(ts)) {
|
|
151
|
-
ts = Date.now();
|
|
152
|
-
if (!warnedBadTimestamp) {
|
|
153
|
-
warnedBadTimestamp = true;
|
|
154
|
-
logger.warn("timeline record carried an unusable timestamp; stamping arrival time", {
|
|
155
|
-
"senpi.event.name": typeof input.name === "string" ? input.name : "unknown",
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
const evicted = ringFor(address).push({
|
|
160
|
-
seq: nextSeq++,
|
|
161
|
-
ts,
|
|
162
|
-
name: input.name,
|
|
163
|
-
level: input.level,
|
|
164
|
-
address,
|
|
165
|
-
// Omitted, not present-and-undefined: absent is the wire's own statement about a record
|
|
166
|
-
// with no scanner, and a held record that spells it differently is a second answer to
|
|
167
|
-
// the same question.
|
|
168
|
-
...(input.scannerId === undefined ? {} : { scannerId: input.scannerId }),
|
|
169
|
-
body: input.body,
|
|
170
|
-
// The caller builds a fresh attribute bag per event and does not mutate it afterwards, so
|
|
171
|
-
// the reference is carried as-is — copying it would be work on the trading path.
|
|
172
|
-
attrs: input.attrs ?? {},
|
|
173
|
-
});
|
|
174
|
-
if (evicted !== undefined && evicted > highestDropped)
|
|
175
|
-
highestDropped = evicted;
|
|
176
|
-
}
|
|
177
|
-
catch (err) {
|
|
178
|
-
// The buffer sits on the serial trading path and must never disturb its caller. A wedged
|
|
179
|
-
// buffer must not be silent either.
|
|
180
|
-
if (!warnedAppendFailure) {
|
|
181
|
-
warnedAppendFailure = true;
|
|
182
|
-
logger.warn("timeline buffer append failed; the live stream may be incomplete", {
|
|
183
|
-
err: err instanceof Error ? err.message : String(err),
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
},
|
|
188
|
-
read(query = {}) {
|
|
189
|
-
return collect(query);
|
|
190
|
-
},
|
|
191
|
-
oldestTs() {
|
|
192
|
-
let oldest;
|
|
193
|
-
for (const ring of rings.values()) {
|
|
194
|
-
const record = ring.oldest();
|
|
195
|
-
if (record === undefined)
|
|
196
|
-
continue;
|
|
197
|
-
if (oldest === undefined || record.ts < oldest)
|
|
198
|
-
oldest = record.ts;
|
|
199
|
-
}
|
|
200
|
-
return oldest;
|
|
201
|
-
},
|
|
202
|
-
highestDroppedSeq() {
|
|
203
|
-
return highestDropped;
|
|
204
|
-
},
|
|
205
|
-
drain() {
|
|
206
|
-
const batch = collect({ afterSeq: drainCursor });
|
|
207
|
-
const last = batch[batch.length - 1];
|
|
208
|
-
// Advance only to what was actually returned: records evicted before this drain are gone, and
|
|
209
|
-
// anything newer than the cursor still comes back on the next tick.
|
|
210
|
-
if (last !== undefined)
|
|
211
|
-
drainCursor = last.seq;
|
|
212
|
-
return batch;
|
|
213
|
-
},
|
|
214
|
-
teardown() {
|
|
215
|
-
// Everything held is now gone, so the frontier moves to the last seq issued: a client holding
|
|
216
|
-
// any cursor from this run lost whatever it had not already read.
|
|
217
|
-
highestDropped = nextSeq - 1;
|
|
218
|
-
rings.clear();
|
|
219
|
-
},
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
// ─── The process buffer: one instance, shared by every runtime in this plugin process ───
|
|
223
|
-
let processBuffer = null;
|
|
224
|
-
/** The process's telemetry buffer, minted (with its epoch) on first use. */
|
|
225
|
-
export function getTimelineBuffer() {
|
|
226
|
-
processBuffer ??= createTimelineBuffer();
|
|
227
|
-
return processBuffer;
|
|
228
|
-
}
|
|
229
|
-
/**
|
|
230
|
-
* Drop the process buffer on plugin reload or shutdown. The next {@link getTimelineBuffer} mints a
|
|
231
|
-
* fresh epoch, so the restarted seq run cannot collide with the one a client already holds.
|
|
232
|
-
*/
|
|
233
|
-
export function teardownTimelineBuffer() {
|
|
234
|
-
processBuffer?.teardown();
|
|
235
|
-
processBuffer = null;
|
|
236
|
-
}
|
|
237
|
-
//# sourceMappingURL=timeline-buffer.js.map
|