All notable changes to Geno will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
Added
geno checkwarns when aletorvarredeclares a mutable variable from the same or an enclosing block (W300), as invar y = f(y)inside a loop wherey = f(y)was meant. The program still passes. The warning is printed to stderr, returned bygeno.check()andgeno.check_path()withSeverity.WARNING, and published by the language server. It covers the project's own modules, not the standard library or installed dependencies.
Fixed
http_listendispatchesOPTIONSandPATCHrequests to routes registered for them in the interpreter and in generated Python. Before, both methods were answered with 501 Unsupported method, so CORS preflight handlers never ran. Unregistered paths still return 404.- Reference apps:
- geno-form stacks its fields and highlights the active tab.
- tetris.geno draws all seven pieces correctly and fits the default canvas.
- App READMEs use
geno run --unsafe --cap.
[0.5.0] - 2026-10-14
Changed
- Promote the frozen 0.5.0-rc.1 feature set to a non-prerelease version under accepted proposal 0002 (#177). The project lead waived the waiting periods and external human review for this promotion; all technical release requirements remain in force.
- Retain the RC's module-level constants, named constructor arguments, single-execution async values, stricter type/import validation, and backend, formatting, hosted-runtime, and exact-integer JSON fixes described below. This promotion changes release metadata and documentation only.
- Geno remains a preview project with the existing pre-1.0 compatibility policy and Alpha package classifier. A non-prerelease version does not confer serious-beta qualification.
Migration from 0.4
- A successful
main() -> Intselects the process exit status modulo 256:2and258exit 2;0 - 1exits 255. Its value is no longer printed as=> N. Print a value and returnUnitwhen output is intended. - JSON mode retains the raw result while exiting with the normalized status.
Allow intentional nonzero exits in
set -eorcheck=Truecallers. Printed output requires--cap printin JSON mode andRunConfig(capabilities={"print"})when embedding. - Embedding still returns the raw value without terminating the caller. Generated Python and Node ESM imports remain inert; browser artifacts keep their existing display behavior.
- Parameterized entrypoints, ambiguous bare imports, duplicate types or constructors, builtin type-name reuse, and duplicate impl methods are rejected. Async computations execute once even when awaited repeatedly. See the RC.1 migration notes below and 0.5 specification.
[0.5.0-rc.1] - 2026-10-14
Release candidate prepared for the first publication of the 0.5 series. It carries accepted proposal 0001. It is a prerelease, because that proposal's Rollout section permits the executable entrypoint change to be published only as a v0.5 prerelease with curated breaking-change notes; taking it stable needs a new proposal amending that direction.
0.5.0-rc.0 below opened the series and was never published, so everything
recorded under it ships here too: module-level constants, the normative
docs/spec/v0.5.md, and the conformance/v0.5 corpus.
Changed
geno runhonors amain() -> Intresult as its exit status. The result is normalized modulo 256, soreturn 2exits 2,return 258exits 2 andreturn 0 - 1exits 255, and it is no longer printed as a=> 2line. All three lanes agree: the default process-isolated run,--unsafe, and--json. Output written before the result still arrives, and a normal nonzero status carries no traceback and no diagnostics.main() -> Unit, a missingmainand any other declared return type keep exiting 0, and a non-Intresult is still displayed. This implements section 4.1.1 ofdocs/spec/v0.5.mdand is a breaking change for anything reading=> Nfrom stdout or expecting 0 from a program whosemainreturns a nonzeroInt. (proposal 0001, #105)geno run --jsonkeeps reporting the raw value:258stays258in the envelope while the process exits2, withoktrue and no diagnostics. Nothing that is not a process boundary gained a status:geno.api.run(),compile_and_exec(), the genericProcessSandboxand the hosted server return the raw result and never terminate their caller.RunResultgainedentrypoint_kindso a caller that is a boundary can derive a status without resolving the program again.- The standalone compiled Python artifact, the standalone Node script and
directly executed Node ESM report an
Intmainresult as their exit status too, normalized the same way and no longer printed. The Python artifact applies it underif __name__ == '__main__'only, and Node setsprocess.exitCoderather than callingprocess.exit, so buffered output still flushes and importing either artifact remains inert. - Browser-targeted JavaScript keeps displaying an
Intresult, because it has no process boundary. The compiler selects that from the target profile, so a bundler'sprocesspolyfill cannot turn a browser artifact's displayed result into an exit status. - The self-hosted CLI in
selfhost/Main.genoapplies the same contract at its own boundary: an inner program'sIntresult becomes the statuscmd_runreturns, normalized once, and every other kind stays success with the value left unprinted, which is what that adapter has always done. conformance/v0.5now carries this contract rather than pytest alone. The corpus runner gained schema 2 --expected_exit_status,expected_stderrandexpected_stderr_contains,expected_exit_classandexpected_json_value-- and runs seven executable boundaries: the in-process embedding lane, the threegeno runlanes, standalone compiled Python, the Node script and directly executed Node ESM. Ten cases carry theUnit,Intand no-mainrows of the section 4.1.1 result table on every one of those boundaries, including a type alias forInt, anasync mainand a synchronousmainthat awaits, since classification reads the resolved return type. The table's remaining row stays ingeno/tests/test_exit_semantics.py, because amainreturning some other type displays a host's own repr rather than text a case can state; so does entry-module ownership, which needs a second module on disk. Schema 1 keeps loading, so the frozenconformance/v0.4corpus is untouched, and a run whose target selection matches no case is now an error instead of an empty pass.
Migration
A main() -> Int whose integer was there to be seen prints it and returns
Unit instead:
func main() -> Int
return double(21)
end func
becomes
func main() -> Unit
print(double(21))
return ()
end func
print takes an Int directly and emits the same digits the => 42 line used
to. geno run grants print by default, and so does --unsafe, so neither
form needs a new flag; the fail-closed lanes -- geno run --json and the
embedding API -- grant nothing, so a program migrated this way needs
--cap print there.
A program that meant its integer as a status keeps main() -> Int and stops
reading stdout for it: the value is what the shell reports as $?, modulo 256.
geno run --json needs the same attention, in the other direction. Its envelope
is unchanged -- value still carries the raw result, so 258 is still 258 -- but
the command now exits with the normalized status, 2 for that envelope.
Anything running it under set -e, or with subprocess(..., check=True), now
has to read a perfectly valid envelope despite a nonzero exit.
Added
Named arguments in constructor calls.
Point(x: 10, y: 20)parses, as sections 6.3 and 7.8 of the spec have always written it and as the grammar'sarg = (IDENT ":")? exprallows for aTYPE_IDENTcallee. The checker validates names against the variant's fields and reports unknown, repeated, missing and positional-after-named arguments; arguments are evaluated in source order on every backend. (#135)Two further rules in
docs/spec/v0.5.md, so the 0.5 contract now differs from v0.4 in three places rather than only section 4.1.1:- Section 7.15: an
Async[T]value runs its computation at most once. Awaiting it again, including while an earlier await of it is still in progress, produces that one run's result, and a run that threw throws the same error to every awaiter.spec.jsoncarries the rule too. (#136) - Section 4.2: a type or type alias name may be defined once per module and may
not reuse a built-in type name, and the constructors of one type must have
distinct names. The section's own examples use fresh names instead of
redefining
OptionandList. (#139)
The document's own summary of how 0.5 differs from v0.4 now names all three sections; it had still said section 4.1.1 was the only one.
- Section 7.15: an
Record fields may begin with a single underscore. The spec and
spec.jsononly ever reserved keywords and dunder names on Python targets, but the compiled-Python prelude refused any_-prefixed field, sor._xpassedgeno checkand crashedgeno runand compiled Python. A single leading underscore is admitted for a declared field of a Geno record; other_attributes and dunders stay blocked. (#142)
Fixed
This candidate includes the defects found by evaluating the 0.5 line. Three reject programs that 0.4.4 accepted, each of which already failed at run time on at least one backend, and two change printed output; see Migration above for what that means for an upgrade.
Large integer JSON results remain exact numbers even beyond Python's default 4,300-digit conversion ceiling, including nested collections, map keys, and sets.
geno run --jsonpreserves the raw value and its normalized exit status without a traceback or a change to Python's global safety limit. The CLI and hosted server share the integer encoder; hosted response-size checks remain enforced. (#116)Target validation imports only the selected compiler backend, avoiding JavaScript compiler startup during Python checks (and vice versa) while preserving all lowering checks and diagnostics. Performance budgets are unchanged.
A method defined twice in one impl block is reported as
E309at the second definition, for the entrypoint and for imported modules alike. Every backend dispatched to the last definition, so the first body was dead code andgeno testfailed its own examples against the other body. Section 4.6 ofdocs/spec/v0.5.mdstates the rule, a fourth place the 0.5 contract differs from v0.4. Extracted from #43 (#51).geno testand the harness helpers compare example numbers one way.run_harness_from_sourceandrun_harness_from_compilednow use the same relative Float tolerance at every depth. TwoIntvalues compare exactly, including values beyond Float range. (#111)Checks that now reject what ran wrong before: an entry
mainthat declares parameters is reported asE304, since every host calls it asmain()(#133); a bare name that two unaliased imports both export is reported at its use, naming the qualified forms, where the compilers had been dropping it as ambiguous and--unsafesilently ran the last import (#132); and a repeated type name, a repeated constructor within one type, or a user type reusing a built-in name is reported at the definition, in both checkers and for imported modules as well as the entrypoint, instead of silently replacing the earlier meaning (#139).Float typing on the Python backends: an
Intheld where aFloatis expected stayed anIntin the interpreter and compiled Python whenever promotion was skipped -- inside a container, at a parameter, default, named or destructured argument, in a lambda or block lambda typed to returnFloat, an annotatedforvariable, or a mixedmax/min-- so it printed7where JavaScript printed7.0. The checker records the Float positions per expression and both Python backends widen along that shape. (#137)geno runkeeps output printed before an uncaught runtime error, on all three lanes and on a sandbox timeout, where the process-sandbox path returned on the worker's error before printing,--unsaferead the buffer only on success, and--jsonreported an emptyoutput. Example-verification output is cleared even when verification fails. (#130)Value semantics with aliased parts:
var pair = Pair(p, p); pair.a.x = 50no longer changespair.b.x. A field write copies one level of each constructor, list or map on its target path before writing, so snapshots still share structure and a shared DAG does not expand exponentially. (#129)A variant may share its type's name:
type Shape = Shape(n: Int) | Otherno longer rebinds the constructor to atyping.Unionin compiled Python, and annotations naming such a type spell out the union. (#128)Cross-variant equality under the sandbox:
Some(1) == Nonecrashed withNameError: NotImplementedon the sandboxedgeno runpath. (#127)matcharms that build different variants of a generic type unify:| 0 -> Err("zero") | _ -> Ok(n)is accepted, andOk(1),Err("x"),Ok(2.5)givesResult[Float, String]whatever the arm order. Diagnostics no longer leak internal__fresh_*names. (#134)An index, field or call after a pipeline stage applies to the stage's result:
xs |> map(_, f)[0]andx |> make(_).handler()no longer leave the postfix behind as a separate statement, in either parser. (#138)geno fmttracks blocks and brackets by token, so a match expression not bound bylet/var, a block lambda, anelse/ifchain, multi-line arguments, list literals and pipelines keep their structure.format_sourcere-parses its output and raisesFormatErrorrather than writing a file whose meaning would change;geno fmtreports it and the LSP returns no edit. The repository's.genofiles are reformatted to the new fixed point, whitespace only. (#131, #145)geno constrain --validatetreatselse ifand comprehensionfor/ifas continuations, so valid programs are no longer reported as having unclosed blocks, while statements in afn ... dolambda still open a block. (#144)The REPL shows buffered output before each result, error, test result and failed
:load, reports positions in the user's own input rather than at the hidden wrapper's line 4, and type-checks expressions before running them. (#143)Compiled JavaScript prints built-in
JsonValueandHttpRequest/HttpResponsevalues canonically rather than asJsonFloat(value: 5)and header tuples as arrays, and_deepCopycarries a constructor's formatter over so a copied value keeps its rendering. (#141)path_parent,path_extensionandpath_is_absoluteagree across backends: the JS runtime followsposixpathsemantics for repeated separators and all-dot names, and the Python drive-letter check accepts only ASCII letters, asdocs/reference/capabilities.mddescribes. (#146)Self-hosted frontend parity: transparent type aliases resolve in signatures, fields, examples and entrypoints (#118), and a match-expression arm evaluates to its expression rather than
()(#140).The VS Code extension's transitive
brace-expansionmoves to 5.0.12. (#125)
[0.5.0-rc.0] - 2026-09-24
Development pre-release opening the 0.5 series. Not published; the version is
dated because scripts/check_version_alignment.py requires a dated heading for
whatever version the tree currently declares, not only at tag time.
Added
- Module-level constants: A
letwritten outside any function now binds a constant every function in the file can read, replacing the duplicated literals and@untestedhelper functions the pattern used to need. The initializer is restricted to literal forms — a number, string or boolean, a negated number, or a list or tuple of those — so a module still executes nothing when it is imported; a call, an operator expression or a reference to another binding is rejected at parse time.varis rejected at module level,export letis not supported yet, and constants are private to the declaring module. Names follow thesnake_casevariable convention. (#72) docs/spec/v0.5.md, the normative 0.5 language contract. Its only change from v0.4 is section 4.1.1, which givesmain() -> Intexecutable exit semantics per accepted proposal 0001.docs/spec/v0.4.mdis frozen and untouched.conformance/v0.5/, a seed corpus so the current and retained previous-minor corpora both resolve. It carries forward core 0.4 cases that proposal 0001 does not change, plus a module-constants case, which is a 0.5-only form that the frozen v0.4 corpus cannot carry. Exit-semantics cases follow with the runner work.
Changed
spec.json:language_seriesis now0.5andhuman_specpoints at the 0.5 document.runtime_semantics.entrypoint_resultdescribes the accepted contract again -- anIntresult normalized modulo 256, output preserved before exit, and no traceback for a normal nonzero status.
Notes
The 0.5 specification scaffolding does not implement the executable entrypoint
change. At this pre-release geno run and both backends still display an Int
result and exit 0; the observable entrypoint change lands with the CLI and
backend work recorded under Unreleased above.
Fixed
- Binding and closure soundness: Same-scope redeclarations retain their type and effect contract, while existing same-type closure rebinding remains observable. A later
letclears priorvarmutability. Local function values resolve their own call metadata, lambdas use an independent control-flow context, and duplicate constructor fields are rejected. - Effect inference: Parameter defaults, assignment targets and indices, and normalized named trait arguments contribute their effects consistently to the checked function signature.
- Backend value and scope parity: Record reassignment and destructuring preserve snapshots while explicit mutable collections keep reference semantics. Module constants remain readable through local and parameter shadowing, and Python postconditions no longer break parameter rebinding.
- Callback and numeric parity: Compiler optimizations require the resolved builtin identity, preserving shadowed callbacks. Ordered comparisons involving NaN now agree across all three execution engines.
- Project and editor workflows: Generated libraries are consumable by the documented import, explicit-file checks retain manifest module mappings, bundles use canonical file discovery, and rename includes reverse importers and converts protocol positions for non-BMP text.
- Quality gates: Differential fuzzing grants its required print capability and fails on crashes, timeouts, missing required backends, or wrong results. CI explicitly requires Node. Runtime benchmark scoring checks correctness before timing and retains failed workloads in the denominator. Analysis and experiment reports share McNemar statistics and chi-square survival calculations; Student t tails and intervals now use the same distribution rather than inconsistent approximations.
- JSON execution capabilities:
geno run --jsonnow honors its documented fail-closed default. Programs that print must pass--cap print; JSON mode continues to use the in-process embedding API. - Hosted lifecycle: Shutdown drains active handlers and workers within a bounded grace period. Readiness checks exercise the hosted worker and return HTTP 503 on failure;
/readyzand/livezdistinguish readiness from liveness, and container probes use readiness.
[0.4.4] - 2026-09-20
Compatibility
This release takes one deliberate exception to the rule in
docs/operations/compatibility-policy.md that a patch preserves documented
source behavior. The target-less check change below (#70) rejects programs that
0.4.3 accepted: with no declared target and an entry module that defines main,
geno check and geno test now validate lowering through the Python backend,
so a name that backend reserves is reported there instead of passing.
The exception is taken because the old behavior withheld a guarantee rather than
made one. 0.4.3 published target-less checks as a "permissive language check
with no backend-lowering guarantee", and the practical effect was that geno check and geno test reported success for programs the default geno run
could not execute. Every program the new rule rejects was already failing at run
time with the same diagnostic; what changes is when the author is told. No
program that ran on 0.4.3 stops running on 0.4.4, and the frozen conformance
corpus is unchanged.
Migration: rename the binding or parameter that collides. The diagnostic names
the identifier — 'len' is a reserved runtime name and cannot be used as a function parameter name.
Rejected from 0.4.4:
func widen(len: Int) -> Int
example 2 -> 4
return len * 2
end func
func main() -> Int
return widen(2)
end func
Accepted:
func widen(count: Int) -> Int
example 2 -> 4
return count * 2
end func
func main() -> Int
return widen(2)
end func
Library modules that define no main are unaffected and stay permissive, as
does the geno.check() embedding API unless a target is selected explicitly.
Added
- Tuple patterns in
matcharms:| (a, b) -> ...now parses, type checks, and runs on the interpreter and both backends, including nested forms such as| Some((key, value)) ->. Because every value of a tuple type has that type's arity, an arm whose elements are all variables or wildcards is exhaustive and needs no default arm; mismatched arity, non-tuple scrutinees, and single-element tuple patterns are rejected with targeted diagnostics. (#79) - Leading
|before the first ADT variant: Sum-type definitions may now begin their variant list with a bar, so idiomatic multiline domain models parse. This — not a one-physical-line requirement — was what rejected multiline sum types. (#78) - Bare-arrow zero-argument examples:
example -> valueis now accepted alongsideexample () -> valueand produces an identical AST; example inputs are still arity-checked against the parameter list. (#71) - CLI latency ratchets:
benchmarks/cli_latency.pytimes six frozen CLI scenarios andmake perf-ratchetsenforces them againstperf-budgets.tomlin the weekly scheduled-quality workflow. (#48) - Hosted header-read budget:
GENO_HEADER_TIMEOUT_SECONDS(default 5s) bounds reading a request head independently of the full request budget. (#61)
Changed
- Target-less checks validate the default run backend:
geno checkandgeno teston a program with no declared target now validate lowering through the backend the defaultgeno runuses, so a suite can no longer pass for a program that cannot run. Library modules that define nomainare never executed bygeno runand stay exempt, since standalone lowering reserves more names than project lowering. (#70) - Interpreter collection caps are now interpreter-local: Builtins installed on an
Interpreterenforce only that interpreter'sSandboxConfig.max_collection_size. The process-widegeno.builtins.set_max_collection_sizecap no longer tightens (or is clobbered by) live interpreters; it now governs only direct module-level builtin calls. Embedders that relied on the global setter to constrain interpreters should pass the limit viaSandboxConfiginstead. - Entrypoint results render in Geno syntax: Compiled Python and the
geno runresult line now formatmain()'s value through the Geno formatter instead of Python'srepr, so aBooldisplays astrueand aList[String]as["a", "b"], matching the JavaScript backend. (#61) - Non-finite float display:
inf,-inf, andnannow render identically in text output andto_stringacross the interpreter and both backends, including nested in collections. JSON serialization rules for non-finite numbers are unchanged. (#89) - Faster
geno runstartup: The default isolatedgeno runno longer imports the lexer, parser, typechecker, and dependency graph in the parent process, which the worker owns;run-hellodrops about 15% andrun-mediumabout 7%. (#48) - VS Code extension build runtime: Building and packaging the extension now requires Node.js 22–24, matching the VSCE 4 toolchain. The supported VS Code API remains unchanged. (#104)
Fixed
- Sandboxed compile diagnostics: The default process-isolated
geno runreported backend rejections asCompiler Error: the isolated frontend failed safely (CompileError), hiding the only actionable text. Compile errors now surface the same message thegeno compilepath prints. (#69) geno-formexample runs again: Two local bindings namedlenshadowed a reserved runtime name, so the shipped app failed the defaultgeno run.- Silent truncation in
geno run: The entrypoint guard wrapped the wholemain()call inexcept NameError: pass, so aNameErrorraised inside a program stopped execution mid-run while the CLI still exited 0. The guard now covers only the entrypoint name lookup, and the runtime prelude no longer names builtins the sandbox withholds. (#61) - Block scoping in the Python backend: A
letinside a nested block leaked into the enclosing scope, so a program whoseifbody rebound a name returned the inner value where the interpreter and the JavaScript backend returned the outer one — a silently wrong answer acrossif,else,while,for,try/catchand match arms. Block bindings that shadow an enclosing one are now renamed in the emitted Python. (#61) - Same-block rebinding on the JavaScript backend:
let x = 1followed bylet x = 2in one block emitted twoconstdeclarations, aSyntaxError, so the program did not run at all while the other two backends treated it as a rebind. The later occurrences are now plain assignments. (#61) Intliterals inFloatpositions: Call arguments, container elements, tuple slots, constructor fields,Optionpayloads and JSON values annotatedFloatrendered as3on the interpreter and compiled Python but3.0on JavaScript;json_to_string(JsonFloat(3))disagreed in machine-readable output. All three paths now use the widened type the typechecker already records. (#61)- Order-independent list inference: Element inference anchored on the first element, so
[2.5, 1, 3]inferredList[Float]while the same values as[1, 2.5, 3]were rejected with "expected Int, got Float", even under an explicitList[Float]annotation. Inference now widens to the type every element is compatible with; genuinely mixed lists still fail. (#61) - Top-level
Stringresults: The interpreter's=>result line quoted a top-levelStringwhere both compiled runtimes print it bare, sogeno runandgeno run --unsafedisagreed on the same program. Diagnostics keep their quotes. (#61) - Call evaluation order and closure bindings: Named-argument calls now evaluate their arguments in source order rather than parameter order, and a lambda that assigns to an enclosing
varupdates that binding, in compiled Python and JavaScript as in the interpreter. (#89) - Import resolution at scale: Deep import chains resolve without rebuilding the graph, source snapshots and private import namespaces survive normalization, imported namespaces are scoped to their module environment, manifested package identities are preserved in direct imports, and LSP project caches refresh after watched-file changes without losing diagnostics. (#88)
- Formatter and REPL: The formatter preserves multiline string literals, and the REPL completes multi-line and nested definitions, including test definitions, instead of ending them at the first blank line. (#90)
- VS Code diagnostics and launch reporting: Stale fallback diagnostics are discarded, language-server launch failures are reported instead of failing silently, and startup is cancelled across deactivation. (#86)
- VS Code API compatibility: The extension keeps working against VS Code 1.91 after the
@types/vscodebump. (#83) - Experiment analysis: Statistical corrections, valid experiment pairings, unavailable comparisons, and strict significance cutoffs are preserved in the benchmark and experiment analysis tooling. (#85)
- Coverage gate: The 80% floor now measures production code only;
geno/tests/*is omitted in both the run and report phases, and the coverage shards install the LSP extra sogeno/lsp_server.pyis actually exercised. (#61) - Documentation: The guide and pitfalls reference now state that
parse_intandparse_floatreturnOption, notResult. (#93) - Await in synchronous entrypoints: A
mainwithout anasyncmodifier can now await in its body or executable contracts on both compiled backends and the process-isolated CLI, matching interpreter behavior for ordinary entrypoint execution. (#108) - Browser apps: Single-file and directory builds share canvas initialization and responsive sizing without enlarging small canvases. Scaled pointer coordinates and input resets after focus or visibility loss prevent misplaced pointers and stuck controls. Builds create missing output parents, and package-lock setup failures produce actionable diagnostics. (#102)
- Actionable diagnostics: Missing standard-library and project imports, named arguments, function examples, and examples rejected by preconditions now explain the relevant correction. Import hints respect aliases and exported declarations. (#102, #107)
- Authoring guidance: Guides explain reserved
resultbindings, immutable list updates, preconditions, module imports, and preserving full precision in Float examples while comparison paths remain inconsistent. (#109)
Security
- Capability grants from untrusted arguments: Both standalone compiled runtimes scanned the entire argv for
--cap, including everything after--, so a program forwarding untrusted arguments could grant itselfenv,fs,process, orhttp. Both parsers now stop at the first--, matchingcli_args(). This is a documented-behavior tightening permitted for a patch release because the previous behavior allowed a capability bypass. (#61) - Half-open connection exhaustion: Reading a request head shared the full 30s request budget, so
MAX_CONNECTIONSpartially-sent headers made every endpoint, including/healthzand/metrics, unreachable with no log line or counter. Head reads now have their own shorter budget and refused connections are counted and warned about. (#61) - Hosted input validation and result bounds: Workers enforce
GENO_MAX_RESPONSE_BODY_BYTESbefore sending results over IPC and oversized/runand/constrainresults return HTTP 413; request source, module source, and constraint prefixes must be valid Unicode, and unpaired JSON surrogate escapes and out-of-range timeouts return HTTP 400. (#87) - Dependency maintenance: Updated
cryptographyin the release lock and the VS Code extension's transitivejs-yaml,qs, andundicidependencies. (#42, #49, #52, #99, #100)
[0.4.3] - 2026-08-01
Changed
- Toolchain and supply-chain maintenance: Updated Ruff, pinned GitHub Actions, the PyPI publishing action, and VS Code extension transitive dependencies.
- Release validation: Release tags must now point to
mainand be newer than existing changelog and repository releases, with dated, non-empty release notes.
Fixed
- Direct Unit results:
geno run --unsafenow keepsmain() -> Unitsilent, matching process-sandboxed and standalone execution.
[0.4.2] - 2026-07-22
Added
- Normative language contract: Published the versioned v0.4 specification, machine-checked effects, diagnostics, integer bounds, compatibility metadata, and a frozen all-backend conformance corpus.
- Language evolution process: Added explicit governance, a design-proposal process, compatibility policy, and evidence-based serious-beta qualification criteria.
- Scheduled quality: Added a weekly all-backend conformance and 2,000-case differential-fuzz workflow with retained failure artifacts.
- Editor formatting: Added LSP whole-document formatting for saved, unsaved, and virtual Geno buffers.
- Filesystem inspection: Added capability-gated
fs_metadata,fs_symlink_metadata, andfs_canonicalizewith typed file kinds, portable millisecond timestamps, scoped symlink handling, and compiled Python/Node parity.
Changed
- Target-aware lowering contract: Checks that name an execution target now validate the checked AST with its canonical compiler backend in memory; manifest compilation selects compatible execution profiles while preserving
compile --target python|jsand adding an explicit--profileoverride. - Stable diagnostic ownership: Parser, type, and effect errors now emit granular E202 and E302-E311 codes at their owning compiler sites, including through the LSP exception bridge.
- Release qualification: The release gate and local release plan now require the frozen corpus on the interpreter, Python, and JavaScript backends.
Fixed
- Compiled Node HTTP: Repaired CommonJS and ES-module HTTP with an
http-only, stdin-based bridge; streamed bounded responses; enforced private-address and redirect policy; preserved duplicate headers and non-2xx bodies across Python and Node; and kept expectedhttp_requestfailures trace-free. - Target/compiler parity: Target-aware CLI and embedding checks now reject backend-invalid integer literals, record fields, runtime names, and project namespaces before artifact emission; invalid manifests cannot be bypassed by an explicit check target, and expected compiler/build failures no longer expose Python tracebacks or partial output paths.
- VS Code packaging: The release gate now verifies the transitive
vscode-jsonrpcruntime is present in the VSIX. - Python 3.10 HTTP redirects: Added explicit HTTP 308 dispatch for compiled Python and hosted callbacks while preserving redirect validation, limits, and method/body semantics.
- Entrypoint ownership: Only
maindeclared in the selected entry module is invoked; an imported module that exportsmainno longer becomes an accidental entrypoint. - Generated ESM import safety: Node ES modules invoke
mainonly when run directly, while imports remain inert and browser-targeted ESM remains free of Node-only imports. - v0.4 result compatibility: Kept
main() -> Intas an ordinary displayed result with successful process status in primarygeno runand standalone compiled artifacts; embedding calls return the value without terminating their host, while the legacy self-hosted command adapter remains unchanged.
[0.4.1] - 2026-07-19
Changed
- Supply-chain maintenance: Consolidated and pinned GitHub Actions, hash-locked release tooling, migrated development coverage to pytest-cov 7, and updated the VS Code TypeScript and language-client toolchain (#9, #13, #15, #16, #17, #20)
- Extension dependencies: Resolved known VS Code extension dependency vulnerabilities and refreshed the lockfile (#14)
- Security support window: The supported release line is now 0.4.x
Fixed
- VS Code packaging: Packaged the Python language-server runtime in the VSIX and aligned the extension build and release gate (#13)
- CI timeout stability: Isolated timeout scheduler regressions and stabilized extension checks after the 0.4.0 release (#13)
- macOS benchmark stability: Gave research-only Python benchmark workers an explicit finite memory budget instead of relying on production defaults (#22)
- macOS sandbox stability: Raised Darwin's production address-space growth budget to 512 MiB, probed XNU's accepted boundary without architecture assumptions, and stabilized the cross-platform config-transport smoke test (#23, #24)
Security
- Untrusted process execution: Hardened POSIX and Windows worker startup, supervision, cleanup, resource enforcement, compiled-runtime prelude handling, and regex limits (#17)
- Package integrity: Hardened source capture, lock and state files, symlink handling, crash recovery, content hashing, and release dependency validation (#17)
- Hosted boundaries: Enforced Host, forwarded-header, request-body, and module boundaries and corrected deployment health checks (#17)
- Regression coverage: Added security tests for the discovered vulnerability classes; the in-process embedding API remains cooperative and generated JavaScript remains for trusted environments (#17)
[0.4.0] - 2026-07-13
Added
- Centralized builtin metadata: New
geno/builtin_metadata.pymodule provides a single source of truth for capability maps, param names, and completion lists — replaces duplicated data across API, server, REPL, LSP, and both compilers (#312, #313) - Backend parity tests: Negative arithmetic and map tuple-pair test programs in the parity suite (#304, #305)
- Match exhaustiveness: Recursive exhaustiveness checking for nested pattern matching with constructor specialization (#306)
- Package manager lockfile tests: Coverage for commit pinning, shallow-to-full clone upgrade, and tag-based installs (#307)
- Server wall-timeout tests: Direct coverage for child-process execution, timeout termination, worker errors, and large output (#308)
- Python support metadata test: Regression test that keeps
pyproject.toml, CI matrix, and classifiers aligned (#309) - Standard library expansions: String module gains
split,join,replace,to_upper,to_lower,starts_with,ends_with,contains,split_once; List module gainslength,map,filter; Map module gainsget,insert(#390) - LSP semantic symbols: Trait/impl header resolution, top-level type aliases, cross-file semantic analysis (#352–#359)
- Selfhost annotation parsing: Parser can now parse
@untested(...)annotations on functions (#403) - Selfhost structured diagnostics: All compiler stages emit structured diagnostics with source locations (#451)
- Selfhost CLI entrypoint: Real CLI with host bridges for file I/O and process execution (#452)
- Selfhost parity checks: CI gates and drift checks between selfhost and Python implementation (#448)
- Differential parity tests: Property-based tests comparing interpreter, Python, and JS backends (#449)
- Browser template smoke tests: Game loop template validated in CI (#447)
- CI spec validation:
spec.jsonvalidated against source metadata on every push (#445) - Noxfile: Local multi-Python testing via
noxsessions aligned with CI matrix (#503) - Dependency lock files:
requirements-dev.lockandrequirements.lockfor reproducible builds (#504) - Shared test fixtures:
conftest.pywith reusable fixtures for the test suite (#505) - Sandbox tests: Coverage for
breakpoint,memoryview, andbytearraybypass vectors (#498)
Changed
- Public preview version: Prepared
geno-lang0.4.0 as the first public package version; release metadata is aligned across Python, the language specification, and the VS Code extension - Canonical value semantics: User-defined constructor values are snapshots when rebound;
withreturns an independent value, whileArray,Vec,Set, andMutableMapremain explicit reference types - Portable integer contract: JavaScript targets reject
Intresults outside-(2^53 - 1)through2^53 - 1instead of silently rounding; Python and interpreter execution retain the configurablemax_integer_bitslimit - Runtime prelude ownership: Python and JavaScript runtime preludes are documented as hand-maintained, target-specific implementations guarded by differential parity tests
- Server process isolation:
/runrequests now execute in amultiprocessing.Processchild (spawn context) instead of a thread pool, enabling hard kill on wall-clock timeout (#308) - Python support range: support metadata and docs now align on
>=3.10,<3.14; badge and public compatibility docs reflect3.10-3.13(#309, #416) - API parameter ordering:
geno.run()now standardizes onfilenamebeforeconfigfor new call sites while preservingrun(source, RunConfig(...))backward compatibility (#380) - File extension: Renamed
.gento.genothroughout project — CLI, tests, examples, docs, and VS Code extension (#454) - Parser modularization: Split
parser.pymonolith into focused mixin components (#450) - Structured logging: Library modules migrated from
print()to Pythonlogging(#500) - Expanded linting: Ruff rule selection expanded with UP, B, C4, SIM, PT, RUF categories (#501)
- Exception handling: Broad
exceptcatches audited and narrowed with structured logging (#502) - Type checking: mypy
check_untyped_defsenabled globally; core library type errors fixed (#506) - CLI modularization: Runtime callbacks extracted to
_serve.py, formatting utilities to_cli_format.py(#507) - Legacy cleanup: Legacy app template hidden from
geno initCLI (#444)
Fixed
- Cross-backend semantics: Interpreter, compiled Python, and compiled JavaScript now agree on mixed
Int/Floatequality, numericdivide, bare-stringprint, map-update insertion order, constructor copy behavior, and mutablewithresults - Example verdict parity:
geno testnow applies the same mixed-numeric equality semantics as shipped compiled programs - Type soundness: Required named parameters cannot be skipped, mutable generic record fields are invariant, and calls through indexed expressions propagate their effects
- Parser and diagnostics: Bare
returnno longer consumes the following line, malformed constructor patterns produce a typed diagnostic instead of an internal crash, duplicate top-level functions are rejected, numeric literal adjacency is diagnosed, and unsupported triple-quoted f-strings fail clearly - Runtime hardening: Overlapping non-capturing regex alternations are rejected, deeply nested JSON returns
Err, default CLI clock/random capabilities work in process mode, rounding is stable at half boundaries, and structural permutation checks avoid a quadratic fallback - Formatter stability: Binding-position
matchexpressions and their following statements retain canonical indentation - Arithmetic parity: Integer division and modulo now use truncation-toward-zero semantics across all three backends (interpreter, compiled Python, compiled JS), matching C/JS behavior instead of Python's floor division (#304)
- Map pair typing:
map_from_list,map_entries, andmap_from_entriesnow useList[(K, V)]tuple pairs instead ofList[List[K]]for type safety (#305) - Result type annotations: Compiled Python output now emits
Union[Ok[T], Err[E]]instead of dropping the generic parameters (#311) - Package manager lockfile:
install()now checks HEAD against locked commit and unshallows when needed; tag-based installs use the correct fetch strategy (#307) - Compiled selfhost forward references: Variant field and function annotations are now quoted strings to avoid
NameErroron recursive ADTs (#404) - Sandbox dunder validation: Thread sandbox now blocks
__getattribute__/__getattr__in class definitions, matching the process sandbox (#365) - Runtime:
list_findcrashes on no match —None_not callable (#499) - JS compiler: Removed
Object.freezefrom constructors to allow field mutation (#485) - JS runtime: Corrected
set_to_listcomparator sign (#486) - Builtins: Corrected
list_group_byreturn type (#484) - Manifest: Preserve unknown TOML keys on round-trip save (#483)
- Package manager: Validate
content_hashon install even when HEAD matches (#482) - Interpreter: Removed duplicate
call_depthincrement in_eval_function_call(#479) - LSP: Added size bounds and LRU eviction to caches to prevent unbounded memory growth (#488)
- Server: Startup checks now fail closed on error (#487)
- CLI: Escaped
build_errorin dev-server HTML response to prevent XSS (#477) - Dep graph: Parser mixin modules included in compiler fingerprint (#480)
- Type annotations: Added annotations to interpreter builtins and typechecker init (#453)
- Packaging: Built wheels now include
_js_runtime_support.js,packages.json, andgeno/std/*.genoruntime assets; publish workflow smoke-tests exercise JS compile, stdlib import, and package index from the installed wheel (#751)
Security
- XFF header validation: Server validates
X-Forwarded-Forheaders - Module size limits: Import resolver enforces size caps on module sources
- Sandbox: Blocked
io.open()file access via module proxy (#476) - Sandbox: Reconciled
reimport policy between thread and process modes (#475) - Sandbox: Rejected
__getattr__override in process sandbox worker (#474) - HTTP builtins: Restricted
http_fetch/http_post/http_requestto http/https schemes — blocksfile://,ftp://,data:URIs (#473) - Dep graph: Replaced unsafe
pickle.loadswithRestrictedUnpickler(#481) - Server: Unauthenticated non-loopback connections require explicit opt-in (#446)
- Project graph: Enforced path containment for manifest files and dependency entrypoints (#478)
Performance
- Lexer: Regex-based number and identifier scanning replaces character-by-character loops; inlined tokenize loop with local state variables (#296, #300)
- Parser: Precedence-climbing expression parser replaces 6 per-level methods; cached current-token fields updated by
_advance()/_match()(#295, #299, #301) - Compiler:
type(x) is Tfast dispatch for statement and expression compilation withisinstanceslowpath fallback (#298) - Typechecker: Frozen
_BuiltinCheckerStatedataclass caches builtin init state for reuse acrossTypeCheckerinstances (#302) - Dependency cache: Pickle-based AST isolation with versioned cache protocol; corrupt payloads caught and invalidated (#297, #303)
- Token layout:
__slots__-basedTokenandSourceLocationclasses replace dataclasses for lower memory and faster construction (#303)
[0.3.1] - 2026-04-07
Added
- Semantic rename/references: Rename and find-references via symbol table (#214)
- JS artifact tests: Pytest coverage for source maps,
.d.ts, and ESM (#227) - Benchmark evidence: Complete benchmark evidence and performance claims (#223)
- Release-gate templates: CI validation script for release-gate templates (#224)
- Build single-file tests: Validate
buildsingle-file.htmlinference (#209) - geno-pipe CLI IO: CLI file input for
geno-pipe(#212) - REPL type completion: Tests for REPL type completion (#228)
cli_argsbuiltin: Access command-line arguments from Geno programs- DateTime module: Typed
DateTimemodule for date/time operations http_respondbuiltin: HTTP response support forservecapability
Changed
- Clarified
geno-formis a canvas-based demo, not a full form framework (#210, #211)
[0.3.0] - 2026-04-06
Added
- CSV/TOML parsing builtins (always available):
csv_parse(text)— parse CSV text intoList[List[String]]csv_parse_with_headers(text)— parse CSV with headers intoList[Map[String, String]]toml_parse(text)— parse TOML text intoResult[JsonValue, String]
- Process execution builtins (capability-gated,
--cap process):exec(command)— run a shell command, returnsResult[ProcessResult, String]exec_with_input(command, stdin)— run a command with piped stdinProcessResultbuilt-in type withexit_code: Int,stdout: String,stderr: String
- Package manager: Git-based dependency management
geno install— install dependencies declared ingeno.tomlgeno add <name> <url> [--branch]— add a dependency and install itgeno update [name]— update one or all dependencies to latest commitsgeno.locklockfile for reproducible buildsgeno_modules/directory for installed dependencies- Module resolver fallback:
import Foosearchesgeno_modules/Foo/ifFoo.genonot in same directory
- LSP server: Full Language Server Protocol support
geno lspcommand (stdio by default,--tcp --portfor TCP)- Diagnostics on open/change/save via
geno.check() - Hover shows type information for builtins
- Go-to-definition for functions and types
- Completion for keywords, builtins, and user-defined names
- VS Code extension updated to use
vscode-languageclientwith fallback toexecFile - Optional dependency:
pip install geno-lang[lsp](requires pygls)
- Bitwise operators:
&(AND),^(XOR),<<(left shift),>>(right shift),~(NOT), andbit_or()builtin for bitwise OR - Exponentiation operator:
**(right-associative), e.g.,2 ** 10evaluates to1024 else ifchaining: Flat conditional chains without extra nesting- Match guards:
| pattern when condition -> bodyfor conditional pattern matching - Index assignment:
arr[0] = valueforArray,Vec, andMutableMaptypes - Field assignment:
obj.field = valuefor constructor values - Negative indexing:
list[-1]returns the last element,-2the second-to-last, etc. - Rest/spread patterns:
[head, ...tail]in list pattern matching range()with step:range(start, end, step)with optional step parameter (supports negative step)- New string builtins:
replace(str, old, new),ends_with(str, suffix),to_upper(str) - Traits and interfaces: Polymorphic dispatch with
trait/implblockstrait Describable ... end traitdefines method signatures withSelftypeimpl Describable for Circle ... end implprovides concrete implementations- Dispatch works across all three backends (interpreter, compiled Python, compiled JS)
- Multi-constructor ADT dispatch (e.g.,
type Shape = Circle(...) | Square(...)) - Duplicate impl detection at type-check time
- Try/catch error handling:
try ... catch e: String ... end tryfor structured error recovery- Catch clause binds the error message as a
String - Works in loops and can be nested
- Catch clause binds the error message as a
- F-string interpolation:
f"Hello {name}, you are {age} years old"syntax- Arbitrary expressions inside
{...}braces - Escape sequences supported in text portions
- Arbitrary expressions inside
- Multi-line block lambdas:
fn(params) do ... end fnfor multi-statement lambdas- Supports
if,while,for,match, and other statements in the lambda body - Return type inferred from
returnstatements
- Supports
- ? propagation operator:
expr?for early return onNone/Err- In
Optioncontext: returnsNonefrom enclosing function if value isNone, unwrapsSome - In
Resultcontext: returnsErrfrom enclosing function if value isErr, unwrapsOk
- In
- Type aliases:
type Coordinate = (Int, Int)andtype Predicate = (Int) -> Bool- Generic aliases:
type Pair[T] = (T, T) - Arity validation for generic aliases
- Generic aliases:
- Tuple destructuring:
let (x, y): (Int, Int) = exprinlet/varbindings - Set[T] type: Immutable set with structural equality
set_new,set_from_list,set_add,set_remove,set_contains,set_size,set_to_list,set_union,set_intersection
- Range and sort builtins:
range(start, end)— returnsList[Int]from start (inclusive) to end (exclusive)sort(list, comparator)— sort with custom comparator functionsort_by(list, key_fn)— sort by key extraction function
- Regex builtins (capability-gated,
--cap regex):regex_match(pattern, text)— returnsOption[String]for first matchregex_find_all(pattern, text)— returnsList[String]of all matchesregex_replace(pattern, replacement, text)— returnsStringwith replacements- Pattern length limit (1000 chars) for ReDoS mitigation
- Clock/date builtins (capability-gated,
--cap clock):clock_now()— current Unix timestamp asIntclock_format(timestamp, fmt)— format timestamp with%Y,%m,%d,%H,%M,%S,%%directivesclock_parse(text, fmt)— parse formatted date string to timestampclock_elapsed(start, end)— compute elapsed seconds between timestamps
- Expanded file I/O (capability-gated,
--cap fs):fs_list_dir(path)— returnsList[String]of directory entriesfs_exists(path)— returnsBoolfor path existence check
- App mode: Graphics, input, and game loop support for interactive applications
geno buildcommand compiles Geno apps to self-contained HTML files with canvas rendering- Graphics builtins:
draw_rect,draw_rect_outline,draw_circle,draw_line,draw_text,clear_screen - Screen builtins:
screen_width,screen_height - Input builtins:
is_key_down,is_key_pressed withexpression for functional record updates (e.g.,player with (x: new_x))
- Self-hosted frontend + interpreter: Geno frontend and tree-walking interpreter written in Geno itself (
selfhost/directory)- 8 modules: Tokens, Lexer, Ast, Parser, Types, TypeChecker, Interpreter, Main
- Host-side parity/drift checks and CLI smoke tests cover the current surface
- Mutable Array[T]: Fixed-size mutable array type for interactive and performance-critical programs
array_new,array_from_list,array_get,array_set,array_length,array_to_list,array_copy,array_fill
- Vec[T]: Growable mutable vector type
vec_new,vec_from_list,vec_push,vec_pop,vec_get,vec_set,vec_length,vec_to_list
- MutableMap[K, V]: Mutable hash map type
mutable_map_new,mutable_map_set,mutable_map_get,mutable_map_contains,mutable_map_delete,mutable_map_keys,mutable_map_size
- String builtins:
char_code,from_char_code - File I/O builtins for self-hosting support (capability-gated)
- Default parameter values:
func f(x: Int, y: Int = 10) -> Int— optional params with defaults- Required parameters must come before optional parameters
- Arity check uses range:
min_required <= args <= total_params - Works across all three backends (interpreter, compiled Python, compiled JS)
- List comprehensions:
[expr for var: Type in list if cond]— concise list transformations- Optional filter clause with
if - Compiles to native comprehensions in Python,
.filter().map()in JS
- Optional filter clause with
- Structured error types (
throw):throw exprfor throwing typed valuesthrow "message"throws a string (caught bycatch e: String)throw MyError("msg")throws a user-defined type (caught bycatch e: MyError)- Catch clauses now accept user-defined ADT types in addition to
String - Unmatched types propagate to outer try/catch blocks
- Async/await:
async funcandawait exprfor asynchronous programmingAsync[T]type wraps asynchronous return values- Interpreter uses lazy evaluation (single-threaded); compilers emit real
async/await awaitis allowed inasync funcand inmain()- Async functions are exempt from
exampleclause requirements
Fixed
- Fix four interpreter bugs: error halting,
float_to_int, value equality, immutable maps - Fix array for-loops, duplicate params, empty match, and runtime parity
- Fix
is_numeric_stringJS parity - Fix 12 review findings across traits, regex, clock, try/catch, type alias, and f-string features
- Prevent path traversal in
bundle_project()file loading - Fix 33 ruff lint errors and 16 mypy type errors across core modules
- Fix test collection for experiment/tooling tests in environments without pyyaml
Security
- Validate dependency names in package manager to prevent
geno_modules/path traversal - Block
asynciofrom sandbox import allowlist (exposes subprocess/networking) - Add f-string collection size limits in compiled output to prevent resource exhaustion
- Restore
remodule to sandbox allowlist (regression from prior security fix brokeclock_parse)
[0.2.1] - 2026-03-31
Added
- JavaScript compiler backend:
geno compile --target jscompiles Geno source to standalone JavaScript runnable with Node.js- New
JSCompilerclass andcompile_to_js()API - JS runtime prelude with structural equality, deep copy, safety limits, and all builtin functions
- 126-test suite including parity tests against all 6 example programs
- Compile-time float division detection (
_float_div) to work around JS'sNumber.isInteger(7.0) === true - Fail-fast on unsupported AST nodes,
_tagfield name collision detection
- New
Security
- Sandbox escape fixes: Block
str.format()C-level attribute traversal, restricttype()to single-arg form, removetypesandoperatorfrom safe import allowlist, wrap imported modules in proxy to block C-level escapes - Builtin hardening: Remove dangerous builtins (
id,pow,object) from both sandbox modes, fixhasattr/getattrpolicy inconsistency, unifySAFE_DUNDERSinto a single constant - Resource exhaustion prevention: Add collection size limits to multiply operator, enforce collection size limits in compiled code path, pre-check collection size before allocation to prevent OOM, cap timeout and
max_stepson/runendpoint - Bounds checking: Add bounds checking to
set_atin runtime prelude, add bounds checking to compiled index access via_safe_index - Parser hardening: Add nesting depth limit to prevent stack overflow DoS, add nesting depth check to
_parse_not_expr - Input validation: Reject negative
Content-Lengthto prevent unbounded read DoS, reject user-defined names that shadow security-critical prelude functions, detect class definitions in static safety validator - Step budget: Count steps for builtin function calls, count steps in
_values_equaland_match_patternto prevent budget bypass - Server hardening: Catch unexpected exceptions in
/runhandler to prevent info leak
Fixed
- Fix process sandbox silently swallowing empty-message exceptions
- Fix interpreter stdout leak, helper name shadowing, server capability enforcement, and example verification parity
- Fix compiler string escaping: escape
\r,\t, and\0in generated Python - Fix exported
safe_hasattrto allowSAFE_DUNDERSlikesafe_getattr - Mangle Python keywords in compiled output to prevent
SyntaxError - Sanitize special float values (
NaN/Infinity) for valid JSON output
Changed
- Benchmark expanded from 55 to 77 problems across 5 difficulty levels (trivial, easy, medium, hard, expert) with coverage for ADTs, pattern matching, pipelines, Option/Result types, contracts, and new domains (trees, graphs, linked structures, systems)
[0.2.0] - 2026-02-06
Added
Embedding API:
geno.run()andgeno.check()for programmatic useRunConfigwith configurable capabilities, timeouts, step budgets, and modulesRunResultandCheckResultwith structured diagnostics and timing infovalue_to_json()for serializing Geno values to JSON-compatible Python objects
Capability-Based Security:
- Capability gating for all I/O and non-deterministic builtins
RunConfig(capabilities={...})to grant specific capabilitiesRUNTIME_CAPABILITY_DENIED(E412) raised when calling a gated builtin without the capability- Capability map:
print,clock,random,fs,http
New Builtins:
clock_now()— current Unix timestamp as Int (gated byclock)random_int(min, max)— random integer in range (gated byrandom)random_float()— random float in [0, 1) (gated byrandom)fs_read_text(path)— read file via host callback (gated byfs)http_fetch(url)— fetch URL via host callback (gated byhttp)
Host Callbacks:
RunConfig(host_callbacks={...})for host-provided implementations offs_read_textandhttp_fetchRUNTIME_HOST_CALLBACK_MISSING(E413) raised when capability is granted but no callback is provided
Step Budgets:
RunConfig(max_steps=...)to limit computation (default: 10,000)RUNTIME_STEP_LIMIT(E503) raised when step budget is exceeded
Error Codes and Diagnostics:
ErrorCodeenum with structured codes for all error categoriesDiagnosticdataclass with severity, code, message, and location- Error code ranges: E1xx (lexer), E2xx (parser), E3xx (type), E4xx (runtime), E5xx (limits)
Modules:
import ModuleNamestatement for in-memory module resolutionRunConfig(modules={...})to provide module sources as a dictionary- Transitive module imports with circular import detection
geno bundleCLI command for packaging multi-file projects
Constrained Decoding:
importkeyword support in the constraints moduleallow_type_identifierfield onAllowedNextfor module name completion
Hardening:
- Security regression corpus with 8 parametrized test cases
- Reference Docker configuration (
Dockerfile,docker-compose.yml) - Enhanced fuzzing: import fuzzing, capability combo fuzzing, host callback fuzzing
- Constraint golden tests for
importkeyword - Expanded SECURITY.md with capability threat model, host callback trust model, module security, and agent execution guidance
Changed
- Version bumped from 0.1.1a1 to 0.2.0
[0.1.1a1] - 2025-01-18 (Alpha Release)
This is an alpha release intended for early adopters and research use. The language design may evolve based on LLM code generation experiments.
What works:
- Complete language implementation (lexer, parser, type checker, interpreter, compiler)
- Secure sandbox with ProcessSandbox (hard timeouts, output limits)
- 351 tests passing
- Benchmark suite with 55 problems
What may change:
- Language syntax and semantics (based on LLM evaluation results)
- Spec details (currently marked as draft)
- API surface for programmatic use
Security
- CRITICAL: Added
safe_getattrandsafe_hasattrwrappers to block reflection-based sandbox escapes - CRITICAL: Added static validation enforcement before code execution in strict mode
- CRITICAL: Fixed
compile_and_exec()to set up sandbox BEFORE exec(), preventing initialization attacks - CRITICAL:
run_sandboxed()now usesProcessSandboxby default for hard timeouts (DoS mitigation) - CRITICAL:
geno runCLI now uses ProcessSandbox by default for hard timeouts - Added
ProcessSandboxclass for subprocess-based execution with hard timeouts - Added
ProcessSandboxConfigwith configurable resource limits (memory, CPU, file size, strict mode, output length) - Added
max_output_lengthenforcement in ProcessSandbox to prevent memory exhaustion - Added
use_processparameter torun_sandboxed()to opt-out to thread-based sandbox - Added
--unsafeflag togeno runto use direct interpreter (no process isolation) - Added
--timeoutflag togeno runto configure execution timeout (default: 30s) - Added safe
__import__that only allows whitelisted modules (dataclasses, typing, math, copy, functools) - Fixed
get_field()in compiler to reject underscore-prefixed field names - Added
__build_class__to safe builtins for class definition support (both sandboxes) - Added
BLOCKED_ATTRIBUTESfrozenset with comprehensive list of dangerous reflection attributes
Added
Interpreter builtins:
reverse(list)- Reverse a listsubstring(str, start, end)- Extract substringis_some(opt)- Check if Option is Someis_none(opt)- Check if Option is Noneunwrap(opt)- Extract value from Some (raises on None)unwrap_or(opt, default)- Extract value or return defaultfloat_to_int(f)- Convert float to intint_to_float(i)- Convert int to float
Testing:
test_security.py- Comprehensive security escape teststest_cli.py- CLI command tests (geno run,geno check,geno compile)test_fuzzing.py- Property-based fuzzing tests using Hypothesis- Runtime security escape tests (
TestRuntimeSecurityEscapes) - ProcessSandbox tests (
TestProcessSandbox)
CI/CD:
- Coverage enforcement (fail if < 80%)
- mypy type checking job
- bandit security scanning job
- pytest-timeout for hanging test prevention
Documentation:
- Threat model section in SECURITY.md
- Known escape vectors (mitigated) documentation
- Security architecture documentation
- Contribution guidelines in README.md
Changed
Spec alignment:
- Type conversion functions now documented as
to_string(),parse_int()(matching implementation) - Added documentation for 11 extra builtins (
take_while,all,split_once,is_sorted, etc.) - Added documentation for typed holes (
?name: Type) - Added documentation for statement labels
- Type conversion functions now documented as
parse_int()now returnsOption[Int](Some/None) instead of raising on invalid inputSandboxContext.execute()now enforces validation before executionrun_sandboxed()now enforces validation before execution
Removed
- BREAKING: Removed
refparameter documentation from spec (not implemented)refparameters for pass-by-reference were documented but never implemented- Implementing them properly would require significant work
- Removed from spec to maintain spec/implementation alignment
Fixed
- Fixed import stripping in
compile_and_exec()that was breaking docstrings - Fixed
test_getattr_blockedregex to match actual error messages - Fixed sandbox to allow
__build_class__for class definitions
[0.1.0] - 2024-12-01
Added
- Initial release of Geno programming language
- Lexer with support for keywords, operators, and literals
- Recursive descent parser
- Static type checker with generics support
- Tree-walking interpreter
- Python code generator/compiler
- Interactive REPL
- Sandbox for safe code execution
- 55-problem benchmark suite
- Experiment framework for LLM evaluation
- Analysis tools and report generation
Language Features
- Explicit block delimiters (
end func,end if,end while) - Mandatory type annotations
- Specification-first design with
exampleclauses - Immutability by default (
letvsvar) - Named parameters for functions with 3+ parameters
- Exhaustive pattern matching
- Pipeline expressions (
|>) - Generic types (
List[T],Option[T],Result[T, E]) - Lambda expressions
- Contracts (
requires,ensures)
Rendered from CHANGELOG.md in the Geno repository.