Skip to content

Records/JSON sugar (pure) + heldout-v3 harness and before/after results - #154

Merged
howlcipher merged 2 commits into
mainfrom
records-json-slice
Oct 12, 2026
Merged

howlcipher merged 2 commits into
mainfrom
records-json-slice

Conversation

@howlcipher

Copy link
Copy Markdown
Owner

Records/JSON slice measured blind on held-out v3 (#152). Two commits.

Phase 1 (e18ad5d, written blind: heldout-v3 not opened): pure desugar records/JSON forms in internal/ast/desugar_records.go

  • (rec k v …) record literal (name or string keys), (get m k …) nested path read (int literal = list index), (put m k … v) nested in-place write (creates missing maps)
  • (read_json p) / (write_json p v): one call each, only through read_file/write_file, so they need exactly filesystem and fail closed without it (tested on all 3 paths, including read-only grant → write denied, no output)
  • (pluck lst k), (select lst k …), (group_by x lst key), (merge a b …) (shallow, new dict)
  • encode_json already writes sorted keys, compact
  • No new opcode or capability. Arity entries, construct table, drift list, three-path parity tests (interpreter / governed -run-bc / HFIR bytecode), Go/JS UNSUPPORTED_ON_TARGET with no output written, governed scan fails closed through every form
  • parse_json now runs under -run, using the same decodeJSONValue. Stabilization parity manifest: interpreter UNSUPPORTED → PASS for fixture 10
  • Every existing run.sh / run_postcf.sh (v0, heldout-v0/v1/v2) re-run: outputs byte-identical

Phase 2 (52d8475): experiments/token-efficiency-heldout-v3 harness that mirrors v2. Python/JS/Go refs; HowlFrame baseline (main a6bb53b6 features, 35/35 also on an a6bb53b6 binary) and postrec/; results pinned; no gaps; specs untouched. Full tables and an honest read are in REPORT.md.

HowlFrame vs (cl100k) median ratio total ratio W/T/L
Python 1.282 → 1.186 1.332 → 1.249 3/0/32 → 8/0/27
JS 1.103 → 1.024 1.145 → 1.074 13/0/22 → 16/0/19
Go 0.460 → 0.434 0.467 → 0.438 35/0/0 → 35/0/0

Records (17): total vs Python 1.244 → 1.118. HowlFrame still loses to Python overall.

Findings (not fixed): an HFIR miscompile where encode_json(parse_json s) prints raw s when a later print argument has a let (also on main), and encode_json loses int precision above 2^53.

Hard nos: no production -compile-bc/HFIR flip; #90 stays Partial. gofmt clean; go build/vet/test ./... pass.

…pluck, select, group_by, merge; parse_json under -run

Phase 1 of the heldout-v3 records slice, written blind (heldout-v3 specs not opened).
- Desugar-only (internal/ast/desugar_records.go): no opcode, no capability.
  read_json/write_json reach the filesystem only via read_file/write_file.
- Arity entries + construct drift; Go/JS reject with UNSUPPORTED_ON_TARGET;
  governed scan fails closed through every form.
- Three-path parity tests (interpreter, -run-bc governed, HFIR bytecode).
- parse_json runs under -run (same decodeJSONValue); parity manifest updated.
- All existing run.sh / run_postcf.sh outputs byte-identical.
…rame baseline (a6bb53b features) and post-records sets, pinned results

Specs untouched. HowlFrame/Python cl100k total 1.332 -> 1.249, median per-task
1.282 -> 1.186, W/T/L vs Python 3/0/32 -> 8/0/27. Records 1.244 -> 1.118.

@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). No blockers. One scope recommendation and a few nits below.

Reviewed head 52d84756ae8dcea0ca0d7336e501d6a1a61a95b6 (base a6bb53b6), using one fresh clone plus a worktree for the base. Tokens counted with tiktoken 0.8.0, cl100k_base.

