nx_jobfollow_lib.nx source
↩ module page · 49 lines · 3560 B
1// nx_jobfollow_lib.nx -- THE ONE FOLLOWER of the tools edge's JOB-STARTED promotion.
2//
3// WHY THIS EXISTS. The edge may answer ANY tools/call with `JOB-STARTED id=<n>` instead of the organ's
4// own receipt; the real receipt lands in `_jobs/job_<n>.out`. A client that does not follow that pointer
5// reads the JOB id as if it were the ORGAN's id -- both are spelled `id=`, and every current reader
6// scans for that key UNANCHORED -- and then blames the SERVER for omitting fields the server never sent
7// on that line.
8//
9// MEASURED 2026-09-04, not theorised: nx_content_put_client posted a begin, received
10// `JOB-STARTED id=1788537161`, printed `CP-BEGIN id=1788537161 chunk_raw= nchunks=` and exited 5, while
11// the artifact held the true receipt `CP-BEGIN id=1788537161795845 ... chunk_raw=48402 nchunks=1`.
12// The job id is a PREFIX of the transfer id (both are epoch-derived), which is exactly why the wrong
13// number looked plausible enough to print as a fact.
14// ANCHOR THE PARSE ON THE REPORT LINE: jf_job_id matches the full literal `JOB-STARTED id=`, never a
15// bare `id=`, so a receipt that merely CONTAINS an id cannot be mistaken for a job pointer.
16//
17// ONE IMPLEMENTATION, TWO CALLERS, BY CONSTRUCTION. nx_content_put_client posts three times
18// (begin/chunk/commit); nx_content_get_client posts twice (begin/chunk). That is five call sites which
19// must agree on what a receipt IS. A shared post-then-follow makes disagreement impossible instead of
20// making it something two organs have to remember to do the same way.
21//
22// THE POLL IS NOT A RETRY, AND THE DISTINCTION IS LOAD-BEARING ON A DEGRADED ARRAY. A retry re-issues
23// work whose outcome is UNKNOWN; this waits for a result the server has already ACCEPTED and promised to
24// write. The original request is never re-posted -- which matters because a chunk upload is not
25// idempotent and the hostops lane has filed that a retry loop against the current array is actively
26// harmful. If the budget expires the caller is told the work is STILL PENDING, never that it failed:
27// an unfinished job and a failed job demand opposite actions.
28//
29// NO SILENT CAP: exhausting the poll budget returns 0 and ANNOUNCES the job id, so the caller can read
30// the artifact by hand rather than concluding the organ died.
31// license_tier: ORIGINAL No hw writes (Rule 26).
32import "nx_syscalls.nx"
33import "nx_https_post_lib.nx"
34import "nx_jobfollow_parse.nx" // the pure half: anchored job-id parse, readiness test, buffer arithmetic
35
36func jf_post_follow(store_i: i64, url: *u8, req: *u8, reqlen: i64,
37 out: *u8, outcap: i64,
38 readcap: *u8, readcaplen: i64,
39 polls: i64, sleep_ms: i64) -> i64 {
40 // DELEGATES. The poll loop and the request it builds now live ONCE, in nx_https_post_lib, so that
41 // the shared post path and this explicit entry point cannot drift apart. This function used to
42 // carry its own copy; two implementations of one invariant is the duplicate-ruler defect, and the
43 // whole reason the split into nx_jobfollow_parse exists is so there did not have to be two.
44 // DI4 client half (2026-09-05): a keyed request whose reply the edge lost is re-posted through the ONE
45 // re-issue loop in nx_https_post_lib (replay-by-construction); an unkeyed one still gets its 503 back.
46 let n: i64 = hp_post_json_reissue(store_i, url, 0, 0, req, reqlen, out, outcap, polls, sleep_ms)
47 if n <= 0 { return n }
48 return hp_follow_job(store_i, url, out, outcap, n, readcap, readcaplen, polls, sleep_ms)
49}