# P6 — Config & DNS: per-cluster summary

One-line top-level summary per P6 cluster (detailed entries live in
[`docs/progress-log/p6.md`](docs/progress-log/p6.md)).

- **P6a DONE: `s_conf.c` TKLINE cluster** (`m_tkline`/`m_untkline`/`prep_kline`/`do_kline`/`wdhms2sec`/`tkline_expire` + the `tkconf` list) — the oper temporary-kill family ported to `ircd-common/s_conf.rs` via an `s_conf_link.o` partial port (`-DPORT_TKLINE_P6`; the rest of `s_conf.c`'s config grammar stays C). Handler cluster, not server-reachable (msgtab col 2/3; do_kline emits only local `SCH_NOTICE`→`&NOTICES` notices + reaps local clients). Gates: L1 (`s_conf_tkline_diff`: wdhms2sec corpus + tkconf add/dedup/expire lifecycle with inverses, highest_fd pinned -1) + L2 (`golden_s_conf_tkline`: oper TKLINE notice/UNTKLINE/bad-format, the two-client reap → 465+disconnect, non-oper 481 inverse). No S2S (no IsServer/remote-field/propagation path).
- **P6b DONE: `res.c` `m_dns`** (the resolver/cache debug command — the *last* command handler still in C) — ported to `ircd-common/res.rs` via a `res_link.o` partial port (`-DPORT_DNS_M_DNS`, de-static'ing `cachetop`/`cainfo`/`reinfo`); the resolver proper stays C. **Every `msgtab` handler is now Rust.** Handler cluster, msgtab col 2 (STAT_OPER) only + the `is_allowed(ACL_DNS)` O-line `d` gate. L2 (`golden_s_dns`, `dns_oper.conf`): oper `DNS` stats + `DNS l` empty cache dump, non-oper 481 inverse. No S2S.
- **P6c DONE: `s_conf.c` flags cluster** (the six pure config-flag converters `{iline,pline,oline}_flags_parse`/`_to_string`) — same `s_conf_link.o` partial port (`-DPORT_SCONF_FLAGS_P6`); first bite into the config *grammar*. Utility/callee (none in msgtab). L1 (`s_conf_flags_diff`: all six vs `cref_`) + L2 (`golden_s_conf_flags`, `flags_oper.conf`). No S2S.
- **P6d DONE: `s_conf.c` `match_ipmask`** (the CIDR/#IP-bitmask matcher — a pure leaf predicate over `cptr->ip`/`username`, used by Rust channel ban-matching + the TKLINE/conf-lookup paths) — same `s_conf_link.o` partial port (`-DPORT_SCONF_IPMASK_P6`). Utility/callee. L1 (`s_conf_match_ipmask_diff`: v4-mapped /32//24//0, j-boundary, user@host/cidr, badmask, native v6, each with its inverse) + L2 (`golden_s_conf_match_ipmask`: channel CIDR `+b`). No S2S.
- **P6e DONE: `s_conf.c` `find_admin`/`find_me`** (two leaf read-only walks of the global `conf` list for the `CONF_ADMIN`/`CONF_ME` entry) — same `s_conf_link.o` partial port (`-DPORT_SCONF_FINDADMIN_P6`). Utility/callee. L1 (`s_conf_find_admin_me_diff`: head/non-head/absent/combined-bits incl. inverses, both globals pointed at one list) + L2 (`golden_s_conf_find_admin`: the `ADMIN` command via Rust `m_admin`). No S2S.
- **P6f DONE: `s_conf.c` config-file parser** (`openconf` + `initconf` — the whole `ircd.conf` grammar) — same `s_conf_link.o` partial port (`-DPORT_INITCONF_P6`, un-static'ing `lookup_confhost`); the line lexer is a new **`nom`-based** module (`config_parse.rs` — faithful `process_escapes`→`split_fields` port of the escape/comment pass + `getfield`), the semantic switch builds byte-identical `aConfItem` lists/classes + side effects. Not in msgtab. L1 (`config_parse_diff`: nom `split_fields` vs `cref_getfield`; `s_conf_initconf_diff`: full conf-list build vs `cref_initconf` over A/I/i/O/o/Y/C/c/N/D/H/L/V/Q/B/K/k) + L2 (`golden_s_conf_initconf`: STATS y/i/o over the parsed config). No S2S.
- **P6g DONE: `s_conf.c` `find_Oline`** (the read-only `conf`-list scan `m_oper` uses to resolve `OPER <name> <pass>`: best `CONF_OPS` entry by name `mycmp` + host `match`/`match_ipmask`, honouring `clients < MaxLinks(Class)` with the over-limit `tmp2` fallback) — same `s_conf_link.o` partial port (`-DPORT_SCONF_FINDOLINE_P6`). Utility/callee (`m_oper` calls it). L1 (`s_conf_find_oline_diff`: name/host-glob/CIDR-in-out/over-limit-fallback/wrong-status/empty incl. inverses) + L2 (`golden_s_conf_find_oline`, `find_oline.conf`: a `10.0.0.0/8` O-line that misses the 127.0.0.1 client → 491, vs a `*@*` O-line → 381 — the host/CIDR-miss branch the existing OPER golden can't reach). No S2S (no IsServer/remote-field path).
- **P6h DONE: `s_conf.c` `find_conf*` family** (the seven read-only conf lookups `find_conf_exact`/`find_conf_name`/`find_conf`/`find_conf_host`/`find_conf_host_sid`/`find_conf_ip`/`find_conf_entry`) — same `s_conf_link.o` partial port (`-DPORT_SCONF_FINDCONF_P6`). Utility/callee (none in msgtab; the still-C `s_serv.c`/`s_bsd.c`/`s_misc.c` server-link + registration paths call them). L1 (`s_conf_find_conf_diff`: all seven vs `cref_` over positive + inverse — name/host/status/port/passwd-asymmetry misses, the CONF_OPERATOR over-limit skip, the server-mask `mycmp`-exact-vs-`match`-glob split, the NUL HOSTLEN early-outs, the `find_conf_ip` in-place `@`-split restore; **the differential caught an inverted `!match(passwd,sid)` sid gate in the first draft**) + S2S (`golden_s2s_s_conf_find_conf`: a peer link drives the C/N-line `find_conf` accept + `find_conf_host`/`find_conf_host_sid` leaf/hub checks → byte-identical burst + post-link routing; the 37-test `golden_s2s_*` suite + registration goldens are the broader regression). No client-only L2 numeric.
- **P6i DONE: `s_conf.c` K-line lookup family** (`find_kill`/`find_two_masks`/`find_conf_flags`/`find_denied`) — the read-only K-line / version-mask / deny lookups named in the PLAN P6 row, ported to `ircd-common/s_conf.rs` via the `s_conf_link.o` partial port (`-DPORT_SCONF_FINDKILL_P6`, three guard regions; `find_bounce` stays C). Config-resolved: TKLINE on (find_kill's tkconf→kconf two-pass loop compiles), KLINE off, TIMEDKLINES off (the reply/check_time_interval block not compiled; `timedklines` arg inert). Utility/callees (none in msgtab); the still-C `s_serv.c` `check_version`, Rust `s_user.c` `register_user`, and `ircd.c` resolve to the Rust defs at link. L1 (`s_conf_find_kill_diff`: all four vs `cref_` over positive + inverse — find_kill tkconf/kconf hit, expired-TK fallthrough, OTHERKILL→ident vs KILL→username `check`, port gate, CIDR in/out, '='-numeric-only skip, resolved-hostname match, IsKlineExempt short-circuit, no-user guard; find_two_masks/find_conf_flags status/host/name/no-slash/prefix/strpbrk inverses; find_denied host-scan/reversed/digit-passwd/no-D-line) + L2 (`golden_s_conf_find_kill`, `kline.conf`: a 127.0.0.1 client hits `K|127.0.0.1|…|*|0|` mid-registration → 465 + `K-lined:` Closing Link) + S2S (`golden_s2s_s_conf_find_two_masks`, `vline_reject.conf` = s2s.conf + `V|*||peer.test|`: m_server_estab check_version → find_two_masks → "Bad version" exit). The no-match version-check path is exercised by every other `golden_s2s_*` test; find_denied is reachable only from `try_connections` server auto-connect (not in the golden harness) → L1 only.
- **P6j DONE: `s_conf.c` `find_bounce`** (the RPL_BOUNCE sender — the read-only conf-list scan that emits `010 :Please use this Server/Port instead` to a rejected connection) — ported to `ircd-common/s_conf.rs` via the `s_conf_link.o` partial port (`-DPORT_SCONF_FINDBOUNCE_P6`, one guard region; the mutating `attach`/`detach`/`rehash` family + `lookup_confhost`/`ipv6_convert` stay C). Three dispatch modes by `fd`: `fd>=0` early-reject (cptr NULL → `sprintf`+raw `sendto`), `fd==-1` class-number host match, `fd==-2` host-match-only. Config: `CONF_BOUNCE`=262144, `RPL_BOUNCE`=10. Utility/callee (not in msgtab); callers s_bsd.c (all-conns-in-use), C `count_cnlines`, and Rust `m_nick`/`m_user` (server-only P-line) resolve to the Rust def at link. L1 (`s_conf_find_bounce_diff`: 9 tests diffing **sent bytes** — sendQ via sendto_one for fd<0, socketpair recv for fd>=0 — over host-glob hit/miss, CIDR in/out, class-number host on fd==-1 vs the fd==-2 skip, non-bounce-skip + first-match-wins, the cptr-NULL guard, empty list, and the fd>=0 empty-host write vs non-empty-host no-op) + L2 (`golden_s_conf_find_bounce`, `bounce.conf`: a server-only P-line `P||||16667|0|S|` + `B|127.0.0.1||bounce.elsewhere|7000|` → client NICK on the server-only port → `010` bounce + `Server only port` exit, byte-identical). No S2S (no IsServer/remote-field path; the fd>=0 raw-sendto branch needs MAXCLIENTS conns → L1 only).
- **P6k DONE: `s_conf.c` attach/detach mutating cluster** (`attach_conf`/`detach_conf`/`det_confs_butmask`/`attach_confs`/`attach_confs_host` + the private `is_attached`/`add_cidr_limit`/`remove_cidr_limit`) — the connected component of conf-attachment mutators (grow/shrink `cptr->confs` + the global `conf` list, mutate class refcounts `aconf->clients`/`ConfLinks` and the per-class CIDR-limit patricia trees), ported to `ircd-common/s_conf.rs` via the `s_conf_link.o` partial port (`-DPORT_SCONF_ATTACH_P6`, three guard regions). Config-resolved: ENABLE_CIDR_LIMITS on (add/remove_cidr_limit compile; `pnode->data` void* counter), YLINE_LIMITS_IPHASH on (per-host limit loop walks the IP hash), YLINE_LIMITS_OLD_BEHAVIOUR off (max-links gate `ConfLinks >= ConfMaxLinks`). Utility/callees (none in msgtab); callers — C `attach_Iline`/`count_cnlines`/`s_bsd.c`/`s_serv.c`/`s_service.c` + Rust `register_user`/`m_oper`/`m_umode` — resolve to the Rust defs at link. Gates: L1 (`s_conf_attach_diff`: two fully-isolated worlds via an `Api` of fn-pointers; round-trip/inverse invariants — attach→detach unlink, is_attached idempotency, -3 Y-line-max + -4 CIDR gates, CIDR freed-slot reuse, free-class on maxLinks==-1, illegal-conf removal from global list, det_confs_butmask, attach_confs/host glob-vs-mycmp; zero diff) + L2 (`golden_s_conf_attach`, `ylinemax.conf`: 2 clients in a maxlinks-1 class → bob rejected via attach_conf -3 → "Too many connections", byte-identical; success+detach paths covered pervasively by the registration + golden_s2s_* suite). No S2S (no IsServer/remote-field path; attach_confs C/N + det_confs_butmask SQUIT covered by the 37-test golden_s2s_* establishment/teardown suite). What stays C in s_conf.c is now only `rehash`, `attach_Iline`/`count_cnlines` (callers of this core), and the initconf-private `lookup_confhost`/`ipv6_convert`.
- **P6l DONE: `s_conf.c` `attach_Iline` + `count_cnlines`** (the two callers of the P6k attach/detach core) — the local-client I-line lookup used during registration (`attach_Iline`: scan `conf` for the best `CONF_CLIENT` line matching the client's port/name/host/password, set the +r/kline-exempt conf flags onto `cptr->user->flags`, copy the resolved host into `sockhost`, then `attach_conf`; emits `464 ERR_PASSWDMISMATCH`/`find_bounce` on rejection) and the read-only single-N-line counter used during inbound server-link establishment (`count_cnlines`), ported to `ircd-common/s_conf.rs` via the `s_conf_link.o` partial port (`-DPORT_SCONF_ILINE_P6`, one guard region s_conf.c:483–664). Config-resolved: UNIXPORT off (no unix-socket host branch), XLINE off (no XlineExempt clear), NO_DNS_LOOKUP on (hp usually NULL). Utility/callees (none in msgtab); callers — C `check_client`/`check_server` (s_bsd.c) — resolve to the Rust defs at link. **What stays C in `s_conf.c` is now only `rehash` + the initconf-private `lookup_confhost`/`ipv6_convert`.** Gates: L1 (`s_conf_iline_diff`: two fully-isolated worlds via an `Api` of fn-pointers — happy glob/CIDR/empty-mask attach + the no-I-line `-2` inverse, port gate, password fall-through vs `-8`+`464`, +r/kline-exempt flag-set, hp!=NULL name-match + `get_sockhost`; `count_cnlines` single-N/zero/two/non-server-skip/empty; zero diff over 8 tests) + L2 (`golden_s_conf_iline`, `iline_pass.conf`: a password I-line with no `F` flag → a client sending no `PASS` → `464` + "Bad password" Closing Link, byte-identical). No S2S (neither has an `IsServer` branch / remote-field formatting; `count_cnlines`'s server-link path is covered by the 37-test `golden_s2s_*` link suite).
- **P6m DONE: `s_conf.c` finalize — `rehash` + `ipv6_convert` + `lookup_confhost` + the `conf`/`kconf`/`networkname` data globals** (the last three functions + the last three data globals s_conf.c owned) — ported to `ircd-common/s_conf.rs`, so **`s_conf.c` is now FULLY Rust → `s_conf.o` dropped outright** (added to `PORTED`; the `s_conf_link.o` partial-compile + the `"s_conf.o" => "s_conf_link.o"` link map removed; the cref oracle keeps the unguarded `s_conf.o`). `rehash` = the faithful config reload (free conf/kconf, mark classes `MaxLinks=-1`, `initconf(0)`, close/reopen listeners, re-read MOTD, the sig `1`/`'a'`/`'d'`/`'t'` branches; ULTRIX/USE_SYSLOG off, TKLINE on); `ipv6_convert` = the pure `[user@]addr[/cidr]` canonicaliser (in-place `@`/`/` poke + restore); `lookup_confhost` = numeric host via `inetpton` (the NO_DNS path) / alpha host via the still-C `gethost_byname`, `badlookup` AND16==255 reset. The conf/kconf/networkname globals flip from bindgen externs to `#[no_mangle] pub static mut` defs (list.rs/s_serv.rs importers resolve to them at link, same as `tkconf`/`classes`). `build.rs` expands `IRCDMOTD_PATH` via make → the `IRCD_MOTD_PATH` rustc-env → `ircd_sys::MOTD_PATH`, so the Rust `rehash`'s `read_motd` reads the identical file reference-C does. Utility/callees (none in msgtab); `rehash` reached from Rust `m_rehash` (oper REHASH) + still-C `ircd.c` SIGHUP. Gates: L1 (`s_conf_rehash_diff`: `ipv6_convert` vs `cref_` over bare-v6/user@/cidr/v4-mapped/passthrough + the input-restore invariant) + L2 (`golden_s_serv_maint` REHASH 382 success/481 reject now drives the Rust `rehash`; `golden_s_conf_initconf` boot exercises `lookup_confhost`/`ipv6_convert`). No S2S (no `IsServer`/remote-field path). `lookup_confhost` is `static` in the unguarded oracle → no `cref_` symbol → covered by the boot golden, not L1.
- **P6n DONE: `res.c`'s pure wire-format leaf `res_comp.c`** (the BIND DNS name compression/expansion `ircd_dn_expand`/`ircd_dn_comp`/`__ircd_dn_skipname` + the big-endian integer marshalers `ircd_getshort`/`ircd_getlong`/`ircd__putshort`/`ircd__putlong`) — ported outright to `ircd-common/res_comp.rs` → **`res_comp.o` dropped** (added to `PORTED`; no `_link.o`). Self-contained leaf: `nm --undefined-only` shows zero ircd-internal deps (only libc `memcpy`/`strchr`/`errno`); the 7 BIND `static` helpers (`ns_name_*`/`special`/`printable`/`mklower`/`dn_find`) ported as private Rust fns. Faithful raw-pointer port; `errno` set (`EMSGSIZE`/`ENOENT`) on every error path as the C does. Utility/leaf (none in `msgtab`); callers — still-C `res.c`/`res_mkquery.c` — resolve to the Rust defs at link. Gate: **L1 only** (`res_comp_diff`: get/put match + put→get round-trip; `dn_comp`→`dn_expand` pack/expand round-trip; the compression-pointer reuse diffed as buffer-relative `dnptrs` offsets + emitted wire bytes; a hand-built compressed-pointer message; `dn_skipname` plain/compressed/truncated; the dstsiz-too-small, malformed-pointer, overlength `-1` inverses with `errno` compared). No L2/S2S — pure wire-format leaf with no client/server-reachable command path under the locked config; end-to-end DNS coverage arrives when the resolver proper is ported under a fixture nameserver.
- **P6o DONE: `res.c`'s DNS query-packet builder `res_mkquery.c`** (`ircd_res_mkquery` — forms QUERY/NS_NOTIFY_OP/IQUERY messages into a caller buffer, returns the packet length or `-1`) — ported outright to `ircd-common/res_mkquery.rs` → **`res_mkquery.o` dropped** (added to `PORTED`; no `_link.o`). Deps resolve at link: `ircd_dn_comp`/`ircd__putshort`/`ircd__putlong` are the P6n Rust defs, the resolver-state global `ircd_res` + `ircd_res_init` stay C (res_init.o). **Layout hazard neutralized:** the 12-byte DNS header is written through the **bindgen `HEADER`** bitfield type (size 12, `set_id`/`set_opcode`/`set_rd`/`set_rcode`/`set_qdcount`/`set_ancount`/`set_arcount` byte-identical to the gcc little-endian `nameser_def.h` layout) instead of hand-packing bitfields; `h_errno` set via `__h_errno_location` exactly as the C. Config-resolved: `DEBUG` off → the `RES_DEBUG` printf block not compiled. Utility/leaf (none in `msgtab`); callers — still-C `res.c` (`query_name`/`do_query_*`) — resolve to the Rust def at link. Gate: **L1 only** (`res_mkquery_diff`: Rust vs `cref_` byte-identical packets over QUERY±recurse, NS_NOTIFY_OP with/without the additional-record branch, IQUERY answer section with/without rdata, plus the `-1` inverses — bad op, NULL buf, `buflen<HFIXEDSZ`, and the question-section `buflen`/`dn_comp` overflow — and the shared `++ircd_res.id` side effect diffed identically; both `ircd_res`/`cref_ircd_res` pinned with RES_INIT to skip the nondeterministic `res_init`/`res_randomid` path, a process-global `RES_LOCK` serializing the parallel `#[test]` threads). No L2/S2S — pure wire-format leaf with no client/server-reachable command path under NO_DNS_LOOKUP; end-to-end DNS coverage arrives when the resolver proper (`res.c`+`res_init.c`) is ported under a fixture nameserver.
- **P6p DONE: `res.c`'s resolver-state bootstrap `res_init.c`** (`ircd_res_init` — reads `/etc/resolv.conf` + `LOCALDOMAIN`/`RES_OPTIONS` env into the global `ircd_res`; the static `res_setoptions` it drives; the random message-id `ircd_res_randomid`) — ported outright to `ircd-common/res_init.rs` → **`res_init.o` dropped** (added to `PORTED`; no `_link.o`). The `ircd_res` global moves from C to a Rust `#[no_mangle] static mut` (`MaybeUninit::zeroed`); `res_mkquery.rs`/`res.c`/`s_bsd.c` resolve their `ircd_res`/`ircd_res_init` references to the Rust defs at link. Self-contained leaf — the only non-libc dep is `inetpton` (P1 `support.rs`). Config-resolved: `DEBUG`/`NEXT`/`RESOLVSORT`/`RFC1535`/`HAVE_GETIPNODEBYNAME`/`USELOOPBACK`/`__BIND_RES_TEXT` all OFF → only the simple path compiles (no NetInfo, no sortlist, no debug printfs, default-`dnsrch` derivation active, `inet6`→`RES_USE_INET6`; `AFINET=AF_INET6` so nameservers parse as IPv6). Utility/leaf (none in `msgtab`). Gate: **L1 only** (`res_init_diff`: the `ircd_res` struct produced by `ircd_res_init` diffed vs `cref_` field-by-field — scalars, ndots/nsort bitfields, the 3 `nsaddr_list` `sockaddr_in6` byte-for-byte, `defdname` bytes, `dnsrch[]` as offsets into `defdname` — over the default-`dnsrch`/`LOCALDOMAIN`-search-tokenizer/`RES_OPTIONS` `ndots:`+`inet6`+`RES_MAXNDOTS`-clamp paths, both globals zeroed + `.id` pinned nonzero to skip the nondeterministic `res_randomid`, env-set/init/restore under a process-global `RES_LOCK`; `res_randomid` is nondeterministic → only its `& 0xffff` mask smoke-tested). Boot/registration + `golden_s_dns` goldens confirm the Rust `ircd_res_init`/`ircd_res` boot byte-identical. No L2/S2S (pure leaf, no client/server command path under `NO_DNS_LOOKUP`; end-to-end DNS coverage arrives when the resolver proper `res.c` is ported under a fixture nameserver). **`res.c` is the only DNS file still in C.**
- **P6q DONE: `res.c` cache-hash leaves `hash_number`/`hash_name`** (the two pure DNS-cache bucket hashes — first bite of the `res.c` cache cluster, ported bottom-up from the resolver's dependency floor) — ported to `ircd-common/res.rs` via the `res_link.o` partial port (`-DPORT_RES_CACHE_P6q`, dropping their bodies + declaring them extern so the still-C cache walkers `find_cache_name`/`add_to_cache`/`rem_cache`/`find_cache_number` call the Rust defs). Both are upstream file-`static`, so a new three-way `RES_CACHE_LINKAGE` macro de-statics them to GLOBAL in the cref oracle's `res.o` (`-DRES_CACHE_EXPOSE`, behavior-neutral; res.o is consumed only by `libcref.a`) so `cref_hash_*` exists for the L1 differential — neutralizing the `l1-cref-static-symbol-limit`. Faithful: `hash_number` is the unrolled `hashv=ip[0]; for i in 1..16 { hashv=2*hashv+ip[i] }` over a **wrapping u32** (`% 1009`=ARES_CACSIZE only at the end; reads exactly 16 bytes = sizeof in6_addr, AFINET=AF_INET6); `hash_name` sums bytes to the first `.`/NUL with **signed-char** promotion (`i8 as i32 as u32`). Utility/leaf (not in `msgtab`). Gate: **L1 only** (`res_hash_diff`: Rust vs `cref_` over 16-byte v6 addrs incl. all-0xff u32-overflow/v4-mapped/high-bit, names incl. empty/dot-first/embedded-dot/8-bit/long; range-checked `[0,1009)`). No L2/S2S (no client/server command path; resolver inert under `NO_DNS_LOOKUP`, cache never populates at runtime — end-to-end DNS coverage arrives when the resolver proper is ported under a fixture nameserver). `golden_s_dns` + boot goldens confirm the still-C cache walkers call the Rust hashes without regression.
- **P6r DONE: `res.c` cache removal/expiry core `rem_list`/`rem_cache`/`expire_cache`/`flush_cache`** (the second bite of the `res.c` cache cluster, building on the P6q hashes) — the connected component that *deletes* cache entries, ported to `ircd-common/res.rs` via the `res_link.o` partial port (`-DPORT_RES_CACHE_REM_P6r`, generalizing the P6q three-way `RES_CACHE_LINKAGE` to `rem_cache`/`rem_list`). `rem_list` unlinks one entry from `cachetop` **only** (leaves the hash buckets + `incache` untouched — the faithful quirk); `rem_cache` does the full delete (NULL any `local[]->hostp` at this `he`, unlink from `cachetop` + both `hashtable` buckets via the Rust `hash_name`/`hash_number`, free name/aliases/addrs/entry, `incache--` + `ca_dels++`); `expire_cache` reaps the past-due + returns the next poll time; `flush_cache` reaps everything. The cache data globals `hashtable`/`incache` join `cachetop`/`cainfo` on `RES_DNS_STATIC`, now also exposed under `-DRES_CACHE_EXPOSE` so the cref oracle gets `cref_cachetop`/`cref_hashtable`/`cref_incache`/`cref_cainfo` for the L1 isolated worlds. Utility/leaf (not in `msgtab`). Gate: **L1 only** (`res_cache_rem_diff`: two fully-isolated `World`s of fn-pointers + global base pointers, entries hand-built à la `add_to_cache`; flush/expire/rem_cache-hostp/rem_list each with its inverse — flush empties + re-inserts clean, expire keeps survivors in order + returns min-future/`now+AR_TTL`, rem_cache clears a per-world dangling `hostp`, rem_list returns the successor + leaks `incache` by design; zero diff). No L2/S2S (resolver inert under `NO_DNS_LOOKUP`; `golden_s_dns` + registration goldens confirm the still-C cache walkers call the Rust removal core without regression). **Still C in `res.c`:** the resolver proper + the cache build/insert/lookup half (`add_to_cache`/`update_list`/`find_cache_*`/`make_cache`).
- **P6s DONE: `res.c` cache lookup/reorder half `update_list`/`find_cache_name`/`find_cache_number`** (the third bite of the `res.c` port — the half of the hostent cache that *looks up* and *reorders/merges* entries, built on the P6q hashes + P6r removal core) — the connected component ported to `ircd-common/res.rs` via the `res_link.o` partial port (`-DPORT_RES_CACHE_LOOKUP_P6s`, extending the three-way `RES_CACHE_LINKAGE` to the three upstream file-statics so the still-C `gethost_byname_type`/`gethost_byaddr`/`proc_answer`/`make_cache` callers resolve to the Rust defs at link, and the cref oracle de-statics them to `cref_*` for L1). `update_list` reorders a hit to the `cachetop` head and (if `rptr != NULL`) merges new aliases (T_PTR) / addresses (T_A/T_AAAA) from the ResRQ reply into the cached `hostent` — the gnarly double-`MyRealloc` of the contiguous IP block + the pointer array, then the `FLG_PTR_*` flag update by `addrcount`; `find_cache_name`/`find_cache_number` do the hashed-bucket lookup + the full-list fallback (the fallback's fixed-`s`=alias[0] quirk + the `find_cache_number` `rem_list` degenerate-entry reap preserved), `update_list` on hit, `ca_na_hits`/`ca_nu_hits` counters. Faithful raw-pointer port preserving the C post-increment index walks (`s = h_aliases[i++]`) and the for-loop `continue`→increment semantics. Utility/leaf (not in `msgtab`). Gate: **L1 only** (`res_cache_lookup_diff`: two isolated `World`s of fn-pointers + global base pointers — `cachetop`/`hashtable`/`cainfo`; find_cache_name hashed hit+reorder+counter / name-miss / flags-gate-miss / alias full-list fallback; find_cache_number hashed hit / absent-addr miss / FLG_PTR_VALID gate / multi-addr fallback; update_list rptr=NULL reorder-only + T_PTR alias-merge & T_A addr-merge each with the already-present no-growth inverse; zero diff over 6 tests). No L2/S2S (resolver inert under `NO_DNS_LOOKUP`; `golden_s_dns` + `golden_registration` confirm the still-C build/insert half calls the Rust lookup/reorder core without regression). **Still C in `res.c`:** the resolver proper + the cache **build/insert** half (`add_to_cache`/`make_cache`).
- **P6t DONE: `res.c` cache build/insert half `add_to_cache`/`make_cache`** (the fourth and final bite of the `res.c` cache cluster — the two functions that *create* and *link in* hostent-cache entries, built on the P6q hashes + P6r removal core + P6s lookup/reorder) → **`res.c`'s hostent cache is now FULLY Rust**; only the resolver proper remains C. Ported to `ircd-common/res.rs` via the `res_link.o` partial port (`-DPORT_RES_CACHE_BUILD_P6t`, extending the three-way `RES_CACHE_LINKAGE` to both upstream file-statics so the still-C `proc_answer` caller of `make_cache` resolves to the Rust def at link, and the cref oracle de-statics them to `cref_*` for L1). `add_to_cache` = list-head push + both hash-bucket links (`hash_name`/`hash_number`) + `++incache > MAXCACHED` LRU tail-evict via `rem_cache` + `ca_adds++`; `make_cache` = dedup-or-build (T_PTR→`find_cache_number` per reply addr / T_A·T_AAAA→`find_cache_name` by name — a hit returns the already-reordered+merged entry; a miss `MyMalloc`s a fresh entry, builds the contiguous IP block + pointer array, **steals** the ResRQ aliases + `h_name` (source nulled), clamps `ttl<600`→600 + `re_shortttl++`, `expireat = timeofday + ttl`, sets the `FLG_A_VALID`/`FLG_AAAA_VALID` flag, then `add_to_cache`). Config: DEBUG off (no Debug blocks), AFINET=AF_INET6 (16-byte IN_ADDR), MAXCACHED 512. Utility/leaf (not in `msgtab`). Gate: **L1 only** (`res_cache_build_diff`: two fully-isolated `World`s of fn-pointers + base pointers incl. `incache`/`reinfo`/`timeofday` — build-by-name A/AAAA with the steal inverse, short-ttl clamp + the ≥600 inverse, dedup-by-name + dedup-by-addr-PTR no-add, multi-addr contiguous-block build, `add_to_cache` LRU boundary at MAXCACHED (evict via ca_dels delta) + the under-cap inverse, both-bucket linkage via the two finders; `timeofday` pinned so `expireat` is deterministic; zero diff over 8 tests). No L2/S2S (resolver inert under `NO_DNS_LOOKUP`, cache never populates at runtime; `golden_s_dns` + `golden_registration` confirm the still-C resolver calls the Rust build/insert core without regression). **Still C in `res.c`:** only the resolver proper (request queue + answer parser + socket I/O).
- **P6u DONE: `res.c` request-queue core `add_request`/`rem_request`/`make_request`/`find_id`** (the first bite of the `res.c` resolver proper — the queue-management primitives the rest of the resolver builds on, ported bottom-up from the outstanding-DNS-request floor after the P6q–P6t cache cluster) — ported to `ircd-common/res.rs` via the `res_link.o` partial port (`-DPORT_RES_REQ_P6u`: a parallel three-way `RES_REQ_LINKAGE` de-statics the four fns — `extern` in res_link.o so the still-C walkers `del_queries`/`timeout_query_list`/`get_res` resolve to the Rust defs, GLOBAL in the cref oracle for `cref_*` L1, static otherwise — and the `first`/`last` queue heads flip from `static` to `RES_DNS_STATIC` so the Rust core + the still-C walkers share one storage, same as the cache globals). `add_request` tail-appends + `reinfo.re_requests++` (NULL → -1 no-op); `rem_request` unlinks (repointing `last` on tail removal) + frees `he.h_name`/`he.h_aliases[]`/`name`/the node; `make_request` `MyMalloc`+`bzero` + seeds `sentat=timeofday`/`retries=3`/`resend=1`/`srch=-1`/`timeout=4`/`he.h_addrtype=AFINET` + copies the `Link` into `cinfo` + `add_request`; `find_id` linear-scans `first`. Config: AFINET=AF_INET6, DEBUG off (Debug() traces gone), MyFree→`libc::free`. Utility/callees (not in `msgtab`). Gate: **L1 only** (`res_request_diff`: two isolated `World`s of fn-pointers + `first`/`last`/`reinfo` base pointers — make_request seeds+appends (NULL-lp zeroes cinfo), add_request order+counter + the NULL no-op, find_id hit/miss + misses-after-rem, rem_request head/middle/tail unlink+`last`-repoint+free, drained queue `first==last==NULL`+reusable; `timeofday` pinned, a process-global LOCK serializes the parallel threads; zero diff over 8 tests). No L2/S2S (resolver inert under `NO_DNS_LOOKUP`, the queue never populates at runtime; `golden_s_dns` + `golden_registration` confirm the still-C walkers call the Rust queue core without regression). **Still C in `res.c`:** `del_queries`/`timeout_query_list` + the resolver socket/answer half (`send_res_msg`/`do_query_*`/`query_name`/`resend_query`/`proc_answer`/`get_res`/`gethost_*`/`init_resolver`/`cres_mem`).
- **P6v DONE: `res.c` queue walkers `del_queries`/`timeout_query_list`** (the second bite of the `res.c` resolver proper — the reaper/timeout layer that walks the outstanding-DNS request queue, built on the P6u queue core) — ported to `ircd-common/res.rs` via the `res_link.o` partial port (`-DPORT_RES_WALK_P6v`). `del_queries(cp)` reaps every node whose `cinfo.value.cp` matches; `timeout_query_list(now)` walks `first`, reaping past-due requests whose `--retries` is exhausted (`reinfo.re_timeouts++` + the per-`cinfo.flags` notify — ASYNC_CLIENT `sendto_iauth`+`ClearDNS`, ASYNC_CONNECT `sendto_flag` SCH_ERROR — then `rem_request`) or backing off the rest (`sentat=now`, `timeout` doubled, `resend_query`, `tout=now+timeout`), returning `(next>now)?next:now+AR_TTL`. A new `RES_RESEND_LINKAGE` seam de-statics the still-C `resend_query` (extern in res_link.o so the Rust `timeout_query_list` resolves to it; the cref oracle keeps it static — `cref_timeout_query_list` resolves to the objcopy-renamed local, self-contained since `resend_query` has no out-of-TU caller). Config-resolved: DEBUG off (`Debug`/`myctime` traces gone), USE_IAUTH on; `retries` is unsigned `char` on this platform → faithful `wrapping_sub`. Utility/callees (not in `msgtab`); still-C callers `s_bsd.c`/`ircd.c` + Rust `list.rs` resolve to the Rust defs at link. Gate: **L1 only** (`res_walk_diff`: two fully-isolated `World`s of fn-pointers + `first`/`last`/`reinfo` base pointers — del_queries head/middle/tail match + absent-cp no-op + all-match drain; timeout future-survive (min `tout`) / exhausted ASYNC_CONF·ASYNC_CLIENT (ClearDNS clears the DNS bit only)·ASYNC_CONNECT / retry back-off (resend=0 → `resend_query` no-op) / a mixed survivor+reaped+retried walk — return value, queue shape, `re_timeouts` all diffed; `timeofday` pinned, `adfd=-1` + BSS-zero `svchans` neutralize the variadic notifies, process LOCK serializes; zero diff over 9 tests). No L2/S2S (resolver inert under `NO_DNS_LOOKUP`, the queue never populates at runtime; `golden_s_dns` + `golden_registration` confirm the still-C resolver calls the Rust walkers without regression). **Still C in `res.c`:** only the answer parser + socket I/O half (`send_res_msg`/`do_query_*`/`query_name`/`resend_query`/`proc_answer`/`get_res`/`gethost_*`/`init_resolver`/`cres_mem`).
===== p6.md tail =====
  - **L1.** `ircd-testkit/tests/res_init_diff.rs`: zero diff vs `cref_ircd_res_init`. Both `ircd_res`/`cref_ircd_res` zeroed + `.id` pinned `0x4321` per test (skip the nondeterministic `res_randomid`); env-set + both inits + env-restore under a process-global `RES_LOCK` (parallel `#[test]` threads share the env + globals). The result structs diffed field-by-field (scalars/bitfields, 3× `nsaddr_list` `sockaddr_in6` bytes, `defdname` bytes, `dnsrch[]` as offsets into `defdname`). The differential makes the `/etc/resolv.conf`+`inetpton` path host-robust (both read the same file → identical, or both skip it); the host-independent branches run on every CI host via env — `plain` (default-`dnsrch`), `LOCALDOMAIN` (search tokenizer), `RES_OPTIONS` `ndots:3 inet6` and `ndots:99` (the `RES_MAXNDOTS` clamp). `res_randomid` nondeterministic → only `& 0xffff` smoke-tested (the one symbol with no L1 diff).
  - **L2/S2S.** None — pure leaf, no client/server command path under `NO_DNS_LOOKUP`. Boot+registration (`golden_registration`) and the resolver-adjacent `golden_s_dns` goldens stay byte-identical, confirming the Rust `ircd_res_init`/`ircd_res` (called from `s_bsd.c` at startup) boot the server identically to reference-C. End-to-end DNS coverage (L2/L4) arrives when the resolver proper `res.c` is ported under a fixture nameserver.

- **2026-06-05 — P6q (`res.c` cache-hash leaves) merged.** First bite of the `res.c` port — the last C file. `res.c` is one large connected cluster (resolver socket + request queue + hostent cache) over file-statics with only 11 `_ext.h`-exported entry points; the dependency floor is the **hostent cache**, whose foundation is the two pure hash leaves `hash_number`/`hash_name`. Ported bottom-up so the cache core (`make_cache`/`update_list`/`find_cache_*`/`rem_cache`, P6r+) can build on Rust hashes.
  - **Cluster choice** — only `hash_number`/`hash_name` (pure: read solely their argument, touch no module state → zero data-global exposure), keeping this bite about establishing the new `res.c` static seam on a trivially verifiable pair before the gnarly cache core.
  - **Guard / seam** — new three-way `RES_CACHE_LINKAGE` macro in `res.c`: `extern` under `-DPORT_RES_CACHE_P6q` (res_link.o LINK build: bodies `#ifndef`'d out → Rust defines them, still-C walkers resolve to Rust at link), empty under `-DRES_CACHE_EXPOSE` (cref oracle res.o: GLOBAL → `cref_hash_*` for L1), else `static` (plain). `build.rs`: res_link.o gains `-DPORT_RES_CACHE_P6q`; a new step recompiles the cref `res.o` with `-DRES_CACHE_EXPOSE` (overwrites the plain one — res.o is consumed only by `libcref.a`, the link set uses res_link.o, so the real binary is untouched). RED confirmed: dropping the C bodies left 8 undefined `hash_name`/`hash_number` refs from the still-C walkers (res.c:1084/1088/1282/1316/1337/1382/1583/1600) — the exact port checklist.
  - **Config-resolved body** — neither function is config-gated; `ARES_CACSIZE`=1009.
  - **Faithfulness** — `hash_number`: `hashv = ip[0]; for i in 1..16 { hashv = 2*hashv + ip[i] }` over a **wrapping `u_int`** (the doubling overflows 32 bits for large addresses — `% ARES_CACSIZE` applied only at the end), reads exactly 16 bytes (sizeof struct in6_addr; AFINET=AF_INET6) → `wrapping_add`/wrapping double. `hash_name`: sum bytes until the first `.`/NUL, `% 1009`; `char` is **signed** on x86-64, so a high-bit byte promotes to a negative `int` before the unsigned `+=` — replicated via `*p as i32 as u32` + `wrapping_add` for 8-bit-clean parity. Mutable `*mut` args (C `u_char*`/`char*`). Returns `c_int` in `[0,1009)`.
  - **Classification** — utility/leaf, **not in `msgtab`**, no client/server command path (the resolver is inert under `NO_DNS_LOOKUP`; the cache never populates at runtime).
  - **L1** — `ircd-testkit/tests/res_hash_diff.rs`: Rust `hash_number`/`hash_name` (`#[link_name]` → the only def once res_link.o drops the C bodies) vs `cref_` over crafted corpora — 16-byte v6 addrs (zero, all-0xff overflow, single-bit, ::ffff: v4-mapped, every-byte-high-bit, random), names (empty, dot-first, no-dot, embedded-dot, case-sensitive, UTF-8/0xff high-bit, long label); each result range-checked `[0,1009)`. Zero diff. Both pure → no global state / no `RES_LOCK`.
  - **L2 / S2S** — none. No client/server command reaches these under the locked config; `golden_s_dns` + the registration/boot goldens (which boot the full ircd + still-C resolver, exercising the C cache walkers that now call the Rust hashes) confirm no regression. End-to-end DNS coverage arrives when the resolver proper is ported under a fixture nameserver.

- **2026-06-05 — P6r (`res.c` cache removal/expiry core) merged.** Second bite of the `res.c` port (after P6q's `hash_number`/`hash_name`). Ported the **removal/expiry** connected component — the half of the hostent cache that *deletes* entries — to `ircd-common/res.rs`, built directly on the P6q Rust hashes. `res.c`'s only remaining strands are the resolver proper (socket + request queue + answer parser) and the cache **build/insert/lookup** half.
  - **Cluster choice.** Bottom-up from the cache floor: `rem_list` (static), `rem_cache` (static), `expire_cache` (global, res_ext.h), `flush_cache` (global, res_ext.h). They form a clean connected component — `expire_cache`/`flush_cache` both call `rem_cache`; `rem_cache`/`rem_list` call only the P6q hashes + touch the cache data structures — with no dependency on the still-C build/insert/lookup half (`add_to_cache`/`update_list`/`find_cache_*`/`make_cache`). The two public entry points give clean `cref_` L1 symbols; the two statics are exposed via the generalized `RES_CACHE_LINKAGE`.
  - **Seam.** `-DPORT_RES_CACHE_REM_P6r` added to the `res_link.o` recipe. `RES_CACHE_LINKAGE` now fires `extern` under either `PORT_RES_CACHE_P6q` **or** `PORT_RES_CACHE_REM_P6r` (res_link.o defines both), so the `rem_cache`/`rem_list` forward decls + definitions de-static; their bodies (+ `expire_cache`/`flush_cache`) are wrapped in `#ifndef PORT_RES_CACHE_REM_P6r`. RED (after fixing a static-vs-non-static decl mismatch on the definitions) was the four undefined refs: `flush_cache` (Rust `s_conf.rs` rehash), `expire_cache` (C `ircd.c:1195`), `rem_list` (C `res.c:1379` find_cache_number), `rem_cache` (C `res.c:1119` add_to_cache) — the exact port checklist. GREEN confirmed via `nm target/debug/ircd` (all four `T` from Rust).
  - **Data-global exposure.** The Rust removal core + the L1 live world need `cachetop`/`cainfo` (already `RES_DNS_STATIC`-exposed for m_dns) **plus** `hashtable[]`/`incache`. `RES_DNS_STATIC` now also blanks `static` under `RES_CACHE_EXPOSE` (so the cref oracle res.o exposes `cref_cachetop`/`cref_cainfo`/`cref_hashtable`/`cref_incache`/`cref_reinfo` for the L1 isolated worlds), and `hashtable`/`incache` moved onto it. In res_link.o (which has `PORT_DNS_M_DNS`) all four stay **defined-global** — C owns the storage, the Rust core + the L1 live world extern them, same as P6k's `conf`. No extern/definition split needed (so no `= NULL`/array-initializer churn). `res.rs` adds `static mut hashtable: [CacheTable; 1009]` + `static mut incache: c_int` to its extern block + `local_base()`/`hashtable_base()` helpers.
  - **Faithfulness.** Raw-pointer walks mirroring the C `aCache **` chains exactly. `rem_list` captures `cp->list_next` first, unlinks from `cachetop` only, `MyFree`s, returns the saved successor — and (the C quirk preserved) does **not** decrement `incache`, touch `cainfo`, or unlink the hash buckets (leaving the freed entry's bucket refs dangling). `rem_cache`'s hostp-clearing loop walks `local[]` from `highest_fd` down; the bucket-by-`hash_name`/`hash_number` unlinks use `hp->h_addr` = `h_addr_list[0]`; `MyFree(x)` → guarded `libc::free` (the macro's re-NULL is moot — the struct is freed; `*h_addr_list` is one contiguous block freed once, then the pointer array). `expire_cache` captures `list_next` before the possible free and returns `(next > now) ? next : now + AR_TTL`.
  - **Classification.** Utility/leaf — none in `msgtab`. **L1 only** — the resolver is inert under `NO_DNS_LOOKUP`, the cache never populates at runtime, so no client/server command path reaches these. No S2S (no `IsServer`/remote-field path).
  - **L1** (`ircd-testkit/tests/res_cache_rem_diff.rs`): the P6k two-isolated-worlds pattern. A `World` bundles fn-pointers + global base pointers (`cachetop`/`hashtable`/`incache`/`cainfo` **and** `local`/`highest_fd` — the hostp loop reads s_bsd globals, which are renamed to `cref_local`/`cref_highest_fd` in the archive, so each world must own its own). Entries are hand-built mimicking `add_to_cache` (one contiguous 16×n address block with `h_addr_list[i]` pointing into it + per-name strdup'd aliases, so `rem_cache`'s frees match `make_cache`'s layout), then the public fns are driven and `Vec<i64>` observations asserted equal. 4 tests, zero diff: **flush_cache** (3 mixed single/multi-alias/multi-addr entries → `cachetop`/buckets empty, `incache` delta 0, `ca_dels`==3; round-trip re-insert + re-flush clean), **expire_cache** (two past + two future at `now=5000` → past removed, survivors kept in order, `incache` -=2, `ca_expires` delta 2, return = min-future 7000; the all-future inverse returns min expireat not `now+AR_TTL`; empty-cache returns `now+AR_TTL`), **rem_cache hostp** (a per-world `local[0]->hostp` at the doomed entry is NULLed, an unrelated `local[1]->hostp` untouched), **rem_list** (returns the successor, unlinks the middle from `cachetop`, `incache` **unchanged** — the leak-by-design quirk — then head + last removal empties the list). A process-global `Mutex` (poison-tolerant) serializes the `#[test]`s (shared cache + s_bsd globals).
  - **No L2/S2S.** Resolver inert under `NO_DNS_LOOKUP`; `golden_s_dns` (oper DNS stats + empty `DNS l` cache dump) + `golden_registration` stay byte-identical, confirming the still-C `add_to_cache`/`find_cache_number` now call the Rust `rem_cache`/`rem_list` (+ the P6q hashes) without regression. End-to-end DNS coverage arrives when the resolver proper is ported under a fixture nameserver.
  - **Plan** — `docs/superpowers/plans/2026-06-05-p6r-res-cache-rem.md`.

- **2026-06-05 — P6s (`res.c` cache lookup/reorder half) merged.** Third bite of the `res.c` port (after P6q's hashes + P6r's removal core). Ported the **lookup/reorder** connected component — the half of the hostent cache that *finds* entries and *reorders/merges* them — to `ircd-common/res.rs`. `res.c`'s only remaining strands are the resolver proper (socket + request queue + answer parser) and the cache **build/insert** half (`make_cache`/`add_to_cache`, → P6t).
  - **Cluster choice.** Bottom-up from the cache floor: `update_list` (static) is the shared leaf both finders call; `find_cache_name`/`find_cache_number` (static) are the lookup entry points. A clean connected component — the finders call `update_list` (+ the already-Rust P6r `rem_list` in `find_cache_number`'s degenerate branch) and touch only `cachetop`/`hashtable`/`cainfo` + `mycmp`/`MyRealloc`/`mystrdup`; no dependency on the still-C build/insert half. Splitting the cache "build/insert/lookup" remainder into lookup (P6s) then build/insert (P6t) keeps the bite small, matching the P6q/P6r cadence.
  - **Seam.** `-DPORT_RES_CACHE_LOOKUP_P6s` added to the `res_link.o` recipe; the three-way `RES_CACHE_LINKAGE` now fires `extern` under `PORT_RES_CACHE_P6q` **or** `PORT_RES_CACHE_REM_P6r` **or** `PORT_RES_CACHE_LOOKUP_P6s` (res_link.o defines all three guards), GLOBAL under `RES_CACHE_EXPOSE` (cref oracle), else static. The three forward decls (res.c:84/85/91) + definitions flip `static` → `RES_CACHE_LINKAGE`; the bodies (update_list … find_cache_number, res.c:1140–1406) wrap in `#ifndef PORT_RES_CACHE_LOOKUP_P6s`. RED was the undefined-symbol link error for `find_cache_name`/`find_cache_number` (still-C callers `gethost_byname_type`:410, `proc_answer`:776, `gethost_byaddr`:440, `make_cache`:1440/1456 — the exact port checklist; `update_list` had no still-C reference once the finders dropped, but Rust defines it for the finders to call). GREEN via `nm target/debug/ircd | grep -E ' T (update_list|find_cache_name|find_cache_number)$'`. (A C-comment gotcha: `gethost_*/proc` and `find_cache_*/cref` each contain a literal `*/` that prematurely closed the seam comment → reworded.)
  - **Config-resolved bodies.** No gates touch these (`DEBUG` off → the `Debug(...)` blocks vanish). `IN_ADDR`=`in6_addr` (sizeof 16, AFINET=AF_INET6); `WHOSTENTP(x)`=OR of the 16 `s6_addr` bytes; `S_ADDR`=`s6_addr`. `ResRQ.he` is the inline `hent` (`h_addr_list:[in6_addr;35]`, `h_aliases:[*char;35]`); `aCache.he` is the libc `hostent` (`**char` lists). Constants T_A=1/T_PTR=12/T_AAAA=28, MAXALIASES/MAXADDRS=35, FLG_*.
  - **Faithfulness.** Raw-pointer translation of the C for-loops with post-increment index walks (`for (i=0,s=h_name; s; s=h_aliases[i++])` → `s = *aliases.offset(i); i+=1` at the loop tail), incl. the `find_cache_name` full-list fallback's **fixed-`s`=alias[0] quirk** (`s` set only in the init, never in the increment → it only ever compares alias[0]). `update_list`'s T_PTR alias-merge reallocs `h_aliases` by one + `mystrdup`s the new name; the T_A/T_AAAA addr-merge reproduces the double-`MyRealloc` exactly — realloc the contiguous IP block (`*h_addr_list[0]`) to `addrcount*16`, realloc the pointer array to `addrcount+1`, repoint each pointer into the (possibly moved) block, NULL-terminate, `bcopy` the new IP into the last slot (`*--ab`) — then the `addrcount>1` → clear `FLG_PTR_FWD|VALID` vs the single-addr `FLG_PTR_PEND_REV`/`FLG_PTR_PEND_FWD` logic. `find_cache_number`'s second loop preserves the C for-loop `continue`→increment semantics (every guard incl. the `cp = rem_list(cp)` degenerate-reap falls through to the trailing `cp = cp->list_next`). `bcmp`→16-byte slice compare; `MyRealloc`/`mystrdup`/`mycmp` are the Rust ports resolved at link.
  - **Classification.** Utility/leaf — none in `msgtab`. → **L1 only.** No `IsServer`/remote-field path; the resolver is inert under `NO_DNS_LOOKUP` so no client/server command populates the cache at runtime.
  - **L1** (`ircd-testkit/tests/res_cache_lookup_diff.rs`): the P6k/P6r two-isolated-`World`s pattern (fn-pointers + global base pointers: `cachetop`/`hashtable`/`cainfo` + `find_cache_*`/`update_list`/`hash_*`). `insert()` builds entries à la `add_to_cache` (contiguous addr block); `make_rptr()` builds a `ResRQ` per world (each owns its pointers → compare CONTENTS not pointers). 6 tests, zero diff: **update_list reorder-only** (rptr=NULL: head move keeping relative order, `ca_updates` bump, repeat on the head = no-op); **find_cache_name** hashed hit + reorder + `ca_na_hits` / name-miss (counter unchanged, order kept) / flags-gate miss / the alias full-list fallback (h_name & alias[0] in distinct hash buckets); **find_cache_number** hashed hit + `ca_nu_hits` / absent-addr miss / FLG_PTR_VALID gate miss / the multi-addr fallback (lookup the 2nd of two addresses, found only via the full-list scan); **update_list T_PTR alias merge** (append a new name) + the already-present (= cached h_name) no-growth inverse; **update_list T_A addr merge** (append a new IP, addrcount→2 flag clear) + the already-present no-growth inverse (single-addr `FLG_PTR_PEND_FWD` set). A process-global `Mutex` serializes the `#[test]`s; each scenario `reset()`s its world's cache + buckets and snapshots counter deltas.
  - **No L2/S2S.** Resolver inert under `NO_DNS_LOOKUP`; `golden_s_dns` (oper DNS stats + empty `DNS l`) + `golden_registration` stay byte-identical, confirming the still-C `proc_answer`/`gethost_*`/`make_cache` now call the Rust lookup/reorder core (+ the P6q hashes + P6r removal) without regression. End-to-end DNS coverage arrives when the resolver proper is ported under a fixture nameserver.
  - **Plan** — `docs/superpowers/plans/2026-06-05-p6s-res-cache-lookup.md`.

- **2026-06-05 — P6t (`res.c` cache build/insert half `add_to_cache`/`make_cache`) merged.** The fourth and final bite of the `res.c` hostent-cache cluster — the two functions that *create* and *link in* cache entries — closing the cache out: with this **`res.c`'s hostent cache is now FULLY Rust**, and only the resolver proper (request queue + answer parser + socket I/O) remains C.
  - **Cluster choice / why now.** The dependency floor was already in place: P6q (the `hash_number`/`hash_name` leaves), P6r (the `rem_list`/`rem_cache`/`expire_cache`/`flush_cache` removal core), P6s (the `update_list`/`find_cache_name`/`find_cache_number` lookup/reorder half). `add_to_cache`/`make_cache` are the *only* remaining cache functions and form a tight connected component (`make_cache`→`find_cache_*`/`MyMalloc`→`add_to_cache`→`rem_cache`), every callee already Rust → the natural last bite.
  - **Guard / partial port.** `#ifndef PORT_RES_CACHE_BUILD_P6t` wraps both C bodies (res.c:1086 `add_to_cache`, res.c:1416 `make_cache`); `-DPORT_RES_CACHE_BUILD_P6t` added to the `res_link.o` compile in `build.rs` alongside the P6q/r/s defines. Both functions' decl+def flipped from `static` to the existing three-way `RES_CACHE_LINKAGE` macro (→ `extern` under any `PORT_RES_CACHE_*` in res_link.o so the still-C `proc_answer` caller of `make_cache` resolves to the Rust def; → GLOBAL under `-DRES_CACHE_EXPOSE` in the cref oracle res.o so `cref_add_to_cache`/`cref_make_cache` exist for L1). `make_cache`'s forward decl at res.c:88 updated to match.
  - **Config-resolved body.** `DEBUG` off → all `Debug((…))` blocks drop out (no `inetntop`/`ipv6string` debug formatting in `add_to_cache`). `AFINET=AF_INET6` → `struct IN_ADDR` = `in6_addr` (16 bytes); `WHOSTENTP` tests all 16 `s6_addr` bytes; `make_cache`'s address-count loop + contiguous-block fill use `sizeof(struct IN_ADDR)` = 16. `MAXCACHED 512`, `MAXADDRS`/`MAXALIASES 35`.
  - **Faithfulness notes.** `make_cache` **steals** the ResRQ's alias pointers (`rptr->he.h_aliases[n]` → cache, source nulled) and `h_name` (nulled after copy) — preserved exactly, so the L1 asserts the source ResRQ fields go NULL. The `*t++ = s; s += sizeof(IN_ADDR)` two-array block-fill is reproduced with raw-pointer `t.add(1)`/`s.add(IN_ADDR_SIZE)` walks; `bzero` → `write_bytes(_,0,_)`. The `ttl < 600` clamp bumps `reinfo.re_shortttl` and forces 600; `expireat = timeofday + ttl`. `add_to_cache`'s `++incache > MAXCACHED` LRU evict walks `cachetop` to the tail (`while !(*cp).list_next.is_null()`) and `rem_cache`s it. `MyMalloc` declared returning `*mut c_char` to match dbuf.rs's decl (avoids the redeclared-signature warning), cast at each use.
  - **Classification.** Utility/leaf — not in `msgtab`. → **L1 only**: no client/server command path under the locked config (`NO_DNS_LOOKUP` on → the resolver is inert, the cache never populates at runtime). End-to-end DNS coverage arrives when the resolver proper is ported under a fixture nameserver.
  - **L1.** `ircd-testkit/tests/res_cache_build_diff.rs` — two fully-isolated `World`s of fn-pointers + global base pointers (`cachetop`/`hashtable`/`incache`/`cainfo`/`reinfo`/`timeofday`), copying `res_cache_lookup_diff.rs`: LIVE (`#[link_name]` → the Rust port over the res_link.o globals) vs CREF (`cref_*` over the renamed oracle). 8 tests with inverse/round-trip invariants — build-by-name T_A (entry contents + linkage + `ca_adds`/`incache` deltas + the steal inverse) and T_AAAA (flag), the short-ttl clamp (`ttl<600`→600 + `re_shortttl`) with the ≥600 inverse, dedup-by-name (T_A HIT → existing entry, no add) and dedup-by-addr (T_PTR HIT via `find_cache_number` → name merged as alias, no add), multi-addr contiguous-block build (distinct pointers, correct bytes), `add_to_cache` LRU boundary at MAXCACHED (incache pre-set near the cap; evict observed via `ca_dels` delta + list length) with the under-cap inverse, and both-bucket linkage (a single add is findable via both `find_cache_name` and `find_cache_number`). `timeofday` pinned to a fixed value per scenario so `expireat` is deterministic; process-global `LOCK` serializes the parallel `#[test]` threads. Zero diff. `make_cache`/`add_to_cache` confirmed `T` (Rust-defined) in `target/debug/ircd`.
  - **No L2/S2S.** Resolver inert under `NO_DNS_LOOKUP` (cache never populates at runtime); no `IsServer`/remote-field path. `golden_s_dns` (oper DNS stats / `DNS l` empty dump / non-oper 481) + `golden_registration` confirm the still-C resolver calls the Rust build/insert core without regression.

- **2026-06-05 — P6u (res.c request-queue management core) merged.** First bite into the `res.c` resolver proper — the only DNS file still in C after the P6q–P6t hostent-cache cluster — ported bottom-up from the outstanding-DNS-request queue floor.
  - **Cluster choice.** `add_request`/`rem_request`/`make_request`/`find_id`: the connected component over the file-statics `first`/`last` (the request queue heads) + `reinfo.re_requests`. No socket I/O, no answer parsing (none call `resend_query`/`query_name`/`do_query_*`/`proc_answer`), so it isolates cleanly as the floor the rest of the resolver builds on. The public queue walkers `del_queries`/`timeout_query_list` stay C this bite (`timeout_query_list` pulls in `resend_query` → the socket half).
  - **Guard / seam.** `res_link.o` gains `-DPORT_RES_REQ_P6u`. A new three-way `RES_REQ_LINKAGE` macro (parallel to `RES_CACHE_LINKAGE`): `extern` under `PORT_RES_REQ_P6u` (res_link.o — the four bodies `#ifdef`'d out, Rust defines them, the still-C callers `del_queries`/`timeout_query_list`/`gethost_*`/`do_query_*`/`get_res` resolve at link), blank under `RES_CACHE_EXPOSE` (cref oracle — GLOBAL so `cref_add_request`/`cref_rem_request`/`cref_make_request`/`cref_find_id` exist for L1), else `static`. The `first`/`last` queue heads flip from `static ResRQ *last, *first;` to `RES_DNS_STATIC` (already exposed in res_link.o via `PORT_DNS_M_DNS` + the cref oracle via `RES_CACHE_EXPOSE`) so the Rust port and the still-C walkers share one storage — exactly the P6r/P6t cache-globals pattern.
  - **Config-resolved body.** AFINET=AF_INET6 → `he.h_addrtype = AF_INET6`; DEBUG off → the `Debug(...)` traces in `rem_request`/`timeout_query_list` drop out; `MyFree` is a macro → `libc::free`; `MyMalloc`+`bzero` → `MyMalloc` + `ptr::write_bytes(…,0,…)`; the `bcopy(lp, &cinfo, sizeof(Link))` → `ptr::copy_nonoverlapping` (or a zero-fill when `lp==NULL`).
  - **Faithfulness notes.** `rem_request`'s `for (rptr=&first; *rptr; r2ptr=*rptr, rptr=&(*rptr)->next)` rendered as a raw `*mut *mut reslist` walk advancing `r2ptr`/`rptr` at the bottom so the `last==old → last=r2ptr` tail fix-up matches; the post-loop free order (`he.h_name`, the `MAXALIASES` `h_aliases[]`, `name`, the node) preserved. `make_request` sets `next=NULL` before `add_request` (which sets it again) verbatim. `add_request(NULL)` returns -1 (the C no-op) before touching the queue.
  - **Classification.** Utility/callees — neither in `msgtab`. → **L1 only, NO L2, NO S2S.** The resolver is inert under `NO_DNS_LOOKUP` (no client/server command path populates the queue at runtime; end-to-end queue coverage arrives under a fixture nameserver when the socket/answer half lands).
  - **L1.** `res_request_diff` — two fully-isolated `World`s of fn-pointers + `first`/`last`/`reinfo` base pointers (LIVE `#[link_name]` → the Rust port over the res_link.o globals; CREF `cref_*` over `cref_first`/`cref_last`/`cref_reinfo`). Positive + inverse/round-trip: `make_request` seeds every default field & appends (NULL `lp` zeroes `cinfo`); `add_request` arrival ordering + `re_requests` increments, and `add_request(NULL)` → -1 with the queue untouched; `find_id` hit (first/middle/tail) / miss / misses-after-its-target-is-`rem_request`'d; `rem_request` head/middle/tail unlink with `last` repointed on tail removal + the node freed; a drained queue is `first==last==NULL` and reusable by a fresh `make_request`. `timeofday` pinned (deterministic `sentat`); a process-global `LOCK` serializes the parallel `#[test]` threads over the shared queue. Zero diff over 8 tests.
  - **L2/S2S.** None (see Classification). `cargo test -p ircd-golden` `golden_s_dns` (2) + `golden_registration` (2) green — the still-C resolver `init_resolver`/walkers call the Rust queue primitives at boot without regression. 0 warnings.
  - **Still C in `res.c`.** `del_queries`/`timeout_query_list` (the public queue walkers) + the resolver socket/answer half: `send_res_msg`, `do_query_name`/`do_query_number`, `query_name`, `resend_query`, `proc_answer`, `get_res`, `gethost_byname`/`gethost_byname_type`/`gethost_byaddr`, `init_resolver`, `cres_mem`, `bad_hostname`.

- **2026‑06‑05 — P6v (`res.c` queue walkers `del_queries`/`timeout_query_list`) merged.** The second bite of the `res.c` resolver proper, built on the P6u queue core — the reaper/timeout layer that walks the outstanding-DNS request queue (`first`/`last`).
  - **Cluster choice.** Continuing bottom-up after the P6u request-queue floor: `del_queries`/`timeout_query_list` are the two queue *walkers* the PLAN names as the next nameable unit. `del_queries` deps are purely Rust (`rem_request` P6u) + the shared `first` global → fully portable. `timeout_query_list` additionally calls the still-C `resend_query` (the resend/socket path) — the one inverse-direction dependency.
  - **Guard / seam.** `res_link.o` gains `-DPORT_RES_WALK_P6v` (build.rs); `res.c` wraps both bodies in `#ifndef PORT_RES_WALK_P6v`. New `RES_RESEND_LINKAGE` macro: `extern` under `PORT_RES_WALK_P6v` (res_link.o exposes `resend_query` as a global so the Rust `timeout_query_list` links against it), `static` otherwise — the cref oracle keeps `resend_query` static, and `cref_timeout_query_list` resolves to the objcopy-renamed local `cref_resend_query` inside libcref.a (self-contained: `resend_query` has no out-of-TU caller). Both `del_queries`/`timeout_query_list` are already public (res_ext.h) → the still-C callers (`s_bsd.c:1361/3288`, `ircd.c:580/1193`) and the Rust `list.rs:free_conf` resolve to the Rust defs at link.
  - **Config‑resolved body.** DEBUG off → the `Debug(())`/`myctime` traces drop out of both functions. USE_IAUTH on → the ASYNC_CLIENT exhausted arm calls `sendto_iauth("%d d", cptr->fd)`. `ClearDNS(x)` = `(*x).flags &= ~FLAGS_DOINGDNS` (0x100; `flags` is c_long). The `cinfo.flags` switch: ASYNC_CLIENT=0 (iauth+ClearDNS), ASYNC_CONNECT=1 (sendto_flag SCH_ERROR), default (ASYNC_CONF=2 etc.) → reap only. **Platform note:** `reslist.retries`/`resend` render as `c_char` = **u8** here, so `--rptr->retries` is ported as `wrapping_sub(1)` (faithful to C's modular char; the `<= 0` test then means `== 0`, matching unsigned C on the same platform) — panic-free even though the live values (1‑3) never underflow.
  - **Faithfulness.** `timeout_query_list` mirrors the C `for (rptr=first; rptr; rptr=r2ptr)` walk with `r2ptr` captured *before* `rem_request` frees the node; the exhausted arm uses `continue` (skipping the `next` update) exactly as C; `next` seeded 0 with the `!next || tout < next` min; return `(next > now) ? next : now + AR_TTL`. `resend_query` short-circuits on `resend == 0`, so the L1 retry test exercises the call with no socket I/O.
  - **Classification.** Utility/callees — neither is in `msgtab` (callers: s_bsd.c registration/cleanup, ircd.c io_loop/exit, list.rs free_conf). **L1 only.**
  - **L1.** `ircd-testkit/tests/res_walk_diff.rs` (modeled on `res_request_diff.rs`): two fully-isolated `World`s (LIVE `#[link_name]` → the Rust port over res_link.o globals; CREF `cref_*` over the renamed oracle), each carrying base pointers to `first`/`last`/`reinfo` + the four fn-pointers. 9 tests, positive + inverse: `del_queries` head/middle/tail match, absent-cp no-op, all-match drain (`first==last==NULL`); `timeout_query_list` future-dated survive (returns min `tout`), exhausted ASYNC_CONF (no-notify reap → `now+AR_TTL`), exhausted ASYNC_CLIENT (ClearDNS clears the DNS bit but leaves an unrelated bit, diffed on two per-world clients), exhausted ASYNC_CONNECT, retry back-off (`retries--`, `sentat=now`, `timeout` 4→8, `resend_query` no-op via resend=0), and a mixed survivor+reaped+retried walk — return value, surviving ids, per-node `retries`/`sentat`/`timeout`, and `re_timeouts` all diffed. `timeofday` pinned; `adfd`/`cref_adfd=-1` so `sendto_iauth` no-ops; `svchans` BSS-zero so `sendto_flag` no-ops; process `LOCK` serializes the parallel `#[test]` threads. **Zero diff.**
  - **No L2/S2S.** The resolver is inert under `NO_DNS_LOOKUP` — the request queue never populates at runtime, so there's no client/server command path that reaches these walkers. `golden_s_dns` (2) + `golden_registration` (2) stay green, confirming the still-C resolver calls the Rust walkers without boot regression. End-to-end DNS coverage arrives when the answer parser + socket I/O half is ported under a fixture nameserver.
  - **Still C in `res.c`:** only the answer parser + socket I/O half — `send_res_msg`/`do_query_name`/`do_query_number`/`query_name`/`resend_query`/`proc_answer`/`get_res`/`gethost_byname`/`gethost_byname_type`/`gethost_byaddr`/`init_resolver`/`cres_mem`/`bad_hostname`.
