@senpi/runtime 3.0.102 → 3.0.106

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 (190) hide show
  1. package/dist/actions/action-events.d.ts +7 -21
  2. package/dist/actions/action-events.d.ts.map +1 -1
  3. package/dist/actions/action-events.js +25 -56
  4. package/dist/actions/action-events.js.map +1 -1
  5. package/dist/actions/action-runtime-module.d.ts.map +1 -1
  6. package/dist/actions/action-runtime-module.js +1 -18
  7. package/dist/actions/action-runtime-module.js.map +1 -1
  8. package/dist/actions/close-position/close-position.action.d.ts.map +1 -1
  9. package/dist/actions/close-position/close-position.action.js +12 -86
  10. package/dist/actions/close-position/close-position.action.js.map +1 -1
  11. package/dist/actions/event-derivation.d.ts +2 -40
  12. package/dist/actions/event-derivation.d.ts.map +1 -1
  13. package/dist/actions/event-derivation.js +9 -189
  14. package/dist/actions/event-derivation.js.map +1 -1
  15. package/dist/actions/open-position/open-position.action.d.ts.map +1 -1
  16. package/dist/actions/open-position/open-position.action.js +17 -115
  17. package/dist/actions/open-position/open-position.action.js.map +1 -1
  18. package/dist/actions/position-tracker/position-tracker.action.d.ts +1 -2
  19. package/dist/actions/position-tracker/position-tracker.action.d.ts.map +1 -1
  20. package/dist/actions/position-tracker/position-tracker.action.js +15 -50
  21. package/dist/actions/position-tracker/position-tracker.action.js.map +1 -1
  22. package/dist/dsl/engine-client/event-mapping.d.ts.map +1 -1
  23. package/dist/dsl/engine-client/event-mapping.js +0 -15
  24. package/dist/dsl/engine-client/event-mapping.js.map +1 -1
  25. package/dist/dsl/events/build-closed-event.d.ts +1 -16
  26. package/dist/dsl/events/build-closed-event.d.ts.map +1 -1
  27. package/dist/dsl/events/build-closed-event.js +0 -5
  28. package/dist/dsl/events/build-closed-event.js.map +1 -1
  29. package/dist/dsl/events/handlers.d.ts +0 -7
  30. package/dist/dsl/events/handlers.d.ts.map +1 -1
  31. package/dist/dsl/events/handlers.js +7 -49
  32. package/dist/dsl/events/handlers.js.map +1 -1
  33. package/dist/dsl/monitor/create-monitor.d.ts.map +1 -1
  34. package/dist/dsl/monitor/create-monitor.js +0 -7
  35. package/dist/dsl/monitor/create-monitor.js.map +1 -1
  36. package/dist/dsl/monitor/dsl-failure-events.d.ts +0 -2
  37. package/dist/dsl/monitor/dsl-failure-events.d.ts.map +1 -1
  38. package/dist/dsl/monitor/dsl-failure-events.js +1 -2
  39. package/dist/dsl/monitor/dsl-failure-events.js.map +1 -1
  40. package/dist/dsl/monitor/process-one-position.d.ts +0 -6
  41. package/dist/dsl/monitor/process-one-position.d.ts.map +1 -1
  42. package/dist/dsl/monitor/process-one-position.js +21 -55
  43. package/dist/dsl/monitor/process-one-position.js.map +1 -1
  44. package/dist/health/types.d.ts +0 -6
  45. package/dist/health/types.d.ts.map +1 -1
  46. package/dist/health/types.js.map +1 -1
  47. package/dist/index.d.ts +4 -16
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +47 -245
  50. package/dist/index.js.map +1 -1
  51. package/dist/runtime/domain-events.d.ts.map +1 -1
  52. package/dist/runtime/domain-events.js +0 -7
  53. package/dist/runtime/domain-events.js.map +1 -1
  54. package/dist/runtime/lifecycle-events.d.ts +1 -9
  55. package/dist/runtime/lifecycle-events.d.ts.map +1 -1
  56. package/dist/runtime/lifecycle-events.js +2 -11
  57. package/dist/runtime/lifecycle-events.js.map +1 -1
  58. package/dist/runtime/reconcile-run.d.ts +0 -2
  59. package/dist/runtime/reconcile-run.d.ts.map +1 -1
  60. package/dist/runtime/reconcile-run.js +1 -2
  61. package/dist/runtime/reconcile-run.js.map +1 -1
  62. package/dist/runtime/reconcile-state.d.ts +0 -8
  63. package/dist/runtime/reconcile-state.d.ts.map +1 -1
  64. package/dist/runtime/reconcile-state.js +1 -3
  65. package/dist/runtime/reconcile-state.js.map +1 -1
  66. package/dist/runtime/runtime.d.ts +5 -5
  67. package/dist/runtime/runtime.d.ts.map +1 -1
  68. package/dist/runtime/runtime.js +20 -44
  69. package/dist/runtime/runtime.js.map +1 -1
  70. package/dist/runtime/strategy-registry.d.ts +1 -0
  71. package/dist/runtime/strategy-registry.d.ts.map +1 -1
  72. package/dist/runtime/strategy-registry.js +19 -0
  73. package/dist/runtime/strategy-registry.js.map +1 -1
  74. package/dist/runtime/supervisor/long-running-supervisor.d.ts +0 -8
  75. package/dist/runtime/supervisor/long-running-supervisor.d.ts.map +1 -1
  76. package/dist/runtime/supervisor/long-running-supervisor.js +0 -1
  77. package/dist/runtime/supervisor/long-running-supervisor.js.map +1 -1
  78. package/dist/runtime/update-apply-deps.d.ts +6 -0
  79. package/dist/runtime/update-apply-deps.d.ts.map +1 -1
  80. package/dist/runtime/update-apply-deps.js +8 -19
  81. package/dist/runtime/update-apply-deps.js.map +1 -1
  82. package/dist/runtime-api/intake/intake-events.d.ts +3 -3
  83. package/dist/runtime-api/intake/intake-events.d.ts.map +1 -1
  84. package/dist/runtime-api/intake/intake-events.js +3 -21
  85. package/dist/runtime-api/intake/intake-events.js.map +1 -1
  86. package/dist/runtime-api/intake/intake-mount.d.ts +2 -0
  87. package/dist/runtime-api/intake/intake-mount.d.ts.map +1 -1
  88. package/dist/runtime-api/intake/intake-mount.js +10 -57
  89. package/dist/runtime-api/intake/intake-mount.js.map +1 -1
  90. package/dist/runtime-api/intake/process-signal-post.d.ts +0 -11
  91. package/dist/runtime-api/intake/process-signal-post.d.ts.map +1 -1
  92. package/dist/runtime-api/intake/process-signal-post.js +8 -39
  93. package/dist/runtime-api/intake/process-signal-post.js.map +1 -1
  94. package/dist/runtime-api/intake/scaffold-error-router.d.ts +43 -0
  95. package/dist/runtime-api/intake/scaffold-error-router.d.ts.map +1 -0
  96. package/dist/runtime-api/intake/scaffold-error-router.js +63 -0
  97. package/dist/runtime-api/intake/scaffold-error-router.js.map +1 -0
  98. package/dist/runtime-api/intake/scanner-launcher.d.ts.map +1 -1
  99. package/dist/runtime-api/intake/scanner-launcher.js +0 -12
  100. package/dist/runtime-api/intake/scanner-launcher.js.map +1 -1
  101. package/dist/runtime-api/intake/types.d.ts +1 -22
  102. package/dist/runtime-api/intake/types.d.ts.map +1 -1
  103. package/dist/runtime-api/routes/signals.d.ts.map +1 -1
  104. package/dist/runtime-api/routes/signals.js +1 -7
  105. package/dist/runtime-api/routes/signals.js.map +1 -1
  106. package/dist/runtime-api/routes/signals.schema.d.ts +0 -1
  107. package/dist/runtime-api/routes/signals.schema.d.ts.map +1 -1
  108. package/dist/runtime-api/routes/signals.schema.js +0 -6
  109. package/dist/runtime-api/routes/signals.schema.js.map +1 -1
  110. package/dist/scanners/engine/engine.d.ts +0 -5
  111. package/dist/scanners/engine/engine.d.ts.map +1 -1
  112. package/dist/scanners/engine/engine.js +2 -30
  113. package/dist/scanners/engine/engine.js.map +1 -1
  114. package/dist/scanners/external/scaffold/delivery.py +12 -79
  115. package/dist/scanners/external/scaffold/scaffold.py +9 -63
  116. package/dist/scanners/external/scaffold/senpi_mcp.py +81 -10
  117. package/dist/scanners/external-scanner-receiver.d.ts +0 -11
  118. package/dist/scanners/external-scanner-receiver.d.ts.map +1 -1
  119. package/dist/scanners/external-scanner-receiver.js.map +1 -1
  120. package/dist/scanners/runtime-module.d.ts.map +1 -1
  121. package/dist/scanners/runtime-module.js +0 -5
  122. package/dist/scanners/runtime-module.js.map +1 -1
  123. package/dist/senpi/client.d.ts.map +1 -1
  124. package/dist/senpi/client.js +4 -12
  125. package/dist/senpi/client.js.map +1 -1
  126. package/dist/senpi/types.d.ts +0 -5
  127. package/dist/senpi/types.d.ts.map +1 -1
  128. package/dist/skills-manager/manifest.d.ts.map +1 -1
  129. package/dist/skills-manager/manifest.js +3 -12
  130. package/dist/skills-manager/manifest.js.map +1 -1
  131. package/dist/skills-manager/skills-manifest.json +3 -3
  132. package/dist/telemetry/event-builders.d.ts +6 -74
  133. package/dist/telemetry/event-builders.d.ts.map +1 -1
  134. package/dist/telemetry/event-builders.js +11 -108
  135. package/dist/telemetry/event-builders.js.map +1 -1
  136. package/dist/telemetry/event-store.d.ts +0 -6
  137. package/dist/telemetry/event-store.d.ts.map +1 -1
  138. package/dist/telemetry/event-store.js +6 -6
  139. package/dist/telemetry/event-store.js.map +1 -1
  140. package/dist/telemetry/metrics.d.ts +2 -14
  141. package/dist/telemetry/metrics.d.ts.map +1 -1
  142. package/dist/telemetry/metrics.js +4 -19
  143. package/dist/telemetry/metrics.js.map +1 -1
  144. package/dist/telemetry/tracing.d.ts +0 -9
  145. package/dist/telemetry/tracing.d.ts.map +1 -1
  146. package/dist/telemetry/tracing.js +0 -36
  147. package/dist/telemetry/tracing.js.map +1 -1
  148. package/dist/types/dsl/index.d.ts +0 -11
  149. package/dist/types/dsl/index.d.ts.map +1 -1
  150. package/dist/types/scanner.d.ts +0 -9
  151. package/dist/types/scanner.d.ts.map +1 -1
  152. package/dist/utils/event-catalog.d.ts +0 -81
  153. package/dist/utils/event-catalog.d.ts.map +1 -1
  154. package/dist/utils/event-catalog.js +0 -1
  155. package/dist/utils/event-catalog.js.map +1 -1
  156. package/dist/utils/keyed-mutex.d.ts +1 -3
  157. package/dist/utils/keyed-mutex.d.ts.map +1 -1
  158. package/dist/utils/keyed-mutex.js +1 -3
  159. package/dist/utils/keyed-mutex.js.map +1 -1
  160. package/openclaw.plugin.json +1 -1
  161. package/package.json +1 -1
  162. package/dist/actions/skipped-signal-results.d.ts +0 -16
  163. package/dist/actions/skipped-signal-results.d.ts.map +0 -1
  164. package/dist/actions/skipped-signal-results.js +0 -47
  165. package/dist/actions/skipped-signal-results.js.map +0 -1
  166. package/dist/scanners/external/scaffold/ulid.py +0 -58
  167. package/dist/telemetry/live-telemetry.d.ts +0 -158
  168. package/dist/telemetry/live-telemetry.d.ts.map +0 -1
  169. package/dist/telemetry/live-telemetry.js +0 -581
  170. package/dist/telemetry/live-telemetry.js.map +0 -1
  171. package/dist/telemetry/scanner-stats.d.ts +0 -136
  172. package/dist/telemetry/scanner-stats.d.ts.map +0 -1
  173. package/dist/telemetry/scanner-stats.js +0 -304
  174. package/dist/telemetry/scanner-stats.js.map +0 -1
  175. package/dist/telemetry/strategy-topology.d.ts +0 -99
  176. package/dist/telemetry/strategy-topology.d.ts.map +0 -1
  177. package/dist/telemetry/strategy-topology.js +0 -93
  178. package/dist/telemetry/strategy-topology.js.map +0 -1
  179. package/dist/telemetry/telemetry-publisher.d.ts +0 -63
  180. package/dist/telemetry/telemetry-publisher.d.ts.map +0 -1
  181. package/dist/telemetry/telemetry-publisher.js +0 -229
  182. package/dist/telemetry/telemetry-publisher.js.map +0 -1
  183. package/dist/telemetry/timeline-buffer.d.ts +0 -95
  184. package/dist/telemetry/timeline-buffer.d.ts.map +0 -1
  185. package/dist/telemetry/timeline-buffer.js +0 -237
  186. package/dist/telemetry/timeline-buffer.js.map +0 -1
  187. package/dist/utils/ulid.d.ts +0 -9
  188. package/dist/utils/ulid.d.ts.map +0 -1
  189. package/dist/utils/ulid.js +0 -43
  190. package/dist/utils/ulid.js.map +0 -1
@@ -1,136 +0,0 @@
1
- /**
2
- * The `plugin.senpi.scanner.stats` payload: per-scanner activity counters plus the per-scanner state
3
- * a summary screen needs, built as one pure function of its inputs.
4
- *
5
- * Two halves, from two sources:
6
- * - The **counters** are computed on demand by scanning the recent-window buffer — no second data
7
- * structure, no pre-aggregation on the write path, no retention setting of their own. Records are
8
- * grouped by `(address, scannerId)` and bucketed by timestamp, because `scannerId` alone collides
9
- * across strategies (the engine keys on the same pair).
10
- * - The **state fields** are not derivable from records at all — health, schedule and liveness live in
11
- * the health/system-state providers — so they arrive as {@link ScannerStateSnapshot} inputs and are
12
- * joined on the same key. A scanner known to only one side still gets an entry: counters without
13
- * state read `unknown`, state without counters carries an empty bucket list.
14
- *
15
- * The box never picks the window. It emits fixed-width buckets and the frontend sums however many it
16
- * wants, so one payload serves every client and every window at once. `since` is what makes that sum
17
- * honest: it is the stamp of the oldest record still held, and when nothing is held it is the moment
18
- * this epoch began watching — "I have been watching since X and nothing happened" is a different
19
- * statement from "no data", and only one of them is true.
20
- */
21
- import type { ComponentHealth } from "../health/types.js";
22
- import type { TimelineRecord } from "./timeline-buffer.js";
23
- /**
24
- * Bucket width in ms. It sets two things at once: the payload cost per active scanner, and the finest
25
- * window a frontend can ask for (it can sum buckets, never split one).
26
- *
27
- * 15s divides the windows a summary screen actually shows — 1m is 4 buckets, 5m is 20, 15m is 60 — so
28
- * no rendered window ever needs a partial bucket. It is also about two ticks of the fastest scanner
29
- * the runtime will schedule (built-in cadence is floored at 7s in `runtime-schema.ts`), which keeps a
30
- * bucket from hiding a run while still letting "it went quiet 30 seconds ago" show up. Buckets are
31
- * emitted sparsely, so an idle scanner costs nothing regardless of this value.
32
- *
33
- * This is the number to change once real payload sizes are measured — see also the `bucketMs` input,
34
- * which overrides it per call.
35
- */
36
- export declare const DEFAULT_BUCKET_MS = 15000;
37
- /**
38
- * Ceiling on the buckets one payload may carry, across every scanner and every address.
39
- *
40
- * Buckets are the only part of this payload that grows without a bound of its own. A scanner
41
- * producing one counted record per run fills one bucket per run, so a retention window of N records
42
- * is N buckets on a single scanner, and every address the box runs adds its own series — the state
43
- * rows do not, because there is one per registered scanner. One bucket serialises to about sixty
44
- * bytes, so this budgets them at roughly 60 KiB and leaves the rest of the frame to the rows.
45
- *
46
- * Nothing downstream survives a breach: the bridge's north read limit is 256 KiB and exceeding it
47
- * closes the client socket, which loses the stream rather than one sample.
48
- *
49
- * The trim cuts at a bucket boundary, so a payload may carry up to one extra bucket per scanner —
50
- * splitting a timestamp across scanners would misalign the series a client sums.
51
- */
52
- export declare const MAX_STATS_BUCKETS = 1000;
53
- /**
54
- * The four counters, which sum to the decisions counted in the bucket. `closes` is its own field
55
- * because an accepted close is none of the other three: it opened nothing, it was not declined, and
56
- * nothing failed — folding it into any of them replaces one wrong number with another.
57
- */
58
- export interface ScannerStatsBucket {
59
- /** Bucket start, epoch ms, aligned to a `bucketMs` grid so buckets line up across scanners. */
60
- readonly t: number;
61
- readonly opens: number;
62
- readonly closes: number;
63
- readonly skips: number;
64
- readonly errors: number;
65
- }
66
- /**
67
- * One scanner's state as the health/system-state providers report it. These fields have no
68
- * representation in the buffer, so the builder takes them rather than reaching into the runtime.
69
- *
70
- * `inFlightSince` is a timestamp, not a boolean: a scan running for 200ms is normal and one running
71
- * for four minutes on a 15s interval is hung, and a boolean cannot tell those apart.
72
- */
73
- export interface ScannerStateSnapshot {
74
- readonly address: string;
75
- readonly scannerId: string;
76
- readonly health: ComponentHealth;
77
- readonly degradedReason?: string | null;
78
- readonly enabled: boolean;
79
- readonly inFlightSince: number | null;
80
- readonly lastRunStartedAt: number | null;
81
- readonly lastRunFinishedAt: number | null;
82
- readonly lastRunStatus: string | null;
83
- readonly lastRunReason: string | null;
84
- readonly nextRunAt: number | null;
85
- readonly lastAliveAt: number | null;
86
- /** Null when no supervision row covers this scanner — a known zero is a different statement. */
87
- readonly restartCount: number | null;
88
- }
89
- /**
90
- * One scanner on the wire. `scannerId` is absent — not null — when the records could not be
91
- * attributed to a scanner; the client buckets those separately rather than seeing them folded into a
92
- * named scanner's numbers. Every state field is `null` when no snapshot covers this scanner, which is
93
- * a different statement from a known `false` or a known `0`.
94
- */
95
- export interface ScannerStatsEntry {
96
- readonly address: string;
97
- readonly scannerId?: string;
98
- readonly health: ComponentHealth;
99
- readonly degradedReason: string | null;
100
- readonly enabled: boolean | null;
101
- readonly inFlightSince: number | null;
102
- readonly lastRunStartedAt: number | null;
103
- readonly lastRunFinishedAt: number | null;
104
- readonly lastRunStatus: string | null;
105
- readonly lastRunReason: string | null;
106
- readonly nextRunAt: number | null;
107
- readonly lastAliveAt: number | null;
108
- readonly restartCount: number | null;
109
- /** Only buckets with activity, ascending by `t`. A gap means zero, not missing. */
110
- readonly buckets: ScannerStatsBucket[];
111
- }
112
- export interface ScannerStatsPayload {
113
- readonly v: 1;
114
- readonly epoch: string;
115
- readonly sampledAt: number;
116
- /** Oldest record still held, or the buffer's start time when nothing is held. Never absent. */
117
- readonly since: number;
118
- readonly bucketMs: number;
119
- readonly scanners: ScannerStatsEntry[];
120
- }
121
- export interface BuildScannerStatsInput {
122
- /** The buffer's epoch ULID. A change means the box restarted. */
123
- readonly epoch: string;
124
- /** Every record the counters are derived from — the buffer's full held window. */
125
- readonly records: readonly TimelineRecord[];
126
- /** Per-scanner state from the health/system-state providers. */
127
- readonly scannerState: readonly ScannerStateSnapshot[];
128
- /** When this epoch began watching. Used as `since` only while the buffer holds nothing. */
129
- readonly bufferStartedAt: number;
130
- readonly sampledAt: number;
131
- /** Overrides {@link DEFAULT_BUCKET_MS}. Ignored unless it is a finite width of at least 1ms. */
132
- readonly bucketMs?: number;
133
- }
134
- /** Build one `plugin.senpi.scanner.stats` payload. Pure: no timers, no clocks, no runtime reads. */
135
- export declare function buildScannerStats(input: BuildScannerStatsInput): ScannerStatsPayload;
136
- //# sourceMappingURL=scanner-stats.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"scanner-stats.d.ts","sourceRoot":"","sources":["../../src/telemetry/scanner-stats.ts"],"names":[],"mappings":"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"}
@@ -1,304 +0,0 @@
1
- /**
2
- * The `plugin.senpi.scanner.stats` payload: per-scanner activity counters plus the per-scanner state
3
- * a summary screen needs, built as one pure function of its inputs.
4
- *
5
- * Two halves, from two sources:
6
- * - The **counters** are computed on demand by scanning the recent-window buffer — no second data
7
- * structure, no pre-aggregation on the write path, no retention setting of their own. Records are
8
- * grouped by `(address, scannerId)` and bucketed by timestamp, because `scannerId` alone collides
9
- * across strategies (the engine keys on the same pair).
10
- * - The **state fields** are not derivable from records at all — health, schedule and liveness live in
11
- * the health/system-state providers — so they arrive as {@link ScannerStateSnapshot} inputs and are
12
- * joined on the same key. A scanner known to only one side still gets an entry: counters without
13
- * state read `unknown`, state without counters carries an empty bucket list.
14
- *
15
- * The box never picks the window. It emits fixed-width buckets and the frontend sums however many it
16
- * wants, so one payload serves every client and every window at once. `since` is what makes that sum
17
- * honest: it is the stamp of the oldest record still held, and when nothing is held it is the moment
18
- * this epoch began watching — "I have been watching since X and nothing happened" is a different
19
- * statement from "no data", and only one of them is true.
20
- */
21
- import { normalizeStrategyAddress } from "./event-store.js";
22
- import { createLogger } from "../utils/logger.js";
23
- const logger = createLogger("[senpi-core][scanner-stats]");
24
- /**
25
- * Bucket width in ms. It sets two things at once: the payload cost per active scanner, and the finest
26
- * window a frontend can ask for (it can sum buckets, never split one).
27
- *
28
- * 15s divides the windows a summary screen actually shows — 1m is 4 buckets, 5m is 20, 15m is 60 — so
29
- * no rendered window ever needs a partial bucket. It is also about two ticks of the fastest scanner
30
- * the runtime will schedule (built-in cadence is floored at 7s in `runtime-schema.ts`), which keeps a
31
- * bucket from hiding a run while still letting "it went quiet 30 seconds ago" show up. Buckets are
32
- * emitted sparsely, so an idle scanner costs nothing regardless of this value.
33
- *
34
- * This is the number to change once real payload sizes are measured — see also the `bucketMs` input,
35
- * which overrides it per call.
36
- */
37
- export const DEFAULT_BUCKET_MS = 15_000;
38
- /**
39
- * Ceiling on the buckets one payload may carry, across every scanner and every address.
40
- *
41
- * Buckets are the only part of this payload that grows without a bound of its own. A scanner
42
- * producing one counted record per run fills one bucket per run, so a retention window of N records
43
- * is N buckets on a single scanner, and every address the box runs adds its own series — the state
44
- * rows do not, because there is one per registered scanner. One bucket serialises to about sixty
45
- * bytes, so this budgets them at roughly 60 KiB and leaves the rest of the frame to the rows.
46
- *
47
- * Nothing downstream survives a breach: the bridge's north read limit is 256 KiB and exceeding it
48
- * closes the client socket, which loses the stream rather than one sample.
49
- *
50
- * The trim cuts at a bucket boundary, so a payload may carry up to one extra bucket per scanner —
51
- * splitting a timestamp across scanners would misalign the series a client sums.
52
- */
53
- export const MAX_STATS_BUCKETS = 1_000;
54
- /** The one event carrying an action decision, one record per evaluation. */
55
- const OUTCOME_EVENT = "signal.outcome";
56
- /** Scanner-side tick failures: the scanner produced nothing, and that is not a decision. */
57
- const ERROR_EVENTS = new Set(["scaffold.tick_error", "scaffold.tick_timeout"]);
58
- const ATTR_OUTCOME_RESULT = "senpi.outcome.result";
59
- const ATTR_ACTION_TYPE = "senpi.action.type";
60
- const ATTR_SCANNER_NAME = "senpi.scanner.name";
61
- const ATTR_SCANNER_ID = "senpi.scanner.id";
62
- /** The only disposition meaning an order was actually placed — of either kind. */
63
- const RESULT_ACCEPTED = "accepted";
64
- /** The disposition meaning the attempt itself failed. */
65
- const RESULT_ERROR = "error";
66
- /** The producer's closed disposition set; anything outside it is drift worth a warning. */
67
- const KNOWN_RESULTS = new Set([RESULT_ACCEPTED, "rejected", "blocked", RESULT_ERROR]);
68
- /** The `ActionType` values that tell an accepted order which bucket it belongs in. */
69
- const ACTION_OPEN = "OPEN_POSITION";
70
- const ACTION_CLOSE = "CLOSE_POSITION";
71
- /** Separates the two halves of a group key. Never appears in a wallet or a scanner id. */
72
- const KEY_SEP = "\u0000";
73
- /**
74
- * Distinct unrecognised dispositions already warned about, process-wide. Keyed by value rather than
75
- * one-shot so a second kind of drift is still reported, and never per record — the flush tick can
76
- * carry thousands of them.
77
- */
78
- const warnedResults = new Set();
79
- /** The same, for action types. The empty string keys an outcome that carried none. */
80
- const warnedActionTypes = new Set();
81
- /**
82
- * The worst overflow warned about so far. A box permanently over the cap would otherwise warn on
83
- * every sample tick, and one that is getting worse would otherwise say so only once.
84
- */
85
- let warnedBucketOverflow = 0;
86
- function key(address, scannerId) {
87
- return `${address}${KEY_SEP}${scannerId ?? ""}`;
88
- }
89
- /**
90
- * Rule 1: `senpi.scanner.id` carries the stable id on `signal.outcome` but the ephemeral, per-spawn id
91
- * on the scaffold tick events, where the stable value rides `senpi.scanner.name`. Reading the id
92
- * unqualified splits one scanner into two on every restart. The tap resolves this; the fallback here
93
- * keeps a record whose tap did not from landing in the unattributed bucket.
94
- */
95
- function resolveScannerId(record) {
96
- if (typeof record.scannerId === "string" && record.scannerId !== "")
97
- return record.scannerId;
98
- const name = record.attrs?.[ATTR_SCANNER_NAME];
99
- if (typeof name === "string" && name !== "")
100
- return name;
101
- const id = record.attrs?.[ATTR_SCANNER_ID];
102
- if (typeof id === "string" && id !== "")
103
- return id;
104
- return undefined;
105
- }
106
- function warnUnknownResult(result) {
107
- if (warnedResults.has(result))
108
- return;
109
- warnedResults.add(result);
110
- logger.warn("signal.outcome carried an unrecognised disposition; counting it as a skip", {
111
- [ATTR_OUTCOME_RESULT]: result,
112
- });
113
- }
114
- function warnUnknownActionType(actionType) {
115
- if (warnedActionTypes.has(actionType))
116
- return;
117
- warnedActionTypes.add(actionType);
118
- logger.warn("an accepted signal.outcome named no action type this counter knows; counting it as an open", {
119
- [ATTR_ACTION_TYPE]: actionType === "" ? "(absent)" : actionType,
120
- });
121
- }
122
- /**
123
- * Which of the two order buckets an accepted outcome feeds.
124
- *
125
- * `closes` is asserted positively — a close is the case that has to be proven, because everything
126
- * that is not a close reads as it always did. An action type that is unrecognised, or absent (the
127
- * producer omits the attribute rather than stamping a placeholder that would poison a counter),
128
- * therefore counts as an open: it is the incumbent reading, it keeps the four counters summing to
129
- * the decisions counted, and it is warned about once per distinct value so the drift is findable
130
- * rather than dropped or silently folded away.
131
- */
132
- function acceptedCounter(record) {
133
- const raw = record.attrs?.[ATTR_ACTION_TYPE];
134
- const actionType = typeof raw === "string" ? raw : "";
135
- if (actionType === ACTION_CLOSE)
136
- return "closes";
137
- if (actionType !== ACTION_OPEN)
138
- warnUnknownActionType(actionType);
139
- return "opens";
140
- }
141
- /**
142
- * Which counter a record feeds, or `null` when it feeds none.
143
- *
144
- * An order leaving the box is asserted positively: `accepted` is the single disposition that means
145
- * one did (`disposition()` in `event-builders.ts` maps the executed result to it), and the action
146
- * type then says whether it opened a position or closed one. Everything else that reached a decision
147
- * placed no order, so `rejected` (actively gated) and `blocked` (not attempted) both read as a skip,
148
- * and so does any value the producer adds later — an unrecognised disposition is warned about rather
149
- * than silently dropped, because opens + closes + skips + errors must stay equal to the decisions
150
- * counted or the four numbers cannot be read together.
151
- *
152
- * A failed attempt is an `error`, not an open or a close (nothing was placed) and not a skip (it was
153
- * not declined) — which is as true of a close as of an open, so the action type splits the accepted
154
- * case only. The scaffold tick failures land in the same counter: from the summary screen both mean
155
- * the scanner lost work to a fault.
156
- */
157
- function classify(record) {
158
- if (ERROR_EVENTS.has(record.name))
159
- return "errors";
160
- if (record.name !== OUTCOME_EVENT)
161
- return null;
162
- const raw = record.attrs?.[ATTR_OUTCOME_RESULT];
163
- const result = typeof raw === "string" ? raw : "";
164
- if (result === RESULT_ACCEPTED)
165
- return acceptedCounter(record);
166
- if (result === RESULT_ERROR)
167
- return "errors";
168
- if (!KNOWN_RESULTS.has(result))
169
- warnUnknownResult(result);
170
- return "skips";
171
- }
172
- /**
173
- * The oldest bucket start the payload may keep, or undefined when everything fits. Chosen so the
174
- * kept buckets number at most {@link MAX_STATS_BUCKETS}, cut on a timestamp so every scanner's
175
- * series starts at the same place.
176
- */
177
- function trimCutoff(groups) {
178
- const starts = [];
179
- for (const group of groups)
180
- for (const t of group.buckets.keys())
181
- starts.push(t);
182
- if (starts.length <= MAX_STATS_BUCKETS)
183
- return undefined;
184
- starts.sort((a, b) => b - a);
185
- const cutoff = starts[MAX_STATS_BUCKETS - 1];
186
- const dropped = starts.filter((t) => t < cutoff).length;
187
- if (dropped > warnedBucketOverflow) {
188
- warnedBucketOverflow = dropped;
189
- logger.warn("scanner stats dropped its oldest buckets to keep the payload inside one frame", {
190
- "senpi.telemetry.buckets_dropped": dropped,
191
- "senpi.telemetry.buckets_kept": starts.length - dropped,
192
- "senpi.telemetry.bucket_limit": MAX_STATS_BUCKETS,
193
- });
194
- }
195
- return cutoff;
196
- }
197
- function resolveBucketMs(requested) {
198
- if (requested === undefined)
199
- return DEFAULT_BUCKET_MS;
200
- // A zero or negative width makes every bucket key Infinity or NaN, which would collapse the whole
201
- // series into one unreadable bucket rather than fail loudly.
202
- return Number.isFinite(requested) && requested >= 1 ? Math.floor(requested) : DEFAULT_BUCKET_MS;
203
- }
204
- function groupFor(groups, address, scannerId) {
205
- const k = key(address, scannerId);
206
- let group = groups.get(k);
207
- if (group === undefined) {
208
- group = { address, ...(scannerId !== undefined ? { scannerId } : {}), buckets: new Map() };
209
- groups.set(k, group);
210
- }
211
- return group;
212
- }
213
- /** Address, then scanner id, with the unattributed entry last so it reads as a footnote. */
214
- function compareEntries(a, b) {
215
- if (a.address !== b.address)
216
- return a.address < b.address ? -1 : 1;
217
- if (a.scannerId === b.scannerId)
218
- return 0;
219
- if (a.scannerId === undefined)
220
- return 1;
221
- if (b.scannerId === undefined)
222
- return -1;
223
- return a.scannerId < b.scannerId ? -1 : 1;
224
- }
225
- function toEntry(group, snapshot, cutoff) {
226
- const buckets = [...group.buckets.entries()]
227
- .filter(([t]) => cutoff === undefined || t >= cutoff)
228
- .sort(([a], [b]) => a - b)
229
- .map(([t, counts]) => ({
230
- t,
231
- opens: counts.opens,
232
- closes: counts.closes,
233
- skips: counts.skips,
234
- errors: counts.errors,
235
- }));
236
- return {
237
- address: group.address,
238
- ...(group.scannerId !== undefined ? { scannerId: group.scannerId } : {}),
239
- health: snapshot?.health ?? "unknown",
240
- degradedReason: snapshot?.degradedReason ?? null,
241
- enabled: snapshot?.enabled ?? null,
242
- inFlightSince: snapshot?.inFlightSince ?? null,
243
- lastRunStartedAt: snapshot?.lastRunStartedAt ?? null,
244
- lastRunFinishedAt: snapshot?.lastRunFinishedAt ?? null,
245
- lastRunStatus: snapshot?.lastRunStatus ?? null,
246
- lastRunReason: snapshot?.lastRunReason ?? null,
247
- nextRunAt: snapshot?.nextRunAt ?? null,
248
- lastAliveAt: snapshot?.lastAliveAt ?? null,
249
- restartCount: snapshot?.restartCount ?? null,
250
- buckets,
251
- };
252
- }
253
- /** Build one `plugin.senpi.scanner.stats` payload. Pure: no timers, no clocks, no runtime reads. */
254
- export function buildScannerStats(input) {
255
- const bucketMs = resolveBucketMs(input.bucketMs);
256
- const groups = new Map();
257
- const snapshots = new Map();
258
- // Seeded first so a scanner the providers know about still gets an entry when it has produced
259
- // nothing — a silent scanner is a fact the summary screen must be able to show.
260
- for (const snapshot of input.scannerState) {
261
- const address = normalizeStrategyAddress(snapshot.address);
262
- snapshots.set(key(address, snapshot.scannerId), snapshot);
263
- groupFor(groups, address, snapshot.scannerId);
264
- }
265
- let oldest;
266
- for (const record of input.records) {
267
- const ts = record.ts;
268
- if (!Number.isFinite(ts))
269
- continue;
270
- // `since` tracks every held record, not only the counted ones: it describes the window the client
271
- // is reading, which is the whole buffer.
272
- if (oldest === undefined || ts < oldest)
273
- oldest = ts;
274
- const counter = classify(record);
275
- if (counter === null)
276
- continue;
277
- const group = groupFor(groups, normalizeStrategyAddress(record.address), resolveScannerId(record));
278
- const t = Math.floor(ts / bucketMs) * bucketMs;
279
- let counts = group.buckets.get(t);
280
- if (counts === undefined) {
281
- counts = { opens: 0, closes: 0, skips: 0, errors: 0 };
282
- group.buckets.set(t, counts);
283
- }
284
- counts[counter] += 1;
285
- }
286
- const cutoff = trimCutoff(groups.values());
287
- const scanners = [...groups.entries()]
288
- .map(([k, group]) => toEntry(group, snapshots.get(k), cutoff))
289
- .sort(compareEntries);
290
- // A broken start time must not put a non-finite `since` on the wire — it serialises to null and
291
- // the client's truncation check silently stops working.
292
- const held = oldest ?? (Number.isFinite(input.bufferStartedAt) ? input.bufferStartedAt : input.sampledAt);
293
- return {
294
- v: 1,
295
- epoch: input.epoch,
296
- sampledAt: input.sampledAt,
297
- // `since` labels the counters, so a trim moves it: the buckets no longer reach back to the
298
- // oldest record held, and a client summing them from an older mark would read the gap as zeros.
299
- since: cutoff !== undefined && cutoff > held ? cutoff : held,
300
- bucketMs,
301
- scanners,
302
- };
303
- }
304
- //# sourceMappingURL=scanner-stats.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"scanner-stats.js","sourceRoot":"","sources":["../../src/telemetry/scanner-stats.ts"],"names":[],"mappings":"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"}
@@ -1,99 +0,0 @@
1
- /**
2
- * Builder for the `plugin.senpi.strategies` payload — the topology view of the live telemetry
3
- * stream: which strategies exist, whether each is up, how healthy it is, and which scanners it
4
- * declares.
5
- *
6
- * Pure. Every fact arrives as an input, nothing is read from the registry or a runtime handle here,
7
- * so the shape on the wire is testable without booting a runtime.
8
- *
9
- * Two properties this module exists to hold:
10
- * - `status` distinguishes a runtime that is down from one that is up but has no live entry
11
- * scanners. The second reads as healthy unless it is named, and it is not.
12
- * - The raw scanner `config` never reaches the wire. It is an unbounded passthrough record and it
13
- * has been through `resolveEnvInObject`, so it may hold resolved secrets. Only the declared,
14
- * typed fields below are copied, each by name.
15
- */
16
- import type { ComponentHealth } from "../health/types.js";
17
- import type { RuntimeScanner } from "../runtime/runtime-schema.js";
18
- /** Payload schema version. Bump when a field changes meaning, not when one is added. */
19
- export declare const STRATEGY_TOPOLOGY_VERSION = 1;
20
- /**
21
- * `stopped` — no runtime handle.
22
- * `running_no_entry_scanners` — up, but its declared external scanners never wired, so it produces
23
- * no entry signals.
24
- * `running` — up and wired.
25
- */
26
- export type StrategyStatus = "running" | "stopped" | "running_no_entry_scanners";
27
- /** Membership test satisfied by both a `Map` and a `Set` — the two shapes the plugin holds. */
28
- export interface IdMembership {
29
- has(id: string): boolean;
30
- }
31
- /** Engine registration facts for one scanner. Absent when the runtime has no live handle. */
32
- export interface TopologyScannerRegistration {
33
- enabled: boolean;
34
- scheduleMode: "interval" | "external";
35
- intervalSeconds: number | null;
36
- configVersion: number | null;
37
- }
38
- export interface TopologyScannerSource {
39
- /**
40
- * The parsed YAML scanner entry. Only `name`, `type`, `interval_seconds` and
41
- * `signal_data_schema` are ever read from it — `config`, `advanced`, `inputs` and any
42
- * passthrough key stay off the wire.
43
- */
44
- entry: RuntimeScanner;
45
- registration?: TopologyScannerRegistration;
46
- }
47
- export interface TopologyStrategySource {
48
- runtimeId: string;
49
- /** The config wallet as written. Normalized to the wire form by the builder — see below. */
50
- address: string;
51
- name: string;
52
- scanners: TopologyScannerSource[];
53
- }
54
- /** The slice of `RuntimeHealthStatus` the topology reads. `startedAt` is ISO, as health emits it. */
55
- export interface RuntimeHealthLike {
56
- health: ComponentHealth;
57
- startedAt?: string;
58
- }
59
- export interface BuildStrategyTopologyInput {
60
- /** The buffer's epoch ULID. A change means the plugin restarted. */
61
- epoch: string;
62
- strategies: TopologyStrategySource[];
63
- /** Runtimes holding a live handle. Everything else is stopped. */
64
- runningRuntimeIds: IdMembership;
65
- /** Runtimes that are up but whose entry scanners never wired. */
66
- unwiredRuntimeIds: IdMembership;
67
- /** Per-runtime health lookup, keyed on `runtimeId` the way `senpi.getHealthStatus` keys. */
68
- readHealth?: (runtimeId: string) => RuntimeHealthLike | undefined;
69
- /** Injectable clock for deterministic tests. */
70
- now?: () => number;
71
- }
72
- export interface StrategyTopologyScanner {
73
- /** The engine id — the YAML `name:` field. Unique within a runtime, not across runtimes. */
74
- scannerId: string;
75
- scheduleMode: "interval" | "external";
76
- enabled: boolean;
77
- intervalSeconds: number | null;
78
- configVersion: number | null;
79
- signalDataSchema: Record<string, unknown> | null;
80
- }
81
- export interface StrategyTopologyEntry {
82
- runtimeId: string;
83
- address: string;
84
- name: string;
85
- status: StrategyStatus;
86
- health: ComponentHealth;
87
- /** Epoch ms, converted from health's ISO stamp; null when health is unavailable. */
88
- startedAt: number | null;
89
- scanners: StrategyTopologyScanner[];
90
- }
91
- export interface StrategyTopologyPayload {
92
- v: number;
93
- epoch: string;
94
- generatedAt: number;
95
- strategies: StrategyTopologyEntry[];
96
- }
97
- /** Builds one `plugin.senpi.strategies` payload from an already-gathered set of facts. */
98
- export declare function buildStrategyTopology(input: BuildStrategyTopologyInput): StrategyTopologyPayload;
99
- //# sourceMappingURL=strategy-topology.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"strategy-topology.d.ts","sourceRoot":"","sources":["../../src/telemetry/strategy-topology.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAInE,wFAAwF;AACxF,eAAO,MAAM,yBAAyB,IAAI,CAAC;AAE3C;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,SAAS,GAAG,2BAA2B,CAAC;AAEjF,+FAA+F;AAC/F,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1B;AAED,6FAA6F;AAC7F,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,UAAU,GAAG,UAAU,CAAC;IACtC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,KAAK,EAAE,cAAc,CAAC;IACtB,YAAY,CAAC,EAAE,2BAA2B,CAAC;CAC5C;AAED,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,4FAA4F;IAC5F,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,qBAAqB,EAAE,CAAC;CACnC;AAED,qGAAqG;AACrG,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,eAAe,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,0BAA0B;IACzC,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,sBAAsB,EAAE,CAAC;IACrC,kEAAkE;IAClE,iBAAiB,EAAE,YAAY,CAAC;IAChC,iEAAiE;IACjE,iBAAiB,EAAE,YAAY,CAAC;IAChC,4FAA4F;IAC5F,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,iBAAiB,GAAG,SAAS,CAAC;IAClE,gDAAgD;IAChD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,uBAAuB;IACtC,4FAA4F;IAC5F,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,UAAU,GAAG,UAAU,CAAC;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAClD;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,eAAe,CAAC;IACxB,oFAAoF;IACpF,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,uBAAuB,EAAE,CAAC;CACrC;AAED,MAAM,WAAW,uBAAuB;IACtC,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,qBAAqB,EAAE,CAAC;CACrC;AAwDD,0FAA0F;AAC1F,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,0BAA0B,GAChC,uBAAuB,CAwBzB"}
@@ -1,93 +0,0 @@
1
- /**
2
- * Builder for the `plugin.senpi.strategies` payload — the topology view of the live telemetry
3
- * stream: which strategies exist, whether each is up, how healthy it is, and which scanners it
4
- * declares.
5
- *
6
- * Pure. Every fact arrives as an input, nothing is read from the registry or a runtime handle here,
7
- * so the shape on the wire is testable without booting a runtime.
8
- *
9
- * Two properties this module exists to hold:
10
- * - `status` distinguishes a runtime that is down from one that is up but has no live entry
11
- * scanners. The second reads as healthy unless it is named, and it is not.
12
- * - The raw scanner `config` never reaches the wire. It is an unbounded passthrough record and it
13
- * has been through `resolveEnvInObject`, so it may hold resolved secrets. Only the declared,
14
- * typed fields below are copied, each by name.
15
- */
16
- import { createLogger } from "../utils/logger.js";
17
- import { normalizeStrategyAddress } from "./event-store.js";
18
- const logger = createLogger("[senpi-core][strategy-topology]");
19
- /** Payload schema version. Bump when a field changes meaning, not when one is added. */
20
- export const STRATEGY_TOPOLOGY_VERSION = 1;
21
- /**
22
- * Mirrors the three-way distinction `senpi.listRuntimes` draws: no handle is stopped, a handle in
23
- * the unwired set is running blind, anything else is running. The wire carries the machine token —
24
- * the operator-facing string is the frontend's to choose.
25
- */
26
- function resolveStatus(runtimeId, running, unwired) {
27
- if (!running.has(runtimeId))
28
- return "stopped";
29
- return unwired.has(runtimeId) ? "running_no_entry_scanners" : "running";
30
- }
31
- /** Health is advisory here: a lookup that fails must degrade the row, never the payload. */
32
- function resolveHealth(runtimeId, readHealth) {
33
- let status;
34
- try {
35
- status = readHealth?.(runtimeId);
36
- }
37
- catch (err) {
38
- logger.warn("strategy topology could not read runtime health; reporting unknown", err, {
39
- runtimeId,
40
- });
41
- return { health: "unknown", startedAt: null };
42
- }
43
- if (!status)
44
- return { health: "unknown", startedAt: null };
45
- const parsed = status.startedAt === undefined ? NaN : Date.parse(status.startedAt);
46
- return {
47
- health: status.health,
48
- startedAt: Number.isFinite(parsed) ? parsed : null,
49
- };
50
- }
51
- /**
52
- * Copies the declared scanner fields one by one. Never spread `entry` and never copy `config` —
53
- * see the module note.
54
- */
55
- function buildScanner(source) {
56
- const { entry, registration } = source;
57
- const declaredSchema = entry.signal_data_schema;
58
- return {
59
- scannerId: entry.name,
60
- scheduleMode: registration?.scheduleMode ?? (entry.type === "external_scanner" ? "external" : "interval"),
61
- enabled: registration?.enabled ?? true,
62
- intervalSeconds: registration?.intervalSeconds ?? entry.interval_seconds ?? null,
63
- configVersion: registration?.configVersion ?? null,
64
- signalDataSchema: declaredSchema ?? null,
65
- };
66
- }
67
- /** Builds one `plugin.senpi.strategies` payload from an already-gathered set of facts. */
68
- export function buildStrategyTopology(input) {
69
- const now = input.now ?? Date.now;
70
- const strategies = input.strategies.map((source) => {
71
- const { health, startedAt } = resolveHealth(source.runtimeId, input.readHealth);
72
- return {
73
- runtimeId: source.runtimeId,
74
- // `(address, scannerId)` is the join key across all four payloads, and the other three
75
- // normalize their own address input. A checksummed EIP-55 wallet reaching the wire raw would
76
- // spell this strategy differently from its own scanner rows and timeline rows, and nothing
77
- // downstream rejects that — the join just returns nothing.
78
- address: normalizeStrategyAddress(source.address),
79
- name: source.name,
80
- status: resolveStatus(source.runtimeId, input.runningRuntimeIds, input.unwiredRuntimeIds),
81
- health,
82
- startedAt,
83
- scanners: source.scanners.map(buildScanner),
84
- };
85
- });
86
- return {
87
- v: STRATEGY_TOPOLOGY_VERSION,
88
- epoch: input.epoch,
89
- generatedAt: now(),
90
- strategies,
91
- };
92
- }
93
- //# sourceMappingURL=strategy-topology.js.map