Skip to content

Bytecode carries the fetch body (fixes #143) - #145

Merged
howlcipher merged 1 commit into
mainfrom
issue143-fetch-body
Oct 12, 2026
Merged

howlcipher merged 1 commit into
mainfrom
issue143-fetch-body

Conversation

@howlcipher

Copy link
Copy Markdown
Owner

Fixes #143

This changes the flip criteria, with the Owner's approval. William Elias signed off: TestProdFlipCriteriaLock/fetch_body and the nested_multi_effect_* conformance cases now lock in "the bytecode fetch body is sent" in place of "the body is skipped".

What changed

  • AST bytecode and HFIR bytecode (-compile-hfir-bc + -run-bc) now carry the fetch body. The interpreter already did.
    • Both compilers lower (fetch url method body) through the shared helper bytecode.FetchWithBody to: url, method, body, CONVERT to_string, HTTP_REQUEST 1, LOAD_CONST "body", MAP_GET.
    • The result is the response body bytes, the same value FETCH returns.
    • No new opcode, no new capability (network), no artifact-format change.
    • A fetch without a body is still the bare FETCH.
  • Tests updated to the new behavior:
    • TestProdFlipCriteriaLock/fetch_body now requires HTTP_REQUEST, no body-less FETCH, and the body kept.
    • The hfir unit test is now TestLowerToBytecodeFetchSendsBodyOperand, which pins the exact instruction sequence.
    • TestHFIRBytecodeFetchOperandOrder now expects the PUT body to be received.
    • nested_multi_effect_*: the only body fetch is in a dead while false branch.
      • Its marker is renamed to phase2d-dead-branch-body.
      • Both compilers must now load the body.
      • The server still sees no body, because the branch never runs.
  • New test TestIssue143FetchBodyEveryPath uses a local httptest server only (no external network).
    • It runs a POST with a string body, a PUT with a numeric body, and a GET with no body on the interpreter, AST bytecode and HFIR bytecode.
    • All three paths must produce the same stdout and send the same requests.
    • With network denied, the bytecode paths must send nothing.

Journal: docs/journals/2026-10-12_issue143_fetch_body.md

Caveats

  • A failed fetch with a body reports http_request failed (opcode HTTP_REQUEST) instead of fetch failed. The error codes are the same.
  • The body is stringified with the VM's to_string. Strings and numbers match the interpreter's fmt.Sprint. Lists, dicts and bytes may format differently.
  • No production -compile-bc/HFIR flip. Write flagged http_server bytecode and stop #90 stays Partial. experiments/token-efficiency-heldout-v2 is untouched.

Verification

  • gofmt -l is empty.
  • go build ./... and go vet ./... pass.
  • go test ./...: one unrelated failure, apps/task_api. A connection reset on localhost:8091 was followed by a 10-minute timeout. No other test uses that port and the app has no fetch. The package passes when rerun on its own.
  • go test ./experiments/... passes.
  • All three run.sh pass: v0 60/60 ×4, heldout-v0 howl 30/30 and others 32/32, heldout-v1 34/34 ×4.

@howlcipher howlcipher left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: merge-ready (COMMENT-only; APPROVE is unavailable for this account). Reviewed head 2c96eaa9522421b56549c6261e58685fb1ebcb5c against base be5fc0e2 in one fresh local checkout.

1. Flip lock scoped to fetch_body. In prod_flip_criteria_test.go the only change is inside t.Run("fetch body") (18 +/- lines). The old checks were "has FETCH" and "body string absent". The new ones are "has HTTP_REQUEST", "no body-less FETCH" and "body string present", all on both the HFIR-lowered and AST programs. The opcode allow-list, the OpFetch registry pin, the transport subtests, try/match control edges, html/attr_escape, regex, time_now and sleep are all untouched. No CLI or default files changed, so there's no prod -compile-bc/HFIR default flip and #90 stays Partial. All 25 TestProdFlipCriteriaLock subtests pass.

2. No new opcode, capability or grant. FetchWithBody emits the existing CONVERT, HTTP_REQUEST 1, LOAD_CONST and MAP_GET. The HTTP_REQUEST registry entry stays Capability: network, and the hfir unit test now asserts that. The VM gates it at runtime the same way as FETCH (the effect-gate conformance already covers OpHttpRequest), and the receipt already handles HTTP_REQUEST IntOperand==1. The profile scan is AST/HFIR-level on the fetch node and is unchanged. TestOpcodeCapabilityDrift passes.

3. TestIssue143FetchBodyEveryPath runs the real binary on three paths: -run (interpreter), build + -run-bc (AST bytecode), and -compile-hfir-bc + -run-bc (HFIR). It runs them against an httptest server that records method and body. It asserts the exact requests POST hello | PUT 42 | GET and the echoed stdout on each path, and with no network grant it asserts that both bytecode paths send zero requests. Passes.

4. Updated tests are justified. The hfir operand test now pins the exact 7-instruction sequence and its operands, so it is stricter than before. In nested_multi_effect, the body fetch sits in a while false branch, so it is now compiled but never runs. The rename to phase2d-dead-branch-body is just the old marker made accurate, and the conformance test still asserts bodies == 0 and no leak of the marker. TestHFIRBytecodeFetchOperandOrder now expects the PUT body to arrive and checks that AST and HFIR emit the same shapes. These tests were changed to reflect the new behaviour, not loosened.

5. Caveats, neither blocking.

  • A failed fetch with a body reports http_request failed. Reproduced: with a body you get IO_ERROR … opcode HTTP_REQUEST, "http_request failed: …"; without one you get opcode FETCH "fetch failed". The interpreter says fetch failed in both cases. The error code is the same, so this is cosmetic. Could be a follow-up.
  • List/dict body formatting. I probed this against a local server on all three paths with (list 1 2 3), (list "a" "b"), (dict ("k" 1) ("z" 2)), true and 1.5. The bodies received were byte-identical on every path: [1 2 3], [a b], map[k:1 z:2], true, 1.5. For these types the difference doesn't happen. Bytes and nested values weren't probed. Optionally, add a list/dict case to the new test to lock this in.

6. #142 arity/drift tests: TestIssue141EveryArityEntry, TestIssue141MotokoArityRepros (29 cases), TestIssue141ArityFuzzNoPanic, TestIssue139FetchArityFailsClosed, TestArityTableCoversRegistry, TestArityMessages, TestArityAcceptsWellFormed, TestOpcodeCapabilityDrift, TestCompareToCanonicalDetectsDrift, the call-arity tests and TestLoweredHFIRABIConformance all pass (exit 0).

7. Build: CI build passes on this head. gofmt -l is empty. Full go test ./... exits 0, and that includes apps/task_api (2.7s) and apps/status_api (2.5s). They didn't flake this run, so no isolated rerun was needed.

8. Hard nos intact: nothing under the 4d74dbcf tip-lock evidence is touched, there's no live LLM and no H1 PASS/KILL claim, and no UI/DOM changes.

Nits (non-blocking):

  • The denied-network check in the new test covers only the two bytecode paths. An interpreter denial assert would make it symmetric.
  • Consider making the HTTP_REQUEST-with-body error say fetch failed for parity.

@howlcipher
howlcipher marked this pull request as ready for review October 12, 2026 19:19
@howlcipher
howlcipher merged commit 576c87c into main Oct 12, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bytecode silently drops the fetch body argument

1 participant