nx_survey_handoff_gate.nx
buildroot/runtime/nx_survey_handoff_gate.nx
about
nx_survey_handoff_gate.nx -- TS1 ADOPTION: THE REAL DAEMON, RESTARTED FOR REAL, ZERO DROPS.
/compare/trafficsafety rung TS1. nx_ts_handoff_gate already proved the MECHANISM on a synthetic
server. This gate proves the ADOPTION, which is the half that was open: it fork+execs the ACTUAL
nx_survey_daemon binary, restarts it for real, and measures what a client saw. Nothing here checks
that the source references a symbol -- a reference is not an adoption, and the estate has a long
record of capabilities that were referenced and never reached.
ACCEPT RULE, taken VERBATIM from trafficsafety.plan and not re-invented here: "a swap under a
synthetic request stream drops ZERO connections, measured by a client that counts refusals and
resets, not by the daemon's own log. The neg-control is mandatory -- the same harness against the
CURRENT ordinary-listener path must show a non-zero drop, or the test proves nothing."
THE TWO ARMS DIFFER IN EXACTLY ONE THING: whether an owner outside the daemon is publishing the
listening socket. Same binary, same argv shape, same client, same request count. In the ARMED arm
an owner holds the listener and hands it to each generation, so ts_handoff_nodrop INHERITS it; in
the NEG-CONTROL there is no owner, so the very same call falls back to binding -- which is today's
behaviour for every other daemon in the estate, and is the disease state.
THE DECISIVE MOMENT IS THE ARRIVAL DURING THE GAP. Generation one is killed AND REAPED, so at that
instant ZERO daemon processes exist. A client connects right then. With an owner the handshake
completes into the kernel's accept queue and generation two serves it; without one there is nothing
bound and the arrival is refused with ECONNREFUSED. That difference is deterministic, which is what
makes this a gate and not a flaky benchmark.
IT ALSO EXERCISES ENVOY'S ORDERING RULE, WHICH IS WHY THE PUBLISH IS THE READINESS SIGNAL.
ts_handoff_publish blocks until a generation asks for the socket, and the adopted daemon asks only
AFTER it has finished every expensive step (its three arenas and the seg-store warm). So a publish
that returns is positive evidence that initialisation completed BEFORE the handoff -- the exact
ordering whose absence caused this estate's 8-minute blackout.
SAFETY: loopback only; every port is FOUND by a bind that would have failed had it been occupied,
because a probe port you did not verify free is not a control, it is a second instance. The live
:8031 survey daemon is NEVER touched -- the subject is given its own port on argv precisely so this
gate can drive a real restart without becoming a second instance of a running service. The subject
runs with CWD inside the gate's own /tmp fixture, so its store self-heal cannot write into a
production tree.
expect_exit: 0 license_tier: ORIGINAL No hw writes (Rule 26).
dependencies 4 imports · 0 importers
imports: nx_syscalls.nxnx_http_server.nxnx_gate_emit_lib.nxnx_gate_verdict.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
| 44 | const SH_NUM_SCRATCH: i64 = 24 |
| 45 | const SH_ASCII_ZERO: i64 = 48 |
| 46 | const SH_B10: i64 = 10 |
| 47 | const SH_ONE: i64 = 1 |
| 48 | const SH_SA_BYTES: i64 = 16 |
| 51 | const SH_SA_PORT_OFF: i64 = 2 |
| 52 | const SH_SA_ADDR_OFF: i64 = 4 |
| 53 | const SH_ADDR_BYTES: i64 = 4 |
| 54 | const SH_BYTE_RADIX: i64 = 256 |
| 55 | const SH_OUT_SLOTS: i64 = 12 |
| 56 | const SH_OUT_BYTES: i64 = 128 |
| 57 | const SH_LOOPBACK_A: i64 = 127 |
| 58 | const SH_LOOPBACK_D: i64 = 1 |
| 59 | const SH_PORT_BASE: i64 = 39500 |
| 60 | const SH_PORT_TRIES: i64 = 64 |
| 61 | const SH_PORT_NONE: i64 = 0 - 1 |
| 62 | const SH_TRUE: i64 = 1 |
| 63 | const SH_FALSE: i64 = 0 |
| 64 | const SH_PATHBUF: i64 = 256 |
| 65 | const SH_ARGV_BYTES: i64 = 64 |
| 66 | const SH_RESP_CAP: i64 = 4096 |
| 67 | const SH_SIG_TERM: i64 = 15 |
| 70 | const SH_RC_EXEC_FAILED: i64 = 127 |
| 71 | const SH_ARGC_SUBJECT: i64 = 2 |
| 73 | const SH_AV_PORT: i64 = 1 |
| 74 | const SH_AV_SOCK: i64 = 2 |
| 75 | const SH_AV_END: i64 = 3 |
| 77 | const SH_BOTH_GENERATIONS: i64 = 2 |
| 83 | const SH_REQS_PER_GEN: i64 = TS_RV_BACKLOG |
| 86 | const SH_DEADLINE_S: i64 = ACCEPT_TMO_S |
| 91 | const SH_READY_POLL_MS: i64 = 20 |
| 92 | const SH_READY_TRIES: i64 = (SH_DEADLINE_S * 1000) / SH_READY_POLL_MS |
| 94 | const SH_O_ATTEMPTED: i64 = 0 |
| 95 | const SH_O_SERVED: i64 = 1 |
| 96 | const SH_O_GAP_RC: i64 = 2 |
| 97 | const SH_O_GAP_DONE: i64 = 3 |
| 98 | const SH_O_PORT: i64 = 4 |
| 99 | const SH_O_G1READY: i64 = 5 |
| 100 | const SH_O_G2READY: i64 = 6 |
| 102 | const SH_DIR: *u8 = "/tmp/nx_survey_handoff_gate" as *u8 |
| 103 | const SH_SOCK_ARMED: *u8 = "/tmp/nx_survey_handoff_gate/armed.sock" as *u8 |
| 104 | const SH_SOCK_NEG: *u8 = "/tmp/nx_survey_handoff_gate/neg.sock" as *u8 |
| 105 | const SH_DEFAULT_DAEMON: *u8 = "./nx_survey_daemon.elf" as *u8 |
| 106 | const SH_REQ: *u8 = "GET /survey HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n" as *u8 |
functions
| 108 | func g_putn(v: i64) -> i64 |
| 119 | func gq(label: *u8, got: i64, want: i64, ctr: *i64) -> i64 |
| 131 | func sh_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } called by 1: sh_exchange |
| 133 | func sh_itoa(v: i64, out: *u8) -> i64 |
| 145 | func sh_sa(sa: *u8, port: i64) -> i64 |
| 158 | func sh_listen(port: i64, reuse: i64) -> i64 called by 2: sh_find_portsh_swap_run calls 7: sys_socketsys_mmapsys_setsockoptsh_sasys_bindsys_close+1 |
| 175 | func sh_find_port(from: i64) -> i64 |
| 186 | func sh_connect(port: i64) -> i64 called by 2: sh_drive_onesh_wait_ready calls 6: sys_mmapsh_sasys_socketsys_set_socket_timeoutsys_connectsys_close |
| 200 | func sh_exchange(fd: i64) -> i64 |
| 217 | func sh_drive_one(port: i64) -> i64 |
| 227 | func sh_spawn(daemon: *u8, port: i64, sockp: *u8) -> i64 |
| 247 | func sh_reap(pid: i64) -> i64 |
| 257 | func sh_wait_ready(port: i64) -> i64 |
| 271 | func sh_swap_run(armed: i64, daemon: *u8, port: i64, sockp: *u8, out: *i64) -> i64 called by 1: main calls 15: sys_unlinkatsys_mmapts_handoff_opensys_set_socket_timeoutsh_listensys_close+9 |
| 358 | func sh_report(tag: *u8, drops: i64, out: *i64) -> i64 |
| 371 | func main(argc: i64, argv: **u8) -> i64 |