code wiki / _hdl_build / nx_survey_daemon.nx
nx_survey_daemon.nx
buildroot/runtime/_hdl_build/nx_survey_daemon.nx
about
nx_survey_daemon.nx -- the DEPLOYABLE Nishi Pulse survey/poll daemon (ops shell around the gated pure
core nx_survey_serve). Serves 0.0.0.0:8031; CWD = nishihost on the NAS. Mounted at nishifamily.com/survey
via a proxy_routes.conf row (the /office precedent). Store = seg_store prefix knowledge/survey/pulse_
(self-healed on startup: the relate-daemon lesson -- root-owned knowledge/ may lack the subdir).
Admin lane reads survey_admin.key from CWD per request (FAIL-CLOSED when absent).
Build with --build-only; run deliberately. license_tier: ORIGINAL
---- TRAFFIC-SAFETY ADOPTION 2026-08-21: THIS DAEMON IS THE FIRST ADOPTER OF TS1 AND TS2. ----
/compare/trafficsafety measured the deficit as ADOPTION, not capability: ts_handoff_nodrop was proven
on 2026-08-21 and NO serving daemon called it, and rt_sigaction resolved 8 times with ZERO of them a
TERM drain. This file closes both for one real, low-blast-radius serving daemon.
WHY THIS DAEMON WAS CHOSEN, from the route table rather than from taste. proxy_routes.conf carries
exactly ONE row for this backend (nishifamily.com /survey 8031 buffered) against 69 rows total, it owns
its binary outright (unlike nx_office_daemon, where one binary serves two registry rows and a deploy
restarts both), and it fronts no control-plane or authentication path. It is deliberately NOT sites.elf:
that front door is what all 69 rows cross and replacing it is rung TS3's problem, not a first adopter's.
(1) TS1 -- KEEP THE SOCKET, REPLACE THE PROCESS. The listener now comes from ts_handoff_nodrop instead
of a private bind. When an owner outside this process is publishing on the rendezvous path, the
listening socket is INHERITED and never released, so a restart has no instant at which zero
processes hold the port and an arrival mid-swap queues instead of being refused. When no owner is
publishing it binds normally, so adopting this CANNOT make a cold start fail -- that fallback is the
whole reason this is safe to land on a live daemon.
IT ALSO REMOVES TWO LATENT HAZARDS THIS FILE CARRIED, because the shared primitive owns them:
the old hand-rolled bind set SO_REUSEADDR but NOT FD_CLOEXEC (the port-hostage class that took the
mgmt API down for every seat) and never ignored SIGPIPE (a client walking away mid-response takes
SIGPIPE's default action, TERMINATE). nx_http_server_listen does all three by construction.
(2) ENVOY'S ORDERING RULE, WHICH IS THE DIRECT FIX FOR OUR OWN 8-MINUTE BLACKOUT. Every expensive step
-- the three request/response arenas and the seg-store warm, which reads real store files off a
shared array -- now happens BEFORE the listener is asked for, while the OUTGOING generation is still
serving. The blackout happened because a registry seed holding a process-lifetime lock ran BEFORE
the bind, so the incoming instance never reached accept() at all: a handoff placed after a blocking
call that the outgoing process gates can never run. Ordering is the entire mitigation and it is free.
the ordering is load-bearing, not cosmetic: moving the warm back below the acquire re-creates the
exact defect, because the warm's cost is set by the busiest thing on the box and not by this code.
(3) TS2 -- COOPERATIVE DRAIN ON TERM. Armed FIRST, before the listener exists, because a daemon that
arms the drain only once it is serving has a window in which TERM still kills it outright, and that
dependencies 3 imports · 0 importers
imports: nx_survey_serve.nxnx_http_server.nxnx_ts_drain_lib.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 54 | const SD_PORT: i64 = 0x1f5f // 8031 |
| 55 | const SD_ADDR_BYTES: i64 = 16 |
| 56 | const SD_MODE_0755: i64 = 493 |
| 57 | const SD_REQ_CAP: i64 = 262144 |
| 58 | const SD_RES_CAP: i64 = 1048576 |
| 59 | const SD_BACKLOG: i64 = 16 |
| 60 | const SD_OUT_BYTES: i64 = 64 |
| 61 | const SD_NUM_SCRATCH: i64 = 24 |
| 62 | const SD_ASCII_0: i64 = 48 |
| 63 | const SD_ASCII_9: i64 = 57 |
| 64 | const SD_B10: i64 = 10 |
| 65 | const SD_ARGC_PORT: i64 = 2 |
| 66 | const SD_ARGC_SOCK: i64 = 3 |
| 67 | const SD_ARGV_PORT: i64 = 1 |
| 68 | const SD_ARGV_SOCK: i64 = 2 |
| 72 | const SD_SA_FAMILY_INET: i64 = 2 |
| 73 | const SD_SA_PORT_OFF: i64 = 2 |
| 74 | const SD_BITS_PER_BYTE: i64 = 8 |
| 75 | const SD_BYTE_MASK: i64 = 0xff |
| 79 | const SD_HANDOFF_SOCK: *u8 = "knowledge/status/survey_handoff.sock" as *u8 |
functions
| 81 | func sd_addr(out: *u8, port: i64) -> i64 called by 1: main |
| 90 | func sd_putn(v: i64) -> i64 called by 1: main |
| 102 | func main(argc: i64, argv: **u8) -> i64 |