-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathlobes-never-advertises-a-capability-it-cannot-serv.json
More file actions
467 lines (467 loc) · 29.3 KB
/
Copy pathlobes-never-advertises-a-capability-it-cannot-serv.json
File metadata and controls
467 lines (467 loc) · 29.3 KB
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
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
{
"slug": "lobes-never-advertises-a-capability-it-cannot-serv",
"title": "lobes never advertises a capability it cannot serve: every endpoint, model and role in the machine-readable contract is proven reachable, and a fleet fault degrades to a retryable 503 instead of a terminal 404",
"schema_version": 1,
"status": "exported",
"created": "2026-07-09T05:14:35Z",
"updated": "2026-07-09T07:02:42Z",
"claims": [
{
"id": "c1",
"kind": "announcement",
"text": "lobes never advertises a capability it cannot serve: every endpoint, model and role in the machine-readable contract is proven reachable, and a fleet fault degrades to a retryable 503 instead of a terminal 404",
"origin": "user",
"status": "confirmed",
"honesty_conditions": [
{
"id": "h1",
"text": "On the live rig, for EVERY role in the contract, the advertised endpoint+path answers a real request with a non-404 status; and no model listed by GET /v1/models returns a 404 'does not exist' from POST /v1/chat/completions on the same origin.",
"status": "confirmed"
}
],
"hard_questions": [],
"links": []
},
{
"id": "c2",
"kind": "audience",
"text": "Programmatic consumers of the lobes contract \u2014 Colleague's resolve_role_base_url dialing GET /capabilities, and any long-running agent loop driving /v1/chat/completions through the gateway \u2014 plus the lobes operator who runs the fleet on the DGX Spark.",
"origin": "user",
"status": "confirmed",
"honesty_conditions": [
{
"id": "h2",
"text": "Colleague's resolve_role_base_url, given ONLY GET /capabilities and no COLLEAGUE_*_BASE_URL override, reaches cortex and senses and gets an answer \u2014 the scenario that failed on 2026-07-06 and again on 2026-07-09.",
"status": "confirmed"
}
],
"hard_questions": [],
"links": []
},
{
"id": "c3",
"kind": "before_state",
"text": "lobes advertises CONFIGURATION and calls it REACHABILITY, and nothing ever dials what it advertises. GET /capabilities returns endpoint=http://localhost:8000 with ready=true for every role while the gateway is published on :8001 and :8000 is an unrelated uvicorn service (reachy-mini-dae) answering {\"detail\":\"Not Found\"}. GET /v1/models lists 6 models while only 4 vLLM containers run. RoleInfo.ready is a config fact (==loaded), not a probe \u2014 its own docstring says so. And the deployed gateway silently runs lobes 0.36.0 while main is 0.39.0, so the #87 fix that shipped in 0.38.0 is not even in the running image.",
"origin": "user",
"status": "confirmed",
"honesty_conditions": [
{
"id": "h3",
"text": "A test asserts that the origin the gateway advertises never equals its own internal listen port when a published port differs, and that GET /capabilities and 'lobes capabilities --json' return byte-identical endpoint values for the same deployment.",
"status": "confirmed"
}
],
"hard_questions": [],
"links": []
},
{
"id": "c4",
"kind": "before_state",
"text": "A fleet fault is laundered into a terminal client error. handle_post rewrites the model id ONCE before the failover loop; order_backends offers every same-task generate backend as a failover candidate ([primary, multimodal, multimodal-coder, middle] for cortex). When the cortex vLLM returns >=500 (EngineDeadError, observed 2026-07-09 04:08:34) the gateway retries the same body \u2014 still naming the Qwen model \u2014 against the Gemma backend, which correctly 404s, and '4xx -> commit to this backend, no failover' relays that terminal 404 to the client. The primary container's logs show the symmetric case at 04:34:01 and 04:39:01: 'The model coolthor/gemma-4-12B-it-NVFP4A16 does not exist.' logged BY the Qwen container.",
"origin": "user",
"status": "confirmed",
"honesty_conditions": [
{
"id": "h4",
"text": "With the cortex backend forced to 5xx or made unreachable, a request naming the cortex model NEVER reaches the Gemma backend, and the client receives 503 + Retry-After \u2014 reproducible in a unit test with a fake fleet, not just observed in production logs.",
"status": "confirmed"
}
],
"hard_questions": [],
"links": []
},
{
"id": "c5",
"kind": "why_it_matters",
"text": "A single unlucky turn kills a whole agent loop. A terminal 404 tells a well-behaved client the model will NEVER exist, so colleague work items abort after N steps with status=error and no output \u2014 while the model is listed in /v1/models before and after. And a lobes-discovered senses endpoint degrades instantly (~0.002s per call) because the advertised origin is dead. This has now regressed three times across #87 -> #90 -> #92: the shape keeps breaking because no test ever dials the deployed artifact.",
"origin": "user",
"status": "confirmed",
"honesty_conditions": [
{
"id": "h5",
"text": "A multi-step client loop survives an owner-backend fault: the turn that would previously have died on a terminal 404 instead receives a retryable status, and a client with ordinary backoff completes the run.",
"status": "confirmed"
}
],
"hard_questions": [],
"links": []
},
{
"id": "c6",
"kind": "after_state",
"text": "Every advertised capability is proven reachable before it is advertised, and every fleet fault surfaces as a retryable status. /capabilities advertises an origin derived from configured truth (never the gateway's internal listen port) and a ready flag backed by a live backend probe. /v1/models lists only backends that actually answer. A request naming a model that /v1/models lists never receives a 404 'does not exist' from the same origin \u2014 an owner that is dead yields 503 + Retry-After. The CLI and the gateway agree on the endpoint byte-for-byte. And an executable verb dials every advertised role endpoint+path so 'advertised implies reachable' is a test, not a promise.",
"origin": "user",
"status": "confirmed",
"honesty_conditions": [
{
"id": "h6",
"text": "Every claim in the after_state is asserted by an executable check, and each of those checks FAILS when run against today's HEAD + today's deployed rig.",
"status": "confirmed"
}
],
"hard_questions": [],
"links": []
},
{
"id": "c7",
"kind": "before_state",
"text": "The same disease reaches the roles themselves. 'senses' advertises responsibilities=[intake, normalize_input, ...] and the catalog claims image+text and audio+text, but every probe lobes owns is text-only: roles_measure.py:183 sends 'Write a short paragraph.' to senses, and assess.py builds no multimodal content parts at all. The image/audio probes exist only in tests/test_smoke_duo.py:287-347, skipped unless LOBES_SMOKE_BASE_URL is set, with no recorded run. The image+text/audio+text evidence in catalog.py:282 belongs to the sakamakismile coder checkpoint; docs/gemma-4-12b-nvfp4.md:279 states outright that those checks were 'not independently re-run against the base [coolthor] checkpoint specifically'. coolthor became the default in 0.35.0 on MTP decode throughput \u2014 orthogonal to the image+audio precondition #74 set.",
"origin": "user",
"status": "confirmed",
"honesty_conditions": [
{
"id": "h7",
"text": "lobes' own tooling sends a real image and a real audio clip to the senses role and verifies the answer against known ground truth (not merely that the response was 200 with non-empty content), and the recorded evidence names the coolthor checkpoint.",
"status": "confirmed"
}
],
"hard_questions": [],
"links": []
},
{
"id": "c8",
"kind": "boundary",
"text": "NOT a service mesh, a load balancer, or a health-check-per-request proxy: the gateway stays a stdlib reverse proxy with a bounded, cached readiness signal \u2014 no per-request upstream probe on the hot path. NOT a claim about answer quality: lobes keeps emitting runtime metrics only, never task-quality claims (the #81 h15 honesty condition still holds). NOT a rewrite of the role vocabulary, the tier aliases, or the compose topology. NOT an attempt to keep cortex alive through EngineDeadError \u2014 recovering the vLLM engine is out of scope; only the gateway's HONESTY about a dead engine is in scope.",
"origin": "user",
"status": "confirmed",
"honesty_conditions": [
{
"id": "h8",
"text": "The gateway's POST hot path opens no additional upstream probe connection per request: the readiness signal is read from a cache, exactly as pressure already is.",
"status": "confirmed"
}
],
"hard_questions": [],
"links": []
},
{
"id": "c9",
"kind": "non_goal",
"text": "Not adding retry/backoff logic inside the gateway on the client's behalf. The gateway's job is to return an honest, correctly-classified status (503 + Retry-After) and let the client's own backoff policy decide. Queuing a request until a backend finishes warming (issue #91's option 2) is explicitly NOT chosen.",
"origin": "user",
"status": "confirmed",
"honesty_conditions": [],
"hard_questions": [],
"links": []
},
{
"id": "c10",
"kind": "success_signal",
"text": "Five falsifiable checks, each runnable: (1) curl GET :8001/capabilities | jq '.[].endpoint' returns the SAME origin as 'lobes capabilities --json' for the same deployment, and that origin answers 200 on its advertised path. (2) With the cortex container stopped, POST /v1/chat/completions model=<cortex id> returns 503 + Retry-After \u2014 never 404, never a Gemma answer. (3) GET /v1/models lists exactly the models whose backends answer. (4) 'lobes doctor' (or the new verify verb) exits non-zero when the deployed gateway's lobes version differs from the CLI's. (5) The live smoke layer posts a real image and a real audio clip at coolthor and records the pass in docs, closing #74 and #69's criterion 7 with evidence rather than assertion.",
"origin": "user",
"status": "confirmed",
"honesty_conditions": [
{
"id": "h9",
"text": "Each of the five success checks is a command a reviewer can paste and watch fail on main, then pass on the branch.",
"status": "confirmed"
}
],
"hard_questions": [],
"links": []
},
{
"id": "c11",
"kind": "requirement",
"text": "The gateway advertises an origin from CONFIGURED TRUTH, never inference: the fleet compose injects GATEWAY_PUBLIC_URL (derived from the published VLLM_PORT) into the gateway container, and reachable_origin prefers it over the request Host header. When neither configured truth nor a Host header is available the gateway MUST NOT fabricate an absolute URL from its internal listen port.",
"origin": "llm",
"status": "rejected",
"honesty_conditions": [
{
"id": "h10",
"text": "With GATEWAY_PUBLIC_URL unset AND the request carrying no Host header, the gateway emits an endpoint that is either absent/empty or explicitly relative \u2014 never an absolute URL built from GATEWAY_PORT.",
"status": "rejected"
}
],
"hard_questions": [],
"links": []
},
{
"id": "c12",
"kind": "requirement",
"text": "A model id returned by GET /v1/models never receives a 404 'model does not exist' from POST /v1/chat/completions on the same origin. This is the single invariant issue #91 asks for, stated as a test.",
"origin": "llm",
"status": "confirmed",
"honesty_conditions": [
{
"id": "h11",
"text": "A test enumerates GET /v1/models and, for each id, POSTs a minimal completion and asserts the status is never 404 \u2014 run against a fake fleet where the owner backend is dead.",
"status": "rejected"
},
{
"id": "h23",
"text": "The listed-never-404 invariant is tested at the RACE, not at rest: a fake fleet lists model M, the owner is then killed, and a completion naming M returns 503 + Retry-After \u2014 never 404. A second test asserts the converse: an id that was NEVER in /v1/models does not silently get served by the default backend under a different model's weights.",
"status": "confirmed"
}
],
"hard_questions": [],
"links": []
},
{
"id": "c13",
"kind": "requirement",
"text": "Failover never forwards a request to a backend that does not serve the resolved model. The failover equivalence class is the SERVED MODEL, not the task family. Cross-backend retry stays legal only when the caller addressed a capability TIER ALIAS (main/minor/multimodal/cheap/normal/hard), in which case the body is re-resolved and re-rewritten per attempt rather than once before the loop.",
"origin": "llm",
"status": "rejected",
"honesty_conditions": [
{
"id": "h12",
"text": "order_backends(table, <explicit model id>) returns at most the backends that serve that exact model; the existing test_order_backends_generate_still_failovers_between_generate_backends is INVERTED to assert the new contract, and a tier-alias request re-rewrites the body per attempt.",
"status": "rejected"
}
],
"hard_questions": [],
"links": []
},
{
"id": "c14",
"kind": "requirement",
"text": "When the owner backend of a requested model is dead, unreachable, or warming, and no legal failover exists, the gateway returns 503 + Retry-After with a distinguishable error type (e.g. type=backend_unavailable), never 404 and never 502-as-terminal.",
"origin": "llm",
"status": "confirmed",
"honesty_conditions": [
{
"id": "h13",
"text": "A dead owner yields status 503 with a Retry-After header and an error type distinguishable from both 'model unknown' and 'all backends down', asserted in a unit test.",
"status": "confirmed"
}
],
"hard_questions": [],
"links": []
},
{
"id": "c15",
"kind": "requirement",
"text": "GET /v1/models and GET /capabilities reflect a bounded, cached LIVE readiness signal, not a config fact. RoleInfo.ready stops being an alias of loaded for the four gateway-fronted roles, exactly as it already stopped being one for stt/tts in #89. Phantom backends (wired by *_SERVED_NAME with no running container) never appear as ready.",
"origin": "llm",
"status": "confirmed",
"honesty_conditions": [
{
"id": "h14",
"text": "A backend wired in config but with no listening container reports ready=false and is absent from GET /v1/models, without the gateway probing on the request hot path.",
"status": "confirmed"
}
],
"hard_questions": [],
"links": []
},
{
"id": "c16",
"kind": "requirement",
"text": "'advertised implies reachable' becomes executable: a read-only verb dials every role's advertised endpoint+path and reports pass/fail per role, and it is the thing CI and the operator run. It also compares the deployed gateway container's lobes.__version__ against the CLI's and fails on skew \u2014 the defect that made #92 look like a code regression when it was a stale 0.36.0 image.",
"origin": "llm",
"status": "rejected",
"honesty_conditions": [
{
"id": "h15",
"text": "The verb exits non-zero on the CURRENT live rig (stale 0.36.0 gateway, :8000 endpoint, 404ing audio path) and exits zero after the fix is deployed.",
"status": "rejected"
}
],
"hard_questions": [],
"links": []
},
{
"id": "c17",
"kind": "requirement",
"text": "The multimodal role is proven multimodal by lobes' own tooling: the live smoke layer (tests/test_smoke_duo.py) is executed against coolthor on the DGX Spark and its image+text and audio+text results are recorded in docs/gemma-4-12b-nvfp4.md, replacing the standing admission at line 279 that those checks were never re-run against the base checkpoint. This closes #74's unchecked box and #69's criterion 7 with evidence instead of assertion.",
"origin": "llm",
"status": "confirmed",
"honesty_conditions": [
{
"id": "h16",
"text": "docs/gemma-4-12b-nvfp4.md no longer contains the admission that the content-correctness checks 'were not independently re-run against the base checkpoint specifically', because they were re-run and recorded.",
"status": "confirmed"
}
],
"hard_questions": [],
"links": []
},
{
"id": "c18",
"kind": "before_state",
"text": "NEITHER surface is authoritative, and they are wrong in OPPOSITE directions. For the generate roles the gateway JSON is wrong (endpoint = its internal :8000) and the CLI is right (:8001 from .env VLLM_PORT). For the audio roles the CLI is wrong (ready=true, loaded=true, endpoint=:8001/v1/audio/speech \u2014 read from .env AUDIO_URL) and the gateway is right (ready=false, loaded=false, endpoint=''). Dialing the CLI-advertised tts path returns 404 'audio endpoints are not configured on this deployment'. Root cause: AUDIO_URL reaches the gateway ONLY via docker-compose.audio.yml:144, so whether stt/tts work depends on whether the operator remembered the overlay -f flag \u2014 while the CLI reads the merged .env and assumes wired. This is #92's exact shape (ready=true on a 404 path) for the audio roles, it is present in main and in the packaged template, and no issue tracks it.",
"origin": "user",
"status": "confirmed",
"honesty_conditions": [
{
"id": "h19",
"text": "A test asserts that 'lobes capabilities' and GET /capabilities agree on ready/loaded/endpoint for ALL SIX roles \u2014 including stt/tts on a deployment where the audio overlay is not composed in \u2014 and that the audio roles never report ready=true while their advertised path returns 404.",
"status": "confirmed"
}
],
"hard_questions": [],
"links": []
},
{
"id": "c19",
"kind": "before_state",
"text": "GET /v1/models advertises phantom backends. _optional_backend wires a backend when *_BASE_URL OR *_SERVED_NAME is set, and falls back to a default_url naming a container that need not exist. On the live rig that yields 6 advertised models against 4 running vLLM containers: 'multimodal-coder' (http://vllm-multimodal-coder:8000) and 'middle' (http://vllm-middle:8000) have no container at all, yet they are listed to clients AND sit in the generate failover chain, which for the cortex model is [primary, multimodal, multimodal-coder, middle].",
"origin": "user",
"status": "confirmed",
"honesty_conditions": [
{
"id": "h20",
"text": "On a deployment where MULTIMODAL_CODER_SERVED_NAME and MIDDLE_SERVED_NAME are set but their containers are absent, GET /v1/models returns exactly the ids whose backends answer, and those absent backends never appear in any failover chain.",
"status": "rejected"
},
{
"id": "h24",
"text": "With MULTIMODAL_CODER_SERVED_NAME and MIDDLE_SERVED_NAME set but no BASE_URL and no container, build_config wires NEITHER backend, GET /v1/models returns exactly the four running ids, and resolve_model('nvidia/Qwen3-14B-NVFP4') does not resolve to the primary's weights.",
"status": "confirmed"
}
],
"hard_questions": [],
"links": []
},
{
"id": "c20",
"kind": "assumption",
"text": "The vLLM engine crash that triggers all this (vllm.v1.engine.exceptions.EngineDeadError on the cortex container, RestartCount=3) is a separate fault whose ROOT CAUSE is out of scope here. This spec makes lobes honest about a dead engine; it does not attempt to keep the engine alive. If EngineDeadError turns out to be caused by lobes' own serve flags (MTP + max_num_seqs), that is a distinct issue.",
"origin": "llm",
"status": "confirmed",
"honesty_conditions": [],
"hard_questions": [],
"links": []
},
{
"id": "c21",
"kind": "decision",
"text": "The unifying repair is a single invariant with three enforcement points: (1) ONE origin resolver \u2014 configured truth (GATEWAY_PUBLIC_URL, injected from the published VLLM_PORT) beats Host header beats nothing-at-all; the internal listen port is never advertised. (2) ONE readiness source \u2014 a bounded cached probe, shared by /v1/models, /capabilities.ready and the failover order, so 'ready' means 'answered recently'. (3) ONE executable check \u2014 a read-only verb that dials every advertised endpoint+path and the deployed gateway's version, run by CI and the operator.",
"origin": "llm",
"status": "rejected",
"honesty_conditions": [],
"hard_questions": [],
"links": []
},
{
"id": "c22",
"kind": "requirement",
"text": "A LOCAL live-test of the capabilities contract runs before every PR. It is not a CI job (CI has no GPU and no fleet); it is a local gate the developer runs against the running deployment, in the same live-gated seam tests/test_smoke_duo.py already uses (LOBES_SMOKE_BASE_URL). It must fail loudly rather than skip silently when the operator intended to run it, so that 'advertised implies reachable' is checked against the DEPLOYED artifact before a PR claims to have fixed it \u2014 the exact step whose absence let #87 regress into #92.",
"origin": "user",
"status": "confirmed",
"honesty_conditions": [
{
"id": "h17",
"text": "The local pre-PR live test, pointed at a deployment whose gateway is stale or whose audio overlay is unwired, FAILS \u2014 it does not skip, and it does not pass.",
"status": "confirmed"
},
{
"id": "h18",
"text": "The gate is ONE trigger and then unattended: a single local command runs the whole capabilities live-test to a pass/fail verdict with no prompts, no manual steps, and no per-role babysitting \u2014 the developer types it, walks away, and reads the exit code.",
"status": "confirmed"
}
],
"hard_questions": [],
"links": []
},
{
"id": "c23",
"kind": "requirement",
"text": "NO cross-backend failover. Every request resolves to exactly ONE backend and is attempted only there. An explicit model id goes to its owner; a capability tier alias resolves once, up front, via the existing static tier_aliases upward fallback (a tier whose gear is not WIRED still maps to a higher rung at table-build time). There is no runtime retry against a second backend, so a caller who asked for cortex can never silently receive a Gemma answer. A dead/unreachable/warming owner yields 503 + Retry-After. order_backends collapses to a single-element list and rewrite_model stays outside any loop.",
"origin": "user",
"status": "confirmed",
"honesty_conditions": [
{
"id": "h21",
"text": "A unit test with a fake fleet proves that a request naming the cortex model, with the primary backend dead, NEVER opens a connection to the multimodal backend, and returns 503 + Retry-After. order_backends(table, served) returns exactly one backend for every input.",
"status": "confirmed"
}
],
"hard_questions": [],
"links": []
},
{
"id": "c24",
"kind": "decision",
"text": "Readiness becomes a bounded BACKGROUND cached probe, mirroring the existing PressureCache: each backend's /health is polled on an interval off the request path. The one cached signal feeds GET /v1/models, GET /capabilities .ready, and the wiring/advertisement decisions. The POST hot path opens no probe connection.",
"origin": "user",
"status": "confirmed",
"honesty_conditions": [],
"hard_questions": [],
"links": []
},
{
"id": "c25",
"kind": "decision",
"text": "Phantom backends are stopped by BOTH gates: (a) a backend is wired only when its *_BASE_URL is set \u2014 the 'or *_SERVED_NAME' clause is dropped, matching the MINOR_BASE_URL convention the fleet already documents; and (b) GET /v1/models is filtered by the live readiness signal, so a wired-but-dead backend is not advertised.",
"origin": "user",
"status": "confirmed",
"honesty_conditions": [],
"hard_questions": [],
"links": []
},
{
"id": "c26",
"kind": "decision",
"text": "Issue #69's 'disabled-by-default DSpark experiment entry' is closed as ANSWERED-NEGATIVE: no catalog entry is shipped for deepseek-ai/dspark_gemma4_12b_block7, because #75 proved Gemma4DSparkModel does not load on vLLM 0.23. The repair is documentary \u2014 docs/gemma4-mtp-draft.md still presents DSpark as 'the ONE route task t3 should wire next' with no superseded banner and must carry the invalidity finding.",
"origin": "user",
"status": "confirmed",
"honesty_conditions": [],
"hard_questions": [],
"links": []
},
{
"id": "c27",
"kind": "requirement",
"text": "The pre-PR gate detects DEPLOYED-ARTIFACT skew, not just source correctness: it compares the running gateway container's lobes.__version__ against the CLI's and fails on mismatch, and it dials every advertised role endpoint+path. This is the check whose absence let #87's fix ship in 0.38.0 while the rig kept running 0.36.0 \u2014 making #92 look like a code regression when the code was already correct and merely undeployed.",
"origin": "user",
"status": "confirmed",
"honesty_conditions": [
{
"id": "h22",
"text": "Run against the rig as it stands right now (gateway 0.36.0, CLI 0.39.0, endpoint :8000, /v1/audio/speech 404ing), the gate exits non-zero and names all three faults; after redeploy it exits zero.",
"status": "confirmed"
}
],
"hard_questions": [],
"links": []
},
{
"id": "c28",
"kind": "decision",
"text": "The unifying repair is one invariant with three enforcement points: (1) ONE origin resolver \u2014 configured truth (GATEWAY_PUBLIC_URL, injected from the published VLLM_PORT) beats the request Host header beats nothing; the internal listen port is never advertised. (2) ONE readiness source \u2014 a background cached probe shared by /v1/models and /capabilities.ready. (3) ONE executable check \u2014 a LOCAL, single-trigger, unattended pre-PR live test that dials every advertised endpoint+path and the deployed gateway's version.",
"origin": "user",
"status": "confirmed",
"honesty_conditions": [],
"hard_questions": [],
"links": []
},
{
"id": "c29",
"kind": "requirement",
"text": "The gateway resolves its advertised origin as: an explicit operator override (GATEWAY_PUBLIC_URL \u2014 for a tunnel or a Host-rewriting reverse proxy) FIRST; else the origin the client actually dialed, echoed from the request Host header; else NOTHING \u2014 an empty endpoint. It must never fabricate an absolute URL from its internal listen port (GATEWAY_PORT), and it must never default GATEWAY_PUBLIC_URL to a localhost URL, because a defaulted public_url outranks the Host header and would tell every LAN/tunnel client to dial its own loopback \u2014 reintroducing the #92 defect in a new place. Each caller therefore receives an origin correct for itself. The base fleet compose still passes AUDIO_URL (empty by default; the audio overlay supplies the real value), which is the #96 half of this change.",
"origin": "user",
"status": "confirmed",
"honesty_conditions": [
{
"id": "h25",
"text": "With GATEWAY_PUBLIC_URL unset and no Host header, the gateway emits an empty endpoint \u2014 never an absolute URL built from GATEWAY_PORT. With GATEWAY_PUBLIC_URL unset and Host='spark.local:8001', it advertises http://spark.local:8001, not http://localhost:8001. With GATEWAY_PUBLIC_URL set, it wins over any Host header.",
"status": "confirmed"
}
],
"hard_questions": [],
"links": []
}
],
"open_vagueness": [
{
"id": "v1",
"text": "Should the gateway's cached readiness probe be a background thread (like PressureCache) or a lazy TTL-cached probe on the /v1/models + /capabilities read path? Background costs a thread and probes when idle; lazy adds latency to the first read after a TTL expiry. Both keep the hot POST path probe-free.",
"kind": "unknown_nonblocking",
"claim_id": null
},
{
"id": "v2",
"text": "Whether requiring *_BASE_URL to wire a backend (dropping the 'OR *_SERVED_NAME' clause) breaks any existing deployment that set only the served-name var. The fleet template always sets both, but a hand-edited .env might not.",
"kind": "unknown_nonblocking",
"claim_id": null
},
{
"id": "v3",
"text": "RESOLVED by user decision (see the DSpark decision claim): close #69's disabled-entry criterion as answered-negative and correct docs/gemma4-mtp-draft.md, which still presents DSpark as the next route to wire. Follow-up: revisit if a vLLM lands that registers Gemma4DSparkModel.",
"kind": "follow_up",
"claim_id": null
}
]
}