The ingest endpoint exists; I reported it missing on guessed paths

T04 said MISSING_INGEST_ENDPOINT. It was wrong about the codebase and right
about the running system, and both halves were avoidable.

The route is POST /api/node/audio/import, on agent_router mounted at /api/node,
guarded by node.require("dataset:ingest"), backed by audio_import.py and
migration 0008_audio_source_import.sql. I found it by reading app.py's
include_router calls - which I had already grepped earlier in this same session
and which printed `include_router(node_router, prefix="/api/node")` on screen
while I was writing the test that declared the path missing.

Two failures, not one:

* I probed six guessed paths when I held SSH access to the source that names the
  router prefixes. A 404 to a guessed path proves only that the guess was wrong.
* I probed with GET only. The route is POST-only, so it answers GET with 404 and
  POST with 405. That is why it looked absent for a whole session even to a
  careful prober.

Both are fixed. T04 probes GET and POST and treats 405 as routed, because 405
means the path is in the table under another method. The failure case is now
UNCONFIRMED_INGEST_ENDPOINT rather than MISSING, and carries a note saying to
confirm against the route table rather than against its 404s. A new verdict,
INGEST_ROUTED_NEEDS_RESTART, distinguishes "routed but not serving" from
"absent".

Why it is routed but not serving: the process on :8442 started 2026-10-14 20:06.
audio_import.py was written 2026-10-15 11:15 and routes_compute.py 12:35, all
uncommitted. /health has been green throughout, because it probes liveness and
not the route table - so "healthy" was never evidence the new work was live.

Suite is 5 pass / 0 fail / 5 blocked now, verdict INGEST_ROUTED_NEEDS_RESTART.
48 tests pass. Nothing pushed.
This commit is contained in:
B.D. Murphy
2026-10-15 13:22:57 -07:00
parent 4c1e6ebc98
commit d2a944ef84

View File

