feat(vm,checker,backends): application primitives and codegen fixes from the HowlBoard dogfood pass - #37
Merged
Conversation
…handler panic Three changes forced by building HowlBoard, the reference application, on standalone bytecode. Each is independently testable and none is specific to that application. store_keys / STORE_KEYS enumerates a native store's record keys as a sorted list. The keys already existed in bcMemoryStore.records but were unreachable from .howl, so kv_cli, todo_cli, task_api, and HowlBoard each maintained a parallel index record that could silently diverge from the records it indexed. Enumeration is sorted because Go randomizes map iteration and callers list records for display. Dict values may now mix types. Dicts are the language's record literal, and both the VM and the native store carry map[string]any, so a record combining strings, ints, lists, and nested dicts already round-tripped correctly through store_put/store_get; only the analyzer rejected constructing one. Heterogeneous dict literals and map_set writes now widen the element type to any via the existing join helper. Key checks, target-kind checks, and list element homogeneity are unchanged. Two test assertions that encoded the old homogeneity rule are removed, with positive coverage added in their place. Route handlers now fail closed. A panic inside a handler wrote nothing to the ResponseWriter, so Go emitted 200 with an empty body, making a denied capability indistinguishable from a completed request. Handlers that fail before responding now return 500 carrying the structured VMError JSON, and the failure is logged to the VM error stream rather than process stdout. Handlers that already committed a response are left untouched. Verified: go build ./..., go vet ./..., go test ./... (32/32 packages), gofmt clean, plus -validate, -run, -compile-bc, -run-bc, -compile-wasm and TestRepoAnalystStandaloneBytecode. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X427iD1w9dAGLML8wKn9aJ
…nterface
Three defects and one gap, all surfaced by compiling a real web_app rather
than a fixture.
for over an expression silently miscompiled in both the JavaScript and Go
backends. Each read ir.Kids[1].Value, which is empty for anything but a bound
symbol, so (for m (map_get d "missions") ...) emitted "for (let m of )" and
"for _, m := range {". Invalid output was produced with no diagnostic at all,
which is the opposite of the fail-closed target contract.
on_event emitted no trailing semicolon. Automatic semicolon insertion does not
apply before "(", so a following top-level statement was parsed as a call of
the addEventListener result, taking down the whole script.
A web_app's top-level statements are now wrapped in an async IIFE. They
routinely contain awaited calls and a classic <script> has no top-level await,
so generated interfaces failed to parse in the browser. Function declarations
stay at top level, keeping them reachable as globals for inline handlers.
time_now is now lowered by the JavaScript backend. The bytecode VM and Go
backend both support it; web_app programs were rejecting it as an unknown
statement, leaving a browser interface unable to render relative times.
Verified: go build ./..., go vet ./..., go test ./... (32/32 packages),
gofmt clean, and the generated frontend/app.js now parses as a classic script.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X427iD1w9dAGLML8wKn9aJ
HowlBoard is now the largest program built on this toolchain and the one that most exercises it. The README and the Pages ecosystem drawer name it as the canonical reference application and human-facing operations interface, and point at its dogfooding journal as the most detailed record of where this language helps and where it gets in the way. Repo Analyst remains the reference for the in-repository bytecode path. Also corrects three stale claims in apps/task_api/DEVELOPMENT_NOTES.md, stated as corrections rather than silently edited into the original text: req_method does exist, a defun can return a dict through the type_hint annotation rather than a positional type symbol, and route handler panics no longer reach the client as an empty 200. The note that store key enumeration is adequate friction no longer applies now that store_keys exists. Verified: go build ./..., go vet ./..., go test ./... (32/32 packages). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X427iD1w9dAGLML8wKn9aJ
TestTaskAPICapabilities/network_only_starts_but_store_access_is_denied asserted the old fail-open behaviour as "real, current behavior": a capability-denied panic inside a route handler reaching the client as a 200 with an empty body. That is the defect this branch fixes, so the assertion is inverted to require a 500 carrying CAPABILITY_DENIED. This strengthens the test rather than relaxing it. It previously proved the denial was invisible; it now proves the denial is reported with its code intact. The unit-level regression test is TestHTTPHandlerFailuresFailClosed in internal/vm. Caught by CI, not locally. `go test ./...` reported this package as cached and passing because its tests drive a compiled bytecode binary through a subprocess rather than importing the packages that changed, so Go's test cache did not invalidate. Verification for this branch now uses -count=1. Verified: gofmt clean, go vet ./..., go build ./..., go test -count=1 ./... — 32/32 packages. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X427iD1w9dAGLML8wKn9aJ
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Six changes to HowlFrame, every one forced by building HowlBoard — the mission-control reference application — rather than by a fixture. Each is independently tested.
Added
store_keys/STORE_KEYSreturns a store's record keys as a sorted list. The keys already existed inbcMemoryStore.recordsand were simply unreachable from.howl, sokv_cli,todo_cli,task_apiand HowlBoard each maintained a parallel index that could silently diverge from the records it indexed. Sorting is deliberate: Go randomizes map iteration, and a platform selling determinism should not hand back listings in arbitrary order.time_nowin the JavaScript backend. Supported by the bytecode VM and the Go backend, rejected as an unknown statement forweb_app, so a browser interface had no way to read the clock.Changed
Dict values may mix types. The analyzer modelled a dict as a homogeneous
map[string]Tand rejected(dict ("id" "x") ("count" 7) ("evidence" (list ...))). The runtime never had this restriction — the VM and native store both carrymap[string]any, and such a record round-trips throughstore_put/store_getandres_jsoncorrectly, verified before changing anything. Only the checker refused to let you write one down. Heterogeneous dict literals andmap_setwrites now widen the element type toanythrough the existingjoinhelper; key checks, target-kind checks and list element homogeneity are unchanged.This was a hard blocker. Dicts are the language's record literal, and the alternative was building JSON by string concatenation and parsing it back.
Fixed
Route handlers fail closed. A panic inside a handler wrote nothing to the
ResponseWriter, so Go emitted 200 with an empty body — makingCAPABILITY_DENIED, the platform's core safety mechanism, indistinguishable from a completed request. Handlers that fail before responding now return 500 carrying the structuredVMErrorJSON with its code preserved, logged to the VM error stream rather than process stdout.forover an expression silently miscompiled in both the JavaScript and Go backends. Each read the iterable's raw node value, empty for anything but a bound symbol, emittingfor (let m of )andfor _, m := range {. Invalid output produced with no diagnostic, in a toolchain whose contract is to fail closed. The bytecode target was always correct, which is why it survived: the backends disagreed and nothing compared them.web_appoutput could not run in a browser.on_eventemitted no trailing semicolon, and ASI does not apply before(, so the next statement was parsed as a call of theaddEventListenerresult. Separately, top-level statements routinely contain awaited calls and a classic<script>has no top-level await. They are now wrapped in an async IIFE, with function declarations left at top level so inline handlers can still reach them as globals.These two together mean no
web_apphad been executed end to end before now. Successful compilation was being treated as success.Verification
gofmtclean,go mod tidy,go build ./...,go vet ./...,go test ./...— 32/32 packages. Plus-validate,-run,-compile-bc,-run-bc,-compile-wasmandTestRepoAnalystStandaloneBytecode. The generatedfrontend/app.jsnow parses and executes as a classic script.Not attempted
Module support in the bytecode target. It is the largest gap —
backend/server.howlis one 665-line file becausemodule/use/import/exportare all unsupported there — but it is a substantial compiler change with real regression risk, and the right response to finding it in a dogfood pass is to report it with evidence rather than attempt it opportunistically.Full findings, including fourteen gaps left open and three corrections to earlier claims in this repository's own notes: HowlBoard's dogfooding journal.
Ordering
This should merge before HowlBoard's companion PR, which builds its compiler from this repository's
main.🤖 Generated with Claude Code
https://claude.ai/code/session_01X427iD1w9dAGLML8wKn9aJ