create-mercato-app 0.7.1-develop.7150.1.c1941e0c22 → 0.7.1-develop.7152.1.a69e92f9c9

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 (119) hide show
  1. package/agentic/shared/ai/harness/cases.json +4 -2
  2. package/dist/agentic/guides/module-facts.json +828 -149
  3. package/dist/agentic/guides/module-facts.v2.json +828 -149
  4. package/dist/agentic/guides/modules/ai_assistant/index.md +1 -1
  5. package/dist/agentic/guides/modules/api_docs/index.md +1 -1
  6. package/dist/agentic/guides/modules/api_keys/index.md +1 -1
  7. package/dist/agentic/guides/modules/attachments/index.md +1 -1
  8. package/dist/agentic/guides/modules/audit_logs/index.md +1 -1
  9. package/dist/agentic/guides/modules/auth/index.md +1 -1
  10. package/dist/agentic/guides/modules/business_rules/index.md +1 -1
  11. package/dist/agentic/guides/modules/catalog/index.md +1 -1
  12. package/dist/agentic/guides/modules/channel_apns/index.md +1 -1
  13. package/dist/agentic/guides/modules/channel_discord/index.md +1 -1
  14. package/dist/agentic/guides/modules/channel_expo/index.md +1 -1
  15. package/dist/agentic/guides/modules/channel_fcm/index.md +1 -1
  16. package/dist/agentic/guides/modules/channel_gmail/index.md +1 -1
  17. package/dist/agentic/guides/modules/channel_imap/index.md +1 -1
  18. package/dist/agentic/guides/modules/channel_resend/acl-features.md +12 -0
  19. package/dist/agentic/guides/modules/channel_resend/contribution-resolutions.md +11 -0
  20. package/dist/agentic/guides/modules/channel_resend/di-registrations-rich.md +12 -0
  21. package/dist/agentic/guides/modules/channel_resend/exact-override-targets.md +16 -0
  22. package/dist/agentic/guides/modules/channel_resend/index.md +18 -0
  23. package/dist/agentic/guides/modules/channel_resend/owned-contract-module-metadata.md +11 -0
  24. package/dist/agentic/guides/modules/channel_resend/setup.md +11 -0
  25. package/dist/agentic/guides/modules/channel_resend/umes-contributions.md +11 -0
  26. package/dist/agentic/guides/modules/channel_ses/acl-features.md +12 -0
  27. package/dist/agentic/guides/modules/channel_ses/contribution-resolutions.md +11 -0
  28. package/dist/agentic/guides/modules/channel_ses/di-registrations-rich.md +12 -0
  29. package/dist/agentic/guides/modules/channel_ses/exact-override-targets.md +16 -0
  30. package/dist/agentic/guides/modules/channel_ses/index.md +18 -0
  31. package/dist/agentic/guides/modules/channel_ses/owned-contract-module-metadata.md +11 -0
  32. package/dist/agentic/guides/modules/channel_ses/setup.md +11 -0
  33. package/dist/agentic/guides/modules/channel_ses/umes-contributions.md +11 -0
  34. package/dist/agentic/guides/modules/checkout/index.md +1 -1
  35. package/dist/agentic/guides/modules/communication_channels/di-registrations-rich.md +10 -9
  36. package/dist/agentic/guides/modules/communication_channels/exact-override-targets.md +12 -11
  37. package/dist/agentic/guides/modules/communication_channels/index.md +1 -1
  38. package/dist/agentic/guides/modules/communication_channels/setup.md +1 -1
  39. package/dist/agentic/guides/modules/configs/index.md +1 -1
  40. package/dist/agentic/guides/modules/content/index.md +1 -1
  41. package/dist/agentic/guides/modules/currencies/index.md +1 -1
  42. package/dist/agentic/guides/modules/customer_accounts/index.md +1 -1
  43. package/dist/agentic/guides/modules/customers/index.md +1 -1
  44. package/dist/agentic/guides/modules/dashboards/index.md +1 -1
  45. package/dist/agentic/guides/modules/data_sync/index.md +1 -1
  46. package/dist/agentic/guides/modules/design_system/index.md +1 -1
  47. package/dist/agentic/guides/modules/devices/index.md +1 -1
  48. package/dist/agentic/guides/modules/dictionaries/index.md +1 -1
  49. package/dist/agentic/guides/modules/directory/index.md +1 -1
  50. package/dist/agentic/guides/modules/documents/index.md +1 -1
  51. package/dist/agentic/guides/modules/entities/index.md +1 -1
  52. package/dist/agentic/guides/modules/eudr/index.md +1 -1
  53. package/dist/agentic/guides/modules/events/index.md +1 -1
  54. package/dist/agentic/guides/modules/feature_toggles/index.md +1 -1
  55. package/dist/agentic/guides/modules/gateway_stripe/index.md +1 -1
  56. package/dist/agentic/guides/modules/generators/index.md +1 -1
  57. package/dist/agentic/guides/modules/inbox_ops/index.md +1 -1
  58. package/dist/agentic/guides/modules/integrations/index.md +1 -1
  59. package/dist/agentic/guides/modules/messages/index.md +1 -1
  60. package/dist/agentic/guides/modules/notifications/index.md +1 -1
  61. package/dist/agentic/guides/modules/onboarding/index.md +1 -1
  62. package/dist/agentic/guides/modules/payment_gateways/index.md +1 -1
  63. package/dist/agentic/guides/modules/perspectives/index.md +1 -1
  64. package/dist/agentic/guides/modules/planner/index.md +1 -1
  65. package/dist/agentic/guides/modules/portal/index.md +1 -1
  66. package/dist/agentic/guides/modules/progress/index.md +1 -1
  67. package/dist/agentic/guides/modules/push_notifications/index.md +1 -1
  68. package/dist/agentic/guides/modules/query_index/index.md +1 -1
  69. package/dist/agentic/guides/modules/record_locks/index.md +1 -1
  70. package/dist/agentic/guides/modules/resources/index.md +1 -1
  71. package/dist/agentic/guides/modules/sales/index.md +1 -1
  72. package/dist/agentic/guides/modules/scheduler/index.md +1 -1
  73. package/dist/agentic/guides/modules/search/index.md +1 -1
  74. package/dist/agentic/guides/modules/security/index.md +1 -1
  75. package/dist/agentic/guides/modules/shipping_carriers/index.md +1 -1
  76. package/dist/agentic/guides/modules/sso/index.md +1 -1
  77. package/dist/agentic/guides/modules/staff/index.md +1 -1
  78. package/dist/agentic/guides/modules/storage_s3/index.md +1 -1
  79. package/dist/agentic/guides/modules/sync_akeneo/index.md +1 -1
  80. package/dist/agentic/guides/modules/sync_excel/index.md +1 -1
  81. package/dist/agentic/guides/modules/system_status_overlays/index.md +1 -1
  82. package/dist/agentic/guides/modules/translations/index.md +1 -1
  83. package/dist/agentic/guides/modules/warranty_claims/index.md +1 -1
  84. package/dist/agentic/guides/modules/webhooks/index.md +1 -1
  85. package/dist/agentic/guides/modules/wms/index.md +1 -1
  86. package/dist/agentic/guides/modules/workflows/index.md +1 -1
  87. package/dist/agentic/guides/reference-module-facts.json +1 -1
  88. package/dist/agentic/guides/upstream/manifest.json +1 -1
  89. package/dist/agentic/shared/ai/harness/cases.json +4 -2
  90. package/package.json +3 -3
  91. package/template/.env.example +46 -1
  92. package/template/package.json.template +2 -0
  93. package/template/scripts/dev-runtime-actions.mjs +99 -0
  94. package/template/scripts/dev-runtime-config.mjs +89 -0
  95. package/template/scripts/dev-runtime-diagnostics.mjs +292 -0
  96. package/template/scripts/dev-runtime-gateway.mjs +370 -0
  97. package/template/scripts/dev-runtime-probe.mjs +77 -0
  98. package/template/scripts/dev-runtime-state.mjs +571 -0
  99. package/template/scripts/dev-runtime-supervisor.mjs +268 -0
  100. package/template/scripts/dev-runtime.mjs +29 -0
  101. package/template/scripts/dev-splash-state.mjs +14 -0
  102. package/template/scripts/dev-splash.html +357 -2
  103. package/template/scripts/dev.mjs +396 -14
  104. package/template/src/app/__tests__/start-page-bootstrap.test.ts +34 -0
  105. package/template/src/app/api/dev-runtime/actions/[action]/route.ts +5 -0
  106. package/template/src/app/api/dev-runtime/diagnostics/route.ts +5 -0
  107. package/template/src/app/api/dev-runtime/logs/route.ts +5 -0
  108. package/template/src/app/api/dev-runtime/status/route.ts +5 -0
  109. package/template/src/app/api/healthz/__tests__/route.test.ts +3 -1
  110. package/template/src/app/global-error.tsx +7 -0
  111. package/template/src/app/layout.tsx +11 -0
  112. package/template/src/app/start/page.tsx +11 -1
  113. package/template/src/i18n/de.json +27 -0
  114. package/template/src/i18n/en.json +27 -0
  115. package/template/src/i18n/es.json +27 -0
  116. package/template/src/i18n/ko.json +27 -0
  117. package/template/src/i18n/pl.json +27 -0
  118. package/template/src/modules/example/__integration__/TC-START-001-onboarding-gate.spec.ts +33 -0
  119. package/template/src/modules.ts +2 -0
@@ -0,0 +1,268 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+
4
+ import {
5
+ browserReportToSignal,
6
+ createDevRuntimeToken,
7
+ createDiagnosticsSink,
8
+ validateActionRequest,
9
+ } from './dev-runtime-diagnostics.mjs'
10
+ import { createRuntimeProbe } from './dev-runtime-probe.mjs'
11
+ import { createRuntimeStateStore } from './dev-runtime-state.mjs'
12
+
13
+ export const RUNTIME_STATUS_FILE_NAME = 'dev-runtime-status.json'
14
+ export const RUNTIME_DIAGNOSTICS_FILE_NAME = 'dev-runtime-diagnostics.ndjson'
15
+ export const RUNTIME_ACTIONS_FILE_NAME = 'dev-runtime-actions.ndjson'
16
+ export const RUNTIME_LOGS_FILE_NAME = 'dev-runtime-logs.json'
17
+
18
+ const SYNC_INTERVAL_MS = 1000
19
+ const MAX_CHILD_SIGNALS_PER_SYNC = 50
20
+
21
+ // Owns the generation-aware runtime state for one supervisor process: it
22
+ // ingests app-runtime signals, drains browser reports, runs the continuous
23
+ // probe, and publishes an atomically written status file the dev-only app
24
+ // bridge can read.
25
+ export function createDevRuntimeSupervisor(options = {}) {
26
+ const enabled = options.enabled !== false
27
+ const stateDirectory = options.stateDirectory ?? path.join(process.cwd(), '.mercato')
28
+ const config = options.config ?? {}
29
+ const logger = options.logger ?? console
30
+ const resolveBaseUrl = typeof options.resolveBaseUrl === 'function' ? options.resolveBaseUrl : () => null
31
+ const readChildState = typeof options.readChildState === 'function' ? options.readChildState : () => null
32
+
33
+ const token = createDevRuntimeToken()
34
+ const statusFilePath = path.join(stateDirectory, RUNTIME_STATUS_FILE_NAME)
35
+ const diagnosticsFilePath = path.join(stateDirectory, RUNTIME_DIAGNOSTICS_FILE_NAME)
36
+ const actionsFilePath = path.join(stateDirectory, RUNTIME_ACTIONS_FILE_NAME)
37
+ const logsFilePath = path.join(stateDirectory, RUNTIME_LOGS_FILE_NAME)
38
+
39
+ const store = createRuntimeStateStore({
40
+ cwd: process.cwd(),
41
+ configuredPort: options.configuredPort,
42
+ publicUrl: options.publicUrl,
43
+ })
44
+ const sink = createDiagnosticsSink({ filePath: diagnosticsFilePath })
45
+ // The in-app banner cannot call the supervisor directly, so the dev-only app
46
+ // route queues an allowlisted action here and the supervisor drains it.
47
+ const actionSink = createDiagnosticsSink({ filePath: actionsFilePath, validate: validateActionRequest })
48
+ const runAction = typeof options.runAction === 'function' ? options.runAction : null
49
+
50
+ let lastIngestedChildSignalSeq = 0
51
+ let lastPublishedPayload = null
52
+ let lastPublishedLogsPayload = null
53
+ let syncTimer = null
54
+
55
+ const probe = createRuntimeProbe({
56
+ config: config.probe,
57
+ resolveBaseUrl,
58
+ shouldRun: () => store.isReadySeen() && !store.isRecoveryBusy(),
59
+ onDegraded: (result) => {
60
+ store.recordSignal({
61
+ source: 'probe',
62
+ generation: store.getGeneration(),
63
+ code: 'probe_unhealthy',
64
+ title: 'Runtime health check is failing',
65
+ detail: result?.status
66
+ ? `The runtime answered /api/healthz with HTTP ${result.status}`
67
+ : 'The runtime stopped answering /api/healthz',
68
+ blocking: false,
69
+ })
70
+ publish()
71
+ },
72
+ onRecovered: () => {
73
+ store.clearIncidentsBySource('probe')
74
+ publish()
75
+ },
76
+ })
77
+
78
+ function writeStatusFile(status) {
79
+ if (!enabled) return
80
+ const payload = JSON.stringify({ token, status, pid: process.pid })
81
+ if (payload === lastPublishedPayload) return
82
+ lastPublishedPayload = payload
83
+ const temporaryPath = `${statusFilePath}.${process.pid}.tmp`
84
+ try {
85
+ fs.mkdirSync(stateDirectory, { recursive: true })
86
+ fs.writeFileSync(temporaryPath, payload, 'utf8')
87
+ fs.renameSync(temporaryPath, statusFilePath)
88
+ } catch {
89
+ try {
90
+ fs.rmSync(temporaryPath, { force: true })
91
+ } catch {
92
+ // Publishing status is best-effort; the terminal stays authoritative.
93
+ }
94
+ }
95
+ }
96
+
97
+ // The bounded log tail lives in its own file so the 2s status poll stays small
98
+ // and the app only pays for lines when a developer opens the logs view.
99
+ function writeLogsFile() {
100
+ if (!enabled) return
101
+ const snapshot = store.getDiagnosticLines(0)
102
+ const payload = JSON.stringify({ token, generation: snapshot.generation, lines: snapshot.lines })
103
+ if (payload === lastPublishedLogsPayload) return
104
+ lastPublishedLogsPayload = payload
105
+ const temporaryPath = `${logsFilePath}.${process.pid}.tmp`
106
+ try {
107
+ fs.mkdirSync(stateDirectory, { recursive: true })
108
+ fs.writeFileSync(temporaryPath, payload, 'utf8')
109
+ fs.renameSync(temporaryPath, logsFilePath)
110
+ } catch {
111
+ try {
112
+ fs.rmSync(temporaryPath, { force: true })
113
+ } catch {
114
+ // Publishing logs is best-effort; the terminal stays authoritative.
115
+ }
116
+ }
117
+ }
118
+
119
+ function publish() {
120
+ const status = store.getStatus()
121
+ writeStatusFile(status)
122
+ writeLogsFile()
123
+ return status
124
+ }
125
+
126
+ function drainBrowserReports() {
127
+ if (!enabled) return
128
+ for (const report of sink.drain()) {
129
+ store.recordSignal(browserReportToSignal(report, store.getGeneration()))
130
+ }
131
+ }
132
+
133
+ // Requests carrying a stale generation are dropped: the runtime they were
134
+ // raised against no longer exists.
135
+ function drainActionRequests() {
136
+ if (!enabled || !runAction) return
137
+ for (const request of actionSink.drain()) {
138
+ if (Number.isInteger(request.generation) && request.generation !== store.getGeneration()) continue
139
+ void runAction(request.action)
140
+ }
141
+ }
142
+
143
+ // The app runtime wrapper publishes classified signals through its splash
144
+ // child-state file. Sequence numbers make ingestion idempotent.
145
+ function ingestChildState(childState) {
146
+ if (!enabled || !childState || typeof childState !== 'object') return
147
+ const signals = Array.isArray(childState.runtimeSignals) ? childState.runtimeSignals : []
148
+ let ingested = 0
149
+ for (const signal of signals) {
150
+ if (!signal || typeof signal !== 'object') continue
151
+ if (!Number.isInteger(signal.seq) || signal.seq <= lastIngestedChildSignalSeq) continue
152
+ if (ingested >= MAX_CHILD_SIGNALS_PER_SYNC) break
153
+ lastIngestedChildSignalSeq = signal.seq
154
+ ingested += 1
155
+ store.recordSignal({ ...signal, generation: store.getGeneration() })
156
+ }
157
+ if (childState.ready === true && !store.isReadySeen()) {
158
+ store.markReady()
159
+ }
160
+ }
161
+
162
+ // One collection cycle: pull the child's typed signals, drain browser
163
+ // reports, and republish. Runs on an interval and is safe to call directly.
164
+ function sync() {
165
+ ingestChildState(readChildState())
166
+ drainBrowserReports()
167
+ drainActionRequests()
168
+ return publish()
169
+ }
170
+
171
+ return {
172
+ enabled,
173
+ token,
174
+ statusFilePath,
175
+ diagnosticsFilePath,
176
+ actionsFilePath,
177
+ logsFilePath,
178
+ getStatus: store.getStatus,
179
+ getGeneration: store.getGeneration,
180
+ isReadySeen: store.isReadySeen,
181
+ ingestChildState,
182
+ publish,
183
+ sync,
184
+
185
+ // Env passed to the managed app process so its dev-only routes can find the
186
+ // status file, the diagnostic sink, and the per-run token.
187
+ childEnv() {
188
+ if (!enabled) return {}
189
+ return {
190
+ OM_DEV_RUNTIME_TOKEN: token,
191
+ OM_DEV_RUNTIME_STATUS_FILE: statusFilePath,
192
+ OM_DEV_RUNTIME_DIAGNOSTICS_FILE: diagnosticsFilePath,
193
+ OM_DEV_RUNTIME_ACTIONS_FILE: actionsFilePath,
194
+ OM_DEV_RUNTIME_LOGS_FILE: logsFilePath,
195
+ OM_DEV_RUNTIME_DIAGNOSTICS: '1',
196
+ OM_DEV_RUNTIME_BANNER: config.bannerEnabled === false ? '0' : '1',
197
+ }
198
+ },
199
+
200
+ recordSignal(signal) {
201
+ if (!enabled) return store.getStatus()
202
+ store.recordSignal({ generation: store.getGeneration(), ...signal })
203
+ return publish()
204
+ },
205
+
206
+ beginGeneration(reason) {
207
+ if (!enabled) return 0
208
+ lastIngestedChildSignalSeq = 0
209
+ probe.reset()
210
+ sink.clear()
211
+ actionSink.clear()
212
+ const generation = store.beginGeneration(reason)
213
+ publish()
214
+ return generation
215
+ },
216
+
217
+ setUpstream(patch) {
218
+ if (!enabled) return
219
+ store.setUpstream(patch)
220
+ publish()
221
+ },
222
+
223
+ beginRecovery(action) {
224
+ if (!enabled) return null
225
+ const status = store.beginRecovery(action)
226
+ publish()
227
+ return status
228
+ },
229
+
230
+ completeRecovery(exitCode, options) {
231
+ if (!enabled) return null
232
+ const status = store.completeRecovery(exitCode, options)
233
+ publish()
234
+ return status
235
+ },
236
+
237
+ getDiagnosticLines(cursor) {
238
+ return store.getDiagnosticLines(cursor)
239
+ },
240
+
241
+ start() {
242
+ if (!enabled || syncTimer) return
243
+ sink.clear()
244
+ actionSink.clear()
245
+ publish()
246
+ syncTimer = setInterval(sync, SYNC_INTERVAL_MS)
247
+ syncTimer.unref?.()
248
+ probe.start()
249
+ },
250
+
251
+ stop() {
252
+ if (syncTimer) {
253
+ clearInterval(syncTimer)
254
+ syncTimer = null
255
+ }
256
+ probe.stop()
257
+ if (!enabled) return
258
+ try {
259
+ fs.rmSync(statusFilePath, { force: true })
260
+ fs.rmSync(logsFilePath, { force: true })
261
+ } catch {
262
+ logger?.debug?.('[dev-runtime] Unable to remove the runtime status file during shutdown.')
263
+ }
264
+ sink.clear()
265
+ actionSink.clear()
266
+ },
267
+ }
268
+ }
@@ -187,7 +187,10 @@ const splashState = {
187
187
  progressPercent: 0,
188
188
  progressLabel: setupSplashMode ? 'Starting app runtime' : 'Preparing app runtime',
189
189
  activities: [],
190
+ runtimeSignals: [],
190
191
  }
192
+ const maxBufferedRuntimeSignals = 20
193
+ let runtimeSignalSequence = 0
191
194
  const startupProgress = {
192
195
  current: runtimeProgressCurrent,
193
196
  total: runtimeProgressTotal,
@@ -559,6 +562,19 @@ function collectRuntimeFailureLines(maxLines = 10) {
559
562
  return lines.slice(-maxLines)
560
563
  }
561
564
 
565
+ // Typed hand-off to the root supervisor's incident collector. The wrapper only
566
+ // reports what it observed; classification, redaction, and state transitions
567
+ // stay in the supervisor so one generation owns them.
568
+ function emitRuntimeSignal(signal) {
569
+ if (!splashChildStateFile) return
570
+ runtimeSignalSequence += 1
571
+ splashState.runtimeSignals.push({ seq: runtimeSignalSequence, ...signal })
572
+ while (splashState.runtimeSignals.length > maxBufferedRuntimeSignals) {
573
+ splashState.runtimeSignals.shift()
574
+ }
575
+ persistSplashState()
576
+ }
577
+
562
578
  function publishRuntimeFailure(detail, options = {}) {
563
579
  const failureLines = Array.isArray(options.failureLines) && options.failureLines.length > 0
564
580
  ? options.failureLines
@@ -573,6 +589,15 @@ function publishRuntimeFailure(detail, options = {}) {
573
589
  ? options.progressLabel
574
590
  : (startupProgress.current >= runtimeProgressCurrent ? startupProgress.label : 'Starting app server')
575
591
 
592
+ emitRuntimeSignal({
593
+ source: options.source ?? 'log',
594
+ message: failureLines.join('\n') || failureDetail,
595
+ detail: failureDetail,
596
+ failureLines,
597
+ failureCommand: 'yarn dev',
598
+ failureStage: options.failureStage,
599
+ })
600
+
576
601
  updateSplashState({
577
602
  phase: 'Runtime error detected',
578
603
  detail: failureDetail,
@@ -668,6 +693,8 @@ function reportUnexpectedChildExit(result) {
668
693
  // The banner was just printed, so buffer it without echoing it a second time.
669
694
  bufferRawLog(report.banner)
670
695
  publishRuntimeFailure(report.banner, {
696
+ source: 'process',
697
+ failureStage: result?.label,
671
698
  progressCurrent: splashState.progressCurrent >= runtimeProgressCurrent ? splashState.progressCurrent : runtimeProgressCurrent,
672
699
  progressLabel: splashState.progressLabel || startupProgress.label,
673
700
  failureLines: report.failureLines,
@@ -1084,6 +1111,8 @@ async function runTargetedRouteWarmup() {
1084
1111
  runtimeWarmupState.failed = true
1085
1112
  const detail = `Warmup failed after ${attempt} retries: ${reason}`
1086
1113
  publishRuntimeFailure(detail, {
1114
+ source: 'warmup',
1115
+ failureStage: 'Startup warmup',
1087
1116
  progressCurrent: runtimeProgressCurrent,
1088
1117
  progressLabel: progressLabel,
1089
1118
  failureLines: [
@@ -4,9 +4,23 @@ function hasUsableReadyTarget(state) {
4
4
  || typeof state.readyUrl === 'string' && state.readyUrl.trim().length > 0
5
5
  }
6
6
 
7
+ // The structured runtime payload is the authority for post-ready problems.
8
+ // `ready` is only a compatibility projection, so it must not erase an incident
9
+ // that the collector still considers active for the current generation.
10
+ export function hasActiveRuntimeIncident(state) {
11
+ const runtime = state?.runtime
12
+ if (!runtime || typeof runtime !== 'object') return false
13
+ if (runtime.health === 'degraded' || runtime.health === 'unavailable') return true
14
+ if (!Array.isArray(runtime.incidents)) return false
15
+ return runtime.incidents.some((incident) => (
16
+ incident && typeof incident === 'object' && incident.generation === runtime.generation
17
+ ))
18
+ }
19
+
7
20
  export function shouldPreferReadySplashState(state) {
8
21
  if (!state || typeof state !== 'object') return false
9
22
  if (state.ready !== true) return false
23
+ if (hasActiveRuntimeIncident(state)) return false
10
24
 
11
25
  const phase = typeof state.phase === 'string' ? state.phase : ''
12
26
  const detail = typeof state.detail === 'string' ? state.detail : ''