asgi.py
Rama main · réplica de lectura del repositorio original.
"""ASGI entry point — the target for any server that must serve /peer_link.
WHY THIS EXISTS
`/peer_link` is a WebSocket. A server handed the plain WSGI `app`
(`hart_intelligence_entry:app`) can never serve it, because WSGI has no
websocket scope. That is how the waitress systemd unit and the
gunicorn/gevent cloud image both ended up with PeerLink.accept() never
firing and PeerLinkManager._links permanently empty.
Pointing those servers here instead gives them the same ASGI stack that
`_serve_app` builds -- `peer_link_asgi(AsyncioWSGIMiddleware(app))` via the
one canonical `core.serve.build_asgi_app` -- WITHOUT running `main()`.
That distinction is the point: `main()` also does boot integrity
verification, guardrail hash enforcement (which can refuse to boot),
EventBus bootstrap, Crossbar subscribers, the skill registry and the agent
daemon supervisor. Those belong to the full-node launcher, not to "serve
HTTP + websockets", and turning them on is a separate decision.
HOW TO USE IT
hypercorn: hypercorn asgi:application --bind 0.0.0.0:6777
uvicorn: uvicorn asgi:application --host 0.0.0.0 --port 6777
gunicorn: gunicorn -k uvicorn.workers.UvicornWorker asgi:application
Do NOT point a pure-WSGI server (waitress, gunicorn's default sync or
plain-gevent workers) at `hart_intelligence_entry:app` if the node is
expected to accept peers -- it will serve HTTP correctly and drop every
websocket, logging nothing.
WHY IMPORTING THIS IS ALLOWED TO FAIL
`build_asgi_app` imports hypercorn's middleware. If that is missing, this
module raises at import and the server refuses to start. That is
deliberate: the alternative is `_serve_app`'s ImportError fallback to
Waitress, which starts fine and silently has no `/peer_link`. A server that
will not boot is a better failure than a peer that is invisible to the hive.
See core/peer_link/server.py and the requirements.txt note on hypercorn's
subtree (the root Dockerfile installs with --no-deps).
EXECUTOR NOTE
`_serve_app` installs a ThreadPoolExecutor sized by HEVOLVE_WORKER_THREADS
(default 256) as the loop's default executor; the bare server CLIs do not,
so sync Flask handlers run on asyncio's default executor
(min(32, cpu_count + 4)). For a regional/central node that is a different
concurrency profile from waitress's threads=50, not a strictly smaller one.
If a deployment needs the larger pool, launch through
`python hart_intelligence_entry.py` instead, which runs `_serve_app`.
"""
from core.serve import build_asgi_app
# TID251 bans importing hart_intelligence_entry so that WORKER code cannot
# reach around the singleton accessor (core.safe_hartos_attr.safe_hartos_attr).
# This module is not worker code: it is the server target, and its whole job is
# to hand one specific Flask app to a server. gunicorn already does exactly
# this as `hart_intelligence_entry:app` -- ruff never saw that because it is a
# config string, not an import. safe_hartos_attr is the wrong tool here: it
# tolerates the module being absent, whereas a server pointed at this file must
# either get the real routed app or fail to start.
from hart_intelligence_entry import app # noqa: TID251
# The name servers target. Built once at import, against a fully-routed app:
# every register_blueprint()/init_social() call in hart_intelligence_entry runs
# at module level (lines ~1016-1177), well before this import completes, which
# is the same reason `hart_intelligence_entry:app` works for gunicorn today.
application = build_asgi_app(app)
# Alias — uvicorn/gunicorn examples in the wild use either name.
asgi_app = application
__all__ = ['application', 'asgi_app']