Fix truncated /api/safety-contact response (988 crisis-line) #96

Merged
will.anderson merged 1 commits from fix/safety-contact-truncation into hotfix/elc-source-typos 2026-07-21 17:16:20 +00:00
Owner

POST/GET /api/safety-contact returned JSON truncated to the file's byte length. Root cause: el runtime response writer uses fs_read's thread-local _tl_fs_read_len as the send length (el_runtime.c:1409); both handlers fs_read then return a longer string. Soul-source fix: POST checks fs_write return (no fs_read); GET resets the thread-local with a no-op fs_read after the file read. Verified: full valid JSON, GATE PASS (both sections), genesis survives, floor compiled in. Stacks on #95 (genesis fix). Do NOT merge — mechanics agent rebuilds from HEAD. Ship-soul: see commit.

POST/GET /api/safety-contact returned JSON truncated to the file's byte length. Root cause: el runtime response writer uses fs_read's thread-local _tl_fs_read_len as the send length (el_runtime.c:1409); both handlers fs_read then return a longer string. Soul-source fix: POST checks fs_write return (no fs_read); GET resets the thread-local with a no-op fs_read after the file read. Verified: full valid JSON, GATE PASS (both sections), genesis survives, floor compiled in. Stacks on #95 (genesis fix). Do NOT merge — mechanics agent rebuilds from HEAD. Ship-soul: see commit.
will.anderson added 1 commit 2026-07-21 17:14:11 +00:00
Saving the 988 crisis-line contact returned truncated, unparseable JSON —
cut mid-"set_at" at the file's byte length (e.g. 178 of a 218-byte
response). The contact written to disk was complete; only the HTTP response
was clipped, so a real customer's crisis-contact save came back corrupt.

Root cause is in the el runtime's response writer, not a handler buffer:
fs_read stores the file's byte count in a thread-local (_tl_fs_read_len)
for binary-safe file serving, and the response writer uses that length when
non-zero instead of strlen(body) (el_runtime.c:1409). Both safety-contact
handlers call fs_read (the POST read-back verify; the GET file read) and
then return a LONGER wrapped JSON string, so the response is capped to the
file size.

Soul-source fix (no runtime change needed):
- POST: verify persistence via fs_write's return (1 = all bytes written)
  instead of an fs_read read-back — removes the fs_read, so nothing caps the
  response.
- GET: fs_read is required, so reset the thread-local after it with a no-op
  fs_read("") (fs_read zeroes the length before it opens a path) so the
  wrapped response is sent in full.

Verified: POST (crisis-line + custom) and GET now return complete, valid
JSON (parses cleanly, full contact incl. set_at). Regenerated dist/soul.c +
dist/safety.c (3GB RSS watchdog, release el_runtime v1.0.0-20260501).
Full suite still green: verify-soul-contract GATE PASS (PRESENCE +
IMMUTABILITY), genesis boot survives (/health 200, no segfault), bounded-
persona floor still compiled in.

NOTE: the underlying runtime leak (any handler that fs_reads then returns a
longer string) is worth a proper fix in el_runtime.c (use the max of
strlen and _tl_fs_read_len) so this class can't recur.
will.anderson merged commit b784750f69 into hotfix/elc-source-typos 2026-07-21 17:16:19 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: neuron-technologies/neuron#96