code wiki / _hdl_build / nx_docportal_search_sticky_gate.nx
nx_docportal_search_sticky_gate.nx
buildroot/runtime/_hdl_build/nx_docportal_search_sticky_gate.nx
about
nx_docportal_search_sticky_gate.nx -- does the web-shard cache KEEP its working handle when a reopen
comes back EMPTY, and does it refuse to MEMOISE the signature of that empty open?
SUBJECT: nx_docportal_search_seg.nx dss_open_maybe_cached, the 2026-08-22 REFUSE-BEFORE-DESTROY fix:
let nh: *i64 = ss_open2(prefix, 1)
if nh[0] == 0 { ss_close(nh); sys_munmap(cur as *u8, DSC_SIGBUF); return dsc_handle }
Until this gate existed that fix was proven BY CONSTRUCTION only. MEASURED 2026-08-22 (pre-fix): one
transient empty manifest read destroyed the good handle, installed an empty one, memoised it against the
current signature, and every request served total=0 for ~8 minutes on a 1.8 GB / 17-segment index.
HOW THE REAL CACHE PATH IS DRIVEN AGAINST A FIXTURE: the cache engages ONLY when the prefix is EXACTLY
"knowledge/store/dp-web-pub-" (dsc_web_prefix_is compares the whole literal; dss_prefix builds it from the
domain "web"), and that path is RELATIVE. So the gate sys_chdir's into /tmp/<gate>/ and builds
knowledge/store/ underneath it: the relative prefix then resolves into the fixture tree and every line of
the production open / refresh / guard code runs UNCHANGED -- no test hook, no prefix-override static,
no production path touched. The entry CWD (sys_getcwd) is restored before the verdict so gv_journal's
relative knowledge/status/harness.jrnl lands where every other gate's does.
FIXTURE-RATCHET LAW: all scratch under /tmp/nx_docportal_search_sticky_gate/, created by sys_mkdir at
SETUP (a teardown does not run when a run crashes); setup is IDEMPOTENT (the manifest is truncated before
the seed commit, so ss_commit -- max_segid+1 under the plane lock -- re-derives seg-1 on every run instead
of appending seg-N; stale segment FILES from an earlier run are overwritten or simply never named).
TEETH (each states itself; the verdict note does not recount them):
T0 accessor reports UNOPENED (-1) before any open (the -1 arm of dss_web_index_segments is real)
T1 fixture-reached-condition: healthy fixture opens with segments == 1 (a fixture that cannot fail is not a test)
T1b fixture-reached-condition: the cache memoised the HEALTHY manifest signature (size>0, == stat)
T2a fixture-reached-condition: the emptied manifest CHANGED the signature, so refresh reaches the reopen branch
T5 anti-vacuity: a direct ss_open2 of the emptied manifest returns ns==0 (T2 tests the condition, not a no-op)
T2 refresh-keeps-working-handle: refresh over the empty manifest -> still ready, SAME handle, still 1 segment
T3 signature-not-memoised: dsc_sig (read via dss_web_cache_sig) still equals the healthy signature, not the empty one
T3b WEAK-regression (passes on the pre-fix code too -- named so nobody reads it as discrimination)
T4 neg-control-reopen-on-real-change: a manifest naming REAL new segment bytes DOES replace the handle (1 -> 2)
T4b the replacement handle really serves the new bytes (ss_hget doc:2 == 1), so T4 counted segments, not names only
THIRD STATE: every setup step (mkdir / chdir / prefix / seed) is a gv_need; if one is missing the teeth are
NOT run and the gate ends SKIP, never RED -- a broken fixture must not read as a broken subject.
WHAT THIS GATE REQUIRES: the two accessors dss_web_index_segments / dss_web_cache_sig in
nx_docportal_search_seg.nx (round2 patch A1). Without them it does not compile -- by design: the gate
reads STATE, and the state is only readable through them (no importer in the estate reads another
module's static; measured over buildroot/runtime, every cross-file mention of a static is a comment).
license_tier: ORIGINAL Writes only under /tmp/nx_docportal_search_sticky_gate/. No hw writes (Rule 26).
dependencies 3 imports · 0 importers
imports: nx_docportal_search_seg.nxnx_gate_verdict.nxnx_syscalls.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
| 47 | const STK_MANIFEST_SMALL: i64 = 16 // "seg-1" + LF is 6 bytes; 16 is one cache line, never a cap reached |
| 48 | const STK_LF: i64 = 10 // the manifest row terminator ss_commit_body writes (nb[no] = 10) |
| 49 | const STK_PUT: i64 = 1 // ss_add kind=1 = put (the kind every seeding gate uses) |
| 50 | const STK_SEG1: i64 = 1 // first seeded segment id |
| 51 | const STK_BOX: i64 = 16 // one out-box (ptr or len) -- a page-rounded mmap; the value is the box size, not a cap |
| 52 | const STK_SEG2: i64 = 2 // second seeded segment id (the neg-control's REAL change) |
functions
| 54 | func stk_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } called by 1: stk_seed |
| 55 | func stk_path(out: *u8, a: *u8, b: *u8) -> i64 called by 1: main |
| 63 | func stk_seed(prefix: *u8, segid: i64, key: *u8, text: *u8) -> i64 |
| 69 | func stk_show2(label: *u8, an: *u8, a: i64, bn: *u8, b: i64) -> i64 |
| 75 | func stk_exists(path: *u8) -> i64 |
| 83 | func main(argc: i64, argv: *i64) -> i64 |