1. Blindness: the order is consistent, but it can't prove the author never looked

  • History is linear: a6bb53b6 (#152 merge, 17:42 ET) → e18ad5d (17:53, parent a6bb53b6) → 52d84756 (18:02, parent e18ad5d).
  • e18ad5d touches only language and test files (12 files) and nothing under experiments/. Its only "v3" mentions are the label "heldout-v3 records slice" in comments and docs. It references no task IDs (x01–x35), fixture names or file contents.
  • 52d84756 touches only experiments/token-efficiency-heldout-v3/** (195 files). No language change came after the solutions were written, so no forms were added post-blind.
  • What this doesn't prove: the specs were public on main 11 minutes before e18ad5d, so commit order can't rule out that they were read. The circumstantial evidence points to blind: 4 of the 9 forms (pluck, select, group_by, merge) plus put are used by zero v3 references. Usage across the postrec set is read_json 20, get 18, write_json 16, rec 6. If the author had been fitting to v3, they wouldn't have built five forms the set never uses. Verdict: consistent with blind, not proven.

2. Baseline only uses features on a6bb53b6

I built the base binary from a6bb53b6 and ran heldoutv3verify -langs howl on solutions/howl: 35/35 pass the oracle. As a control, postrec/ on the base binary gets 12/35, which confirms the 23 rewrites really depend on the slice.

3. Fail-closed (governed build, -run-bc, grants filesystem:read=in, filesystem:write=out)

  • read_json works inside the grant. Denied (CAPABILITY_DENIED at READ_FILE) for: an absolute path outside the grant, in/../secret traversal, a symlink from in/ to secret/, and no -allow-caps at all.
  • write_json is denied outside the grant, on out/../secret traversal, and with a read-only grant. Nothing was written to secret/ or out/.
  • Malformed JSON gives a clean RUNTIME_ERROR parse_json failed: unexpected EOF, with no panic. Piping malformed input through write_json leaves no partial file, because the parse fails before WRITE_FILE runs.
  • -required-caps on the artifact reports filesystem at both the READ_FILE and WRITE_FILE sites.
  • Go and JS give UNSUPPORTED_ON_TARGET for all 9 forms. ARITY_ERROR is checked for get, put, read_json (0 and 2 args), write_json, pluck (1 and 3), select, group_by and merge. Odd-length rec gives a clean error.
  • parse_json under -run: a let-bound string now decodes, matching both bytecode paths, and needs no capability. read_json/read_text under -run are still capability denied: filesystem. Governed still forbids literal (parse_json Any "..."), the same as on base. No capability is widened.

4. pluck, select, group_by, merge (and put)

No v3 reference uses them, and put is also unused. Across the earlier sets, only 3 solutions have the map_list x l (map_get x "k") shape that pluck would save a couple of tokens on (heldout-v1 v29/v30, heldout-v2 w33). Nothing uses a select, group or merge shape. pluck overlaps map_list, select overlaps map_list+dict, and group_by sits next to count_by. Recommendation: hold pluck/select/group_by/merge. Split them into a follow-up PR, or mark them experimental in the construct table, until a frozen set measures them. Each one is permanent surface (arity, drift, target rejection, docs). They're pure desugar and tested, so I'm not blocking on this. Keep put because it pairs with get, but it's unmeasured too.

5. Results reproduce exactly

  • With heldoutv3verify (PR binary) all 4 languages pass, 140/140, and postrec is 35/35. verification.json is identical to the pin.
  • Rerunning count_tokens.py (baseline and postrec) and compare_postrec.py leaves the tree clean: every pinned results file comes out byte-identical.
  • vs Python: median 1.282 → 1.186, total 3783 → 3547 / 2840 = 1.332 → 1.249, W/T/L 3/0/32 → 8/0/27. Records: 1828 → 1643 / 1469 = 1.244 → 1.118. JS 1.103 → 1.024, Go 0.460 → 0.434, matching REPORT.md.
  • Attribution: every task whose count changed uses at least one new form. No task changed without one.
  • Sandbagging: I read the worst baseline ratios (x34 1.92, x32 1.78, x24 1.76). They're already tight (one while loop each; x24 does inline gcd/lcm with % and //), and I found no padding I could golf out with main features.
  • Earlier frozen pins: the PR touches nothing under experiments/ outside heldout-v3. go test -count=1 ./experiments/... passes, 7 packages ok.

6. Parity across the 3 paths (interpreter, governed -run-bc, HFIR -run-bc)

Identical output on all 3 paths for: get on a missing key → null, nested get m "a" "b" 1 "c" (via read_json), put creating intermediate maps, put on a non-record (the same TYPE_ERROR map_set expected dict everywhere), empty group_by → {}, group_by order, merge conflict (later wins, inputs untouched), pluck/select with missing keys, unicode é😀 round-trip, and print of 2^53+1 (exact).

  • encode_json of 2^53+1 gives ...992 on the interpreter and AST bytecode. This is the filed precision issue.
  • The filed HFIR encode_json miscompile reproduces: HFIR prints the raw text. It does not affect any v3 result, because the verifier only uses the governed AST build.
  • Separate finding, on base too, not caused by this PR: HFIR diverges from AST bytecode on 8/35 v3 programs, both baseline and postrec: x02, x03, x07, x08, x10, x11, x15, x27. On x10 and x11 HFIR lowering fails (map_get requires a target name and key). On x02 it gives a runtime TYPE_ERROR. On x03 it gives a spurious CAPABILITY_DENIED at WRITE_FILE inside the grant. The base binary shows the same 8, so this is pre-existing. Please file it as a separate issue, broader than the encode_json one. It also strengthens the case against any HFIR default flip.

7. CI and tests

CI build is green. gofmt -l is empty, go vet ./... exits 0, and go test ./... exits 0 with no apps/* flake this run. go test -count=1 -run 'TestProdFlipCriteriaLock|Arity|Drift|Fuzz|Record' over root and internal exits 0.

8. Hard nos

No prod -compile-bc/HFIR default flip. No lock or tip files touched, and no tip-lock 4d74dbcf. No live LLM run, and no H1 PASS/KILL claim.

Nits (non-blocking)

  1. The doc says get gives nil for a missing key, but that's only true at the last step. A missing intermediate key gives TYPE_ERROR map_get expected dict, got <nil> on all 3 paths. Either document this or make get nil-propagate.
  2. Statically typed homogeneous lists mean (rec a (rec b (list 10 (rec c "x")))) is rejected at check time. Mixed-shape nested records only work via read_json. Worth a line in the construct table.
  3. REPORT.md should say put was also unused on v3, not only the four list forms.
  4. File the 8-task HFIR divergence described above.

@howlcipher
howlcipher marked this pull request as ready for review October 12, 2026 22:08
@howlcipher
howlcipher merged commit d7eb096 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.

1 participant