@@ -42,12 +42,19 @@ from typing import Any
DEFAULT_BASE_URL = os.environ.get("UMBRA_AUDIO_CONTROLLER_URL", "http://192.168.123.14:8442")
INGEST_CANDIDATES = (
# Confirmed from the controller's own route table: agent_router, mounted at
# /api/node, exposes POST /audio/import. This path was MISSING from this list
# originally, and the report said MISSING_INGEST_ENDPOINT on the strength of
# guessed paths while the route table was one SSH command away. Guessing is
# not discovery; see t04_ingest_exists.
"/api/node/audio/import",
"/api/audio/ingest",
"/api/projects/import/audio",
"/api/import/audio",
"/api/audio/dataset",
"/api/projects/audio-dataset",
"/api/manifest/ingest",
"/api/compute/audio/import",
)
PROBE_PATHS = (
@@ -242,18 +249,50 @@ def t03_version(client: Client) -> Result:
def t04_ingest_exists(client: Client) -> Result:
probed: dict[str, int] = {}
"""Probe every candidate with both GET and POST.
Probing only with GET is how a POST-only ingest route looked absent for a
whole session: the real path is `POST /api/node/audio/import`, which answers
GET with 404 and POST with 405, so a GET-only sweep concluded it was not
there. A 404 to the wrong method is not evidence of absence.
"""
probed: dict[str, dict[str, int]] = {}
routed: tuple[str, int] | None = None
for path in INGEST_CANDIDATES:
status, _, _ = client.request("GET", path)
probed[path] = status
if status in (200, 405):
return Result("T04", "Ingest endpoint exists", "PASS",
f"{path} exists ({status})", {"probed": probed})
results = {}
for method in ("GET", "POST"):
status, _, _ = client.request(
method, path, body={} if method == "POST" else None)
results[method] = status
probed[path] = results
for method, status in results.items():
# 405 means the path is routed with a different method: it exists.
if status in (200, 201, 405) and routed is None:
routed = (f"{method} {path}", status)
if routed is not None:
which, status = routed
return Result("T04", "Ingest endpoint exists", "PASS",
f"{which} is routed ({status}). 405 means the path exists "
f"under another method, which is a route being present.",
{"probed": probed, "routed": which, "status": status,
"reachable": status in (200, 201),
"note": ("Routed is not the same as reachable. A route can "
"be registered and still be refused for want of a "
"credential; 401/403 on a real path is progress, "
"not failure.")})
return Result("T04", "Ingest endpoint exists", "FAIL",
"MISSING_INGEST_ENDPOINT: no bulk audio-dataset ingest path "
"exists on the controller. This blocks a push, not the "
"metadata handover, which is file-based by design.",
{"probed": probed, "finding": "MISSING_INGEST_ENDPOINT"})
"no ingest path responded to GET or POST. NOTE: this is probed "
"paths only, not the controller's route table. Before treating "
"this as absence, enumerate the routes the controller actually "
"registers - include_router(...) in its app module - because a "
"guessed path that 404s proves only that the guess was wrong. "
"That mistake was made here once.",
{"probed": probed, "finding": "UNCONFIRMED_INGEST_ENDPOINT",
"how_to_confirm": "read the controller's router registrations, "
"not its 404s"})
def t05_dry_run(client: Client) -> Result:
@@ -302,10 +341,9 @@ def t09_error_handling(client: Client) -> Result:
"""
status, body, _ = client.request("DELETE", "/api/health")
envelope = body.get("error") if isinstance(body, dict) else None
has_code = bool(isinstance(envelope, dict) and envelope.get("code"))
has_message = bool(isinstance(envelope, dict) and envelope.get("message"))
structured = has_code and has_message
raw = body.get("error") if isinstance(body, dict) else None
envelope: dict[str, Any] = raw if isinstance(raw, dict) else {}
structured = bool(envelope.get("code")) and bool(envelope.get("message"))
if structured:
detail = (f"error envelope carries code={envelope['code']!r} and an "
@@ -490,11 +528,14 @@ def main() -> int:
failed = [r for r in results if r.status == "FAIL"]
blocked = [r for r in results if r.status == "BLOCKED"]
missing_ingest = any(r.evidence.get("finding") == "MISSING_INGEST_ENDPOINT" for r in results)
missing_ingest = any(r.evidence.get("finding") == "UNCONFIRMED_INGEST_ENDPOINT"
for r in results)
routed_ingest = next((r for r in results if r.test_id == "T04"
and r.evidence.get("routed")), None)
transport = "plain HTTP (uvicorn); no TLS on this port" \
if client.base_url.startswith("http") else "HTTPS"
report = {
report: dict[str, Any] = {
"report_version": 1,
"generated_at": now_iso(),
"source_system": {"system": "umbraaudio", "node_id": "umbraaudio-m4"},
@@ -521,13 +562,28 @@ def main() -> int:
"passing": [r.test_id for r in passing],
"failing": [r.test_id for r in failed],
"blocked_ids": [r.test_id for r in blocked],
"blocking_findings": ["MISSING_INGEST_ENDPOINT"] if missing_ingest else [],
"blocking_findings": ["UNCONFIRMED_INGEST_ENDPOINT"] if missing_ingest else [],
},
"readiness": {
"verdict": "READY_FOR_METADATA_HANDOVER_NOT_READY_FOR_PUSH",
"rationale": [
"The controller has no bulk ingest endpoint, so a dataset cannot "
"be pushed to it; nothing was pushed and nothing should be.",
"verdict": ("INGEST_ROUTED_NEEDS_RESTART" if routed_ingest
and not routed_ingest.evidence.get("reachable")
else "READY_FOR_METADATA_HANDOVER_NOT_READY_FOR_PUSH"),
"rationale": (
[
f"The ingest route {routed_ingest.evidence['routed']} is "
"registered but not reachable. Nothing was pushed.",
"A route that answers 405 is present in the table and absent "
"from the running process, which means the serving process "
"predates the code that defines it. /health stays green "
"throughout, because it probes liveness and not the route "
"table.",
] if routed_ingest and not routed_ingest.evidence.get("reachable") else [
"No ingest path responded to GET or POST, so a dataset cannot "
"be pushed; nothing was pushed. This is UNCONFIRMED: confirm "
"against the controller's route table before treating it as "
"absent. A 404 to a guessed path is not evidence of absence.",
]
) + [
"The controller's own request is for a metadata bundle, validated "
"by its own validator, handed over as a file. That is done.",
"Its request also says the accepted manifest format comes after "