1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
//! Typed failures for every host boot, serve, and shutdown path.
use std::net::SocketAddr;
use std::path::PathBuf;
use frame_core::capability::CapabilityMutationError;
use frame_core::component::ComponentId;
use frame_core::error::RegistryError;
use frame_core::event::LifecycleState;
use thiserror::Error;
/// A typed failure from the frame host.
#[derive(Debug, Error)]
pub enum HostError {
/// The embedding application's own hook (readiness proof or fact
/// announcement) refused.
#[error("application {stage} failed: {source}")]
Application {
/// Which application hook refused (`readiness`, `announce`).
stage: &'static str,
/// The application's typed failure.
#[source]
source: crate::spec::AppError,
},
/// The application-event announcer failed to connect to the embedded
/// bus at boot. A typed boot failure — never a silently event-less page.
#[error(
"application-event announcer failed to connect to the embedded bus at {address}: {detail}"
)]
AnnouncerConnect {
/// The embedded bus TCP wire address the connect targeted.
address: String,
/// Exact SDK connect failure.
detail: String,
},
/// The application-event announcer failed to publish on the bus. At
/// boot this is a boot failure; at runtime the announcer records the
/// death loudly and the served page shows it by absence of events.
#[error("application-event announcer failed to publish on '{channel}': {detail}")]
AnnouncerPublish {
/// The application channel the publish targeted.
channel: String,
/// Exact SDK publish failure.
detail: String,
},
/// A fact was announced after teardown had already closed the
/// announcer's intake.
#[error("application-event announcer intake is closed: teardown has begun")]
AnnouncerIntakeClosed,
/// The announcer pump was started twice or its subscription was
/// already consumed — a host composition error.
#[error("application-event announcer pump was already started")]
AnnouncerAlreadyStarted,
/// The announcer pump thread could not be spawned.
#[error("failed to spawn application-event announcer pump thread: {source}")]
AnnouncerSpawn {
/// Underlying spawn failure.
#[source]
source: std::io::Error,
},
/// The announcer pump thread panicked.
#[error("application-event announcer pump thread panicked")]
AnnouncerPanicked,
/// The composed component runtime refused or failed an operation
/// (composition, policy validation, or residue-checked teardown).
#[error("component runtime operation failed")]
Runtime(#[from] frame_core::runtime::RuntimeError),
/// The component registry refused or failed an operation.
#[error("component registry operation failed")]
Registry(#[from] RegistryError),
/// The host capability facade refused an operation.
#[error("host capability operation failed")]
Capability(#[from] CapabilityMutationError),
/// An installed component vanished from the registry between operations.
#[error("component {id} has no status snapshot in the registry")]
StatusMissing {
/// Identity whose snapshot was absent.
id: ComponentId,
},
/// An installed component did not reach Running after start.
#[error("component {id} is {state:?} after start instead of Running")]
NotRunning {
/// Identity that failed to reach Running.
id: ComponentId,
/// Observed lifecycle state.
state: LifecycleState,
},
/// The lifecycle event logger thread could not be spawned.
#[error("failed to spawn lifecycle event logger thread: {source}")]
EventLoggerSpawn {
/// Underlying spawn failure.
#[source]
source: std::io::Error,
},
/// The lifecycle event logger thread panicked.
#[error("lifecycle event logger thread panicked")]
EventLoggerPanicked,
/// The application-truth recorder thread ([`crate::truth::AppTruth`])
/// could not be spawned.
#[error("failed to spawn app-truth recorder thread: {source}")]
TruthRecorderSpawn {
/// Underlying spawn failure.
#[source]
source: std::io::Error,
},
/// The application-truth recorder thread panicked.
#[error("app-truth recorder thread panicked")]
TruthRecorderPanicked,
/// The asset directory is unusable as a shell root.
#[error("asset directory {path} is unusable: {detail}")]
AssetRoot {
/// Configured asset directory.
path: PathBuf,
/// Exact refusal detail.
detail: String,
},
/// The served config would be refused by the shell's config contract.
#[error("shell config contract violation: {detail}")]
ConfigContract {
/// Exact contract refusal and the flag that fixes it.
detail: String,
},
/// The asset directory has no `index.html` shell entry point.
#[error(
"asset directory {path} has no index.html; refusing to serve a shell with no entry point"
)]
MissingIndex {
/// Configured asset directory.
path: PathBuf,
},
/// Binding the shell listener failed.
#[error("failed to bind shell server on {addr}: {source}")]
Bind {
/// Requested socket address.
addr: SocketAddr,
/// Underlying bind failure.
#[source]
source: std::io::Error,
},
/// A stated `[frame].bind` page-server address is already in use. The page
/// server binds exactly what `[frame].bind` states and never silently
/// moves — this is the loud refusal (2026-07-22 portless ruling).
#[error(
"page server address {addr} ([frame].bind) is already in use: {source}. The page server \
binds exactly the stated [frame].bind and never silently moves to another port. Free \
{addr}, or omit [frame].bind entirely to let the host prefer 127.0.0.1:4190 and walk \
forward to a free port. If you deliberately moved [frame].bind, also update \
[bus.websocket].allowed_origins to the page's new origin (http://HOST:PORT) or the \
served page is Origin-refused by the bus."
)]
PageServerUnavailable {
/// The stated page-server address that could not be bound.
addr: SocketAddr,
/// Underlying bind failure.
#[source]
source: std::io::Error,
},
/// The forward walk from the preferred page-server port found no free port
/// anywhere above it — a genuinely exhausted local port space, never a
/// silent give-up.
#[error(
"no free page-server port found walking forward from {from}: the entire port range above \
the preferred port is in use. Free a port, or state an explicit [frame].bind."
)]
NoFreePagePort {
/// The preferred port the exhausted walk started from.
from: u16,
},
/// The shell server failed while serving.
#[error("shell server failed: {source}")]
Serve {
/// Underlying accept/serve failure.
#[source]
source: std::io::Error,
},
/// The async runtime hosting the shell server could not be built.
#[error("failed to build tokio runtime for the shell server: {source}")]
AsyncRuntime {
/// Underlying builder failure.
#[source]
source: std::io::Error,
},
/// The shutdown signal handler could not be installed or failed.
#[error("shutdown signal handling failed: {source}")]
ShutdownSignal {
/// Underlying signal failure.
#[source]
source: std::io::Error,
},
/// Ordered shutdown left live processes on the scheduler.
#[error("ordered stop leaked {count} live scheduler process(es)")]
ProcessResidue {
/// Processes still alive after the ordered drain.
count: usize,
},
/// Host-internal synchronization was poisoned by a panic.
#[error("host synchronization is poisoned")]
SynchronizationPoisoned,
/// The frame configuration file could not be read.
#[error("failed to read frame config {path}: {source}")]
ConfigRead {
/// Configured frame.toml path.
path: PathBuf,
/// Underlying read failure.
#[source]
source: std::io::Error,
},
/// The frame configuration file could not be parsed as TOML into the
/// `[frame]` + `[bus]` schema (or `[liminal]`, the deprecated alias of
/// `[bus]` during the compatibility window), or its bus section was
/// missing or doubled.
#[error("failed to parse frame config {path}: {detail}")]
ConfigParse {
/// Configured frame.toml path.
path: PathBuf,
/// Exact TOML/deserialization refusal.
detail: String,
},
/// The embedded `[bus]` config failed liminal's own validation.
#[error("embedded bus config ([bus] section) is invalid: {source}")]
LiminalConfig {
/// Exact typed liminal validation failure.
#[source]
source: liminal_server::ServerError,
},
/// The `[bus]` config selects a shape the embedded frame server does
/// not faithfully orchestrate (cluster, worker-front-door, or an absent
/// WebSocket transport). Loud refusal at startup, never a silent downgrade.
#[error("embedded frame mode does not support this bus shape: {detail}")]
EmbeddedModeUnsupported {
/// Which shape was refused and why.
detail: String,
},
/// A liminal component failed to bind or boot. Frame-host exits nonzero
/// with the component named — never a half-up stack.
#[error("embedded liminal component '{component}' failed to boot: {source}")]
LiminalComponent {
/// Which liminal component failed (health endpoint, connection
/// services, connection supervisor, TCP listener, WebSocket listener).
component: &'static str,
/// Exact typed liminal failure.
#[source]
source: liminal_server::ServerError,
},
/// Liminal's graceful shutdown sequence failed.
#[error("embedded liminal graceful shutdown failed: {source}")]
LiminalShutdown {
/// Exact typed liminal shutdown failure.
#[source]
source: liminal_server::ServerError,
},
/// The embedded liminal component stopped answering at runtime while the
/// host was still meant to be serving. A dead server behind a healthy host
/// is forbidden: frame-host tears down and exits nonzero.
#[error("embedded liminal component terminated unexpectedly at runtime: {detail}")]
LiminalExited {
/// Which liveness probe failed and against which address.
detail: String,
},
/// The `[document]` initial-content file could not be read.
#[error("failed to read [document].content_path {path}: {source}")]
DocumentContent {
/// The resolved content path.
path: PathBuf,
/// Underlying read failure.
#[source]
source: std::io::Error,
},
/// The document service's frame-state store refused an operation.
#[error("document state store failed: {source}")]
DocumentState {
/// Exact typed frame-state failure.
#[source]
source: frame_state::StateError,
},
/// The document service refused to boot, run, or shut down.
#[error("document service failed: {source}")]
DocumentService {
/// Exact typed service failure.
#[source]
source: frame_doc_service::DocServiceError,
},
/// The document authority's declared configuration was refused.
#[error("[document] authority configuration refused: {source}")]
DocumentAuthorityConfig {
/// Exact typed configuration refusal.
#[source]
source: frame_authority::ConfigError,
},
/// The document binding's bus client (publisher or authoring
/// subscriber) failed to connect or subscribe.
#[error("document bus client '{component}' failed: {detail}")]
DocumentBusClient {
/// Which client failed (feed publisher, authoring subscriber).
component: &'static str,
/// Exact SDK failure detail.
detail: String,
},
/// The authoring pump thread could not start or join.
#[error("authoring pump thread failure: {detail}")]
AuthoringPump {
/// What failed.
detail: String,
},
}