|
4 | 4 | OrderBookValidationError, |
5 | 5 | } from "../src/helpers/market-data-archive/canonical-orderbook"; |
6 | 6 | import { |
| 7 | + assertMarketCaptureArchiveStartable, |
7 | 8 | createMarketCaptureContext, |
8 | 9 | resolveMarketCaptureArchiveState, |
9 | 10 | validateExternalFallbackContext, |
@@ -221,6 +222,36 @@ describe("canonical market capture contract", () => { |
221 | 222 | ).toEqual({ enabled: true }); |
222 | 223 | }); |
223 | 224 |
|
| 225 | + test("startup fails closed when archival was requested but capture identity is incomplete", () => { |
| 226 | + // The resolver stays non-throwing (above); the START decision is what must |
| 227 | + // fail closed. Without this, a production broker missing its deployment or |
| 228 | + // bundle id runs healthy, serves RPC, and writes no market-data row at all — |
| 229 | + // one warn line at boot and no counter, metric or alarm anywhere. |
| 230 | + for (const reason of [ |
| 231 | + "invalid_capture_environment", |
| 232 | + "missing_deployment_id", |
| 233 | + "missing_capture_bundle_id", |
| 234 | + ] as const) { |
| 235 | + expect(() => |
| 236 | + assertMarketCaptureArchiveStartable({ enabled: false, reason }), |
| 237 | + ).toThrow(reason); |
| 238 | + } |
| 239 | + |
| 240 | + // Deliberate opt-outs must keep the broker available — that is the whole |
| 241 | + // point of resolving to a typed state rather than throwing outright. |
| 242 | + for (const reason of [ |
| 243 | + "archive_disabled", |
| 244 | + "market_archive_disabled", |
| 245 | + ] as const) { |
| 246 | + expect(() => |
| 247 | + assertMarketCaptureArchiveStartable({ enabled: false, reason }), |
| 248 | + ).not.toThrow(); |
| 249 | + } |
| 250 | + expect(() => |
| 251 | + assertMarketCaptureArchiveStartable({ enabled: true }), |
| 252 | + ).not.toThrow(); |
| 253 | + }); |
| 254 | + |
224 | 255 | test("external fallback rows cannot cross venue or omit their reason", () => { |
225 | 256 | expect(() => |
226 | 257 | validateExternalFallbackContext({ |
|
0 commit comments