<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>jm, branch HEAD</title>
<subtitle>Odin for scripts: small packages and a runner, on core: only</subtitle>
<entry>
<id>59ff9c1e3ec71d3fa4e9918c138683aaa2dd9088</id>
<published>2026-09-24T20:44:01Z</published>
<updated>2026-09-24T20:44:01Z</updated>
<title type="text">sqlite3: fuzz the bindings against the properties they promise</title>
<link rel="alternate" type="text/html" href="commit/59ff9c1e3ec71d3fa4e9918c138683aaa2dd9088.html" />
<author>
<name>Jack Mordaunt</name>
<email>jackmordaunt.dev@gmail.com</email>
</author>
<content type="text">commit 59ff9c1e3ec71d3fa4e9918c138683aaa2dd9088
parent 924a2d919032142e97c129583274c7073ea8c3da
Author: Jack Mordaunt &lt;jackmordaunt.dev@gmail.com&gt;
Date:   Thu, 24 Sep 2026 17:44:01 -0300

sqlite3: fuzz the bindings against the properties they promise

jm:sqlite3/fuzz generates values and damaged SQL and checks six properties:
a bound value reads back as itself, a value is never parsed as SQL, broken
SQL faults and leaves the connection usable, a mismatched argument list is
refused, a failed transaction leaves nothing behind, and a reused statement
stays honest. tools/sqlite3-fuzz runs it for a time or a count; `just test`
runs 600 cases on each of four fixed seeds.

A run is a function of its seed, and every report names the seed it used, so
a random run that fails replays exactly. Report.digest fingerprints the
randomness each case consumed, which is what makes that checkable on the runs
where nothing failed.

Two things the harness needed to be worth having. Its properties read every
row before comparing any of them, because comparing inside the loop passes
even when a column read hands back SQLite&#39;s memory instead of a copy:
deleting the clone from text leaves the example-based tests green and fails
one case in three here. And each case has a deadline, since a damaged
recursive CTE can return rows without end; sqlite3.interrupt is new, and is
what stops one.

</content>
</entry>
<entry>
<id>924a2d919032142e97c129583274c7073ea8c3da</id>
<published>2026-09-24T20:03:35Z</published>
<updated>2026-09-24T20:03:35Z</updated>
<title type="text">sqlite3: bind SQLite over the vendored amalgamation</title>
<link rel="alternate" type="text/html" href="commit/924a2d919032142e97c129583274c7073ea8c3da.html" />
<author>
<name>Jack Mordaunt</name>
<email>jackmordaunt.dev@gmail.com</email>
</author>
<content type="text">commit 924a2d919032142e97c129583274c7073ea8c3da
parent 190a3391f526379823270b8b2927884d367ef668
Author: Jack Mordaunt &lt;jackmordaunt.dev@gmail.com&gt;
Date:   Thu, 24 Sep 2026 17:03:35 -0300

sqlite3: bind SQLite over the vendored amalgamation

Scripts get a real database without installing one: the archive is compiled
from sqlite3/vendor by `just sqlite` and linked in, so ldd on a built script
names no libsqlite3.

Values are bound, never interpolated, which is the whole reason to bind the C
API rather than shell out to the sqlite3 CLI. bind refuses an argument list
that does not match the statement&#39;s parameter count, because SQLite would
otherwise store NULL for the ones left unset. text and blob clone what they
read: SQLite frees its copy on the next step and reuses the memory from its
own pool, so a stale pointer yields the next row&#39;s bytes rather than
crashing, and AddressSanitizer cannot see it.

foreign import resolves the archive relative to the package directory, which
is why lib/ sits there rather than in build/. odin check never opens it, so
`just check` still type-checks linux, darwin and windows from one machine
with nothing built.

</content>
</entry>
<entry>
<id>190a3391f526379823270b8b2927884d367ef668</id>
<published>2026-09-24T20:03:25Z</published>
<updated>2026-09-24T20:03:25Z</updated>
<title type="text">sqlite3: vendor the SQLite 3.53.4 amalgamation</title>
<link rel="alternate" type="text/html" href="commit/190a3391f526379823270b8b2927884d367ef668.html" />
<author>
<name>Jack Mordaunt</name>
<email>jackmordaunt.dev@gmail.com</email>
</author>
<content type="text">commit 190a3391f526379823270b8b2927884d367ef668
parent 72ed70346bbf6d2f3183971bd1c847f6d6b2dd73
Author: Jack Mordaunt &lt;jackmordaunt.dev@gmail.com&gt;
Date:   Thu, 24 Sep 2026 17:03:25 -0300

sqlite3: vendor the SQLite 3.53.4 amalgamation

sqlite3.c and sqlite3.h as sqlite.org publishes them, verified against the
SHA3-256 on its download page:
628a44cfe82c66aed1ccbbe85a562d2e33ebe64b3288981ed76285612227934e for
sqlite-amalgamation-3530400.zip, source id
bf7c7f30031888f4e796e429ab3978879485813aaca6f641c7b33e4e09459bcc.

shell.c and sqlite3ext.h are not taken; nothing here needs the CLI or the
extension headers. SQLite is public domain, so carrying it imposes no licence
obligation.

</content>
</entry>
<entry>
<id>72ed70346bbf6d2f3183971bd1c847f6d6b2dd73</id>
<published>2026-09-24T19:25:54Z</published>
<updated>2026-09-24T19:25:54Z</updated>
<title type="text">jm: rename the collection from jfm to jm</title>
<link rel="alternate" type="text/html" href="commit/72ed70346bbf6d2f3183971bd1c847f6d6b2dd73.html" />
<author>
<name>Jack Mordaunt</name>
<email>jackmordaunt.dev@gmail.com</email>
</author>
<content type="text">commit 72ed70346bbf6d2f3183971bd1c847f6d6b2dd73
parent f4d4f24b11ae3b81471ebba24353b6fce27ef10c
Author: Jack Mordaunt &lt;jackmordaunt.dev@gmail.com&gt;
Date:   Thu, 24 Sep 2026 16:25:54 -0300

jm: rename the collection from jfm to jm

Every import, the odin-run build define, ols.json, the justfile flags and
the test fixture names move from jfm to jm. The baked-in define becomes
JM_COLLECTION so it shares vocabulary with the ODIN_RUN_COLLECTION
environment variable it falls back from.

cache_dir&#39;s test now asserts the returned path ends in the app name, so a
stub that ignored the argument could no longer pass it.

</content>
</entry>
<entry>
<id>f4d4f24b11ae3b81471ebba24353b6fce27ef10c</id>
<published>2026-09-24T00:35:28Z</published>
<updated>2026-09-24T00:35:28Z</updated>
<title type="text">examples: add a script that touches every package</title>
<link rel="alternate" type="text/html" href="commit/f4d4f24b11ae3b81471ebba24353b6fce27ef10c.html" />
<author>
<name>Jack Mordaunt</name>
<email>jackmordaunt.dev@gmail.com</email>
</author>
<content type="text">commit f4d4f24b11ae3b81471ebba24353b6fce27ef10c
parent 8daab04c5e65654cc69fe2d3780c38f30ae9d4f2
Author: Jack Mordaunt &lt;jackmordaunt.dev@gmail.com&gt;
Date:   Wed, 23 Sep 2026 21:35:28 -0300

examples: add a script that touches every package

hello.odin is the smoke test for the collection and the shape a new
script copies: prelude.init for the context, sh for the shell, path for
files, timefmt for the log line and http for one request. just example
compiles and runs it through odin-run, and just check type-checks it for
all three targets beside the packages.

</content>
</entry>
<entry>
<id>8daab04c5e65654cc69fe2d3780c38f30ae9d4f2</id>
<published>2026-09-24T00:34:50Z</published>
<updated>2026-09-24T00:34:50Z</updated>
<title type="text">odin-run: compile a script once and run the cached binary after that</title>
<link rel="alternate" type="text/html" href="commit/8daab04c5e65654cc69fe2d3780c38f30ae9d4f2.html" />
<author>
<name>Jack Mordaunt</name>
<email>jackmordaunt.dev@gmail.com</email>
</author>
<content type="text">commit 8daab04c5e65654cc69fe2d3780c38f30ae9d4f2
parent 710fb57514be825119014419392beb08cad5e351
Author: Jack Mordaunt &lt;jackmordaunt.dev@gmail.com&gt;
Date:   Wed, 23 Sep 2026 21:34:50 -0300

odin-run: compile a script once and run the cached binary after that

A script only feels like a script when running it costs nothing after
the first time. odin-run takes a single-file Odin program, builds it
with the jfm collection on the include path when the cache is stale, and
executes the cached binary with the script&#39;s arguments. The cache key
covers the script&#39;s bytes and path, every .odin file in the collection,
the odin binary itself, the target and the build flags, so any change to
a dependency rebuilds. ODIN_RUN_COLLECTION overrides the root baked in
at install, ODIN_RUN_FLAGS adds build flags, ODIN_RUN_VERBOSE=1 prints
the build line, and -clean drops the cache. When the Odin tree ships
libcurl.dll it is copied beside the binary so http works on Windows.

</content>
</entry>
<entry>
<id>710fb57514be825119014419392beb08cad5e351</id>
<published>2026-09-24T00:34:01Z</published>
<updated>2026-09-24T00:34:01Z</updated>
<title type="text">tar: read git archive output without a tar program</title>
<link rel="alternate" type="text/html" href="commit/710fb57514be825119014419392beb08cad5e351.html" />
<author>
<name>Jack Mordaunt</name>
<email>jackmordaunt.dev@gmail.com</email>
</author>
<content type="text">commit 710fb57514be825119014419392beb08cad5e351
parent 08b3ecbe7ebc37c4ecfbbcaab7d11b7a0e240490
Author: Jack Mordaunt &lt;jackmordaunt.dev@gmail.com&gt;
Date:   Wed, 23 Sep 2026 21:34:01 -0300

tar: read git archive output without a tar program

A script that wants a clean tree of a commit runs git archive and has to
hand the bytes to a tar binary, which Windows may not have. extract
reads the archive in memory and writes its files under a destination:
ustar and GNU headers, the pax extended headers git writes for long
paths and the commit id, regular files and directories. Links, devices
and anything else are skipped, and a path that climbs out of the
destination is not written anywhere. The tests build a real archive with
git archive through sh and extract it whole.

</content>
</entry>
<entry>
<id>08b3ecbe7ebc37c4ecfbbcaab7d11b7a0e240490</id>
<published>2026-09-24T00:30:43Z</published>
<updated>2026-09-24T00:30:43Z</updated>
<title type="text">flow: add the worker-pool package from sonar</title>
<link rel="alternate" type="text/html" href="commit/08b3ecbe7ebc37c4ecfbbcaab7d11b7a0e240490.html" />
<author>
<name>Jack Mordaunt</name>
<email>jackmordaunt.dev@gmail.com</email>
</author>
<content type="text">commit 08b3ecbe7ebc37c4ecfbbcaab7d11b7a0e240490
parent 84ba2311339b9b36decc453e70010e7586a90e97
Author: Jack Mordaunt &lt;jackmordaunt.dev@gmail.com&gt;
Date:   Wed, 23 Sep 2026 21:30:43 -0300

flow: add the worker-pool package from sonar

Work that splits into independent items wants many threads without the
locks that make threads hard. Every shape here obeys one rule: a worker
owns one slot of the caller&#39;s states and never shares it, and the caller
merges the slots once the run is over. width says how many workers a
piece of work deserves, each claims items from a shared counter until
they run out, and manage does the same for work that discovers more of
itself as it goes, with a manager that queues what each finished item
reveals. The package is the copy sonar now imports; the tests cover
every item reached once, early stops, empty input and failed items being
requeued.

</content>
</entry>
<entry>
<id>84ba2311339b9b36decc453e70010e7586a90e97</id>
<published>2026-09-24T00:23:45Z</published>
<updated>2026-09-24T00:23:45Z</updated>
<title type="text">http: add a small client over vendor:curl</title>
<link rel="alternate" type="text/html" href="commit/84ba2311339b9b36decc453e70010e7586a90e97.html" />
<author>
<name>Jack Mordaunt</name>
<email>jackmordaunt.dev@gmail.com</email>
</author>
<content type="text">commit 84ba2311339b9b36decc453e70010e7586a90e97
parent 3636dc06b2e4e30205982ce3ac6a8d1765c69982
Author: Jack Mordaunt &lt;jackmordaunt.dev@gmail.com&gt;
Date:   Wed, 23 Sep 2026 21:23:45 -0300

http: add a small client over vendor:curl

Scripts fetch JSON from an API, post a payload and download a file, and
core has no HTTP client. get, post, post_json, get_json and download
cover those calls; request is the general form with a method, headers
and a body. Every call returns (Response, Error), and Error is set only
when the transfer could not complete: an HTTP 4xx or 5xx comes back as a
Response with ok false, so the caller decides whether a status is fatal.
vendor:curl links system libcurl on Linux and macOS and the bundled
libcurl.lib on Windows, so TLS, proxies and redirects come from whatever
curl build is linked.

</content>
</entry>
<entry>
<id>3636dc06b2e4e30205982ce3ac6a8d1765c69982</id>
<published>2026-09-24T00:22:32Z</published>
<updated>2026-09-24T00:22:32Z</updated>
<title type="text">timefmt: format and parse times with strftime directives</title>
<link rel="alternate" type="text/html" href="commit/3636dc06b2e4e30205982ce3ac6a8d1765c69982.html" />
<author>
<name>Jack Mordaunt</name>
<email>jackmordaunt.dev@gmail.com</email>
</author>
<content type="text">commit 3636dc06b2e4e30205982ce3ac6a8d1765c69982
parent 9e2ca18ae08c67d987b537c7a8ac9abef559cb2f
Author: Jack Mordaunt &lt;jackmordaunt.dev@gmail.com&gt;
Date:   Wed, 23 Sep 2026 21:22:32 -0300

timefmt: format and parse times with strftime directives

Log names, report headers and the timestamps in tool output all follow
strftime conventions, and core:time speaks a different dialect. format
renders a time in UTC from a directive string, local does the same in
the machine&#39;s zone, and parse reads one back. iso, stamp and date are
the three shapes scripts write most, and duration prints a time.Duration
the way a person reads it. Formatting is UTC unless the local variants
are used; parsing produces UTC.

</content>
</entry>
<entry>
<id>9e2ca18ae08c67d987b537c7a8ac9abef559cb2f</id>
<published>2026-09-24T00:21:16Z</published>
<updated>2026-09-24T00:21:16Z</updated>
<title type="text">path: add the file-system vocabulary a script reaches for</title>
<link rel="alternate" type="text/html" href="commit/9e2ca18ae08c67d987b537c7a8ac9abef559cb2f.html" />
<author>
<name>Jack Mordaunt</name>
<email>jackmordaunt.dev@gmail.com</email>
</author>
<content type="text">commit 9e2ca18ae08c67d987b537c7a8ac9abef559cb2f
parent 12abde45be05f4a725b7755c7bf389b23211c5f5
Author: Jack Mordaunt &lt;jackmordaunt.dev@gmail.com&gt;
Date:   Wed, 23 Sep 2026 21:21:16 -0300

path: add the file-system vocabulary a script reaches for

Reading a config, writing a report and walking a tree each take several
core:os calls with error handling between them. expand resolves a
home-relative path, mkdirs makes a directory tree, read, read_lines,
write and append_file move whole files, list and walk visit a directory
or a tree, and temp_dir and same answer the two questions scripts ask
about paths. state_dir, config_dir, cache_dir and log_dir find the
per-user folders on Linux, macOS and Windows so a script keeps its files
where the platform expects them. Every proc that can fail returns an
os.Error, so must composes with all of them.

</content>
</entry>
<entry>
<id>12abde45be05f4a725b7755c7bf389b23211c5f5</id>
<published>2026-09-24T00:18:56Z</published>
<updated>2026-09-24T00:18:56Z</updated>
<title type="text">sh: run external commands through the shell or by argv</title>
<link rel="alternate" type="text/html" href="commit/12abde45be05f4a725b7755c7bf389b23211c5f5.html" />
<author>
<name>Jack Mordaunt</name>
<email>jackmordaunt.dev@gmail.com</email>
</author>
<content type="text">commit 12abde45be05f4a725b7755c7bf389b23211c5f5
parent 2300ca1fa3f4d6f68a2eac3c0988ca67c32a316b
Author: Jack Mordaunt &lt;jackmordaunt.dev@gmail.com&gt;
Date:   Wed, 23 Sep 2026 21:18:56 -0300

sh: run external commands through the shell or by argv

A script is mostly other programs, and Odin&#39;s core:os/os2 process API
takes a dozen lines per call. out, lines, ok and run take a shell string
so pipes, globs and redirects work; exec and exec_run take an argv slice
so arguments need no quoting. capture is the general form: stdin, a
working directory, extra environment and a timeout that kills a child
that runs too long. which finds a program on PATH, quote makes a word
safe for the platform shell, and error turns a failed result into one
line for die. The shell is /bin/sh on Unix and cmd.exe on Windows unless
Opts.shell picks PowerShell, which behaves the same everywhere it is
installed.

</content>
</entry>
<entry>
<id>2300ca1fa3f4d6f68a2eac3c0988ca67c32a316b</id>
<published>2026-09-24T00:17:20Z</published>
<updated>2026-09-24T00:17:20Z</updated>
<title type="text">prelude: give scripts init, must, die and a log file</title>
<link rel="alternate" type="text/html" href="commit/2300ca1fa3f4d6f68a2eac3c0988ca67c32a316b.html" />
<author>
<name>Jack Mordaunt</name>
<email>jackmordaunt.dev@gmail.com</email>
</author>
<content type="text">commit 2300ca1fa3f4d6f68a2eac3c0988ca67c32a316b
parent 5a3b38321c0ebee3d9d6a5259be5d72069b08cca
Author: Jack Mordaunt &lt;jackmordaunt.dev@gmail.com&gt;
Date:   Wed, 23 Sep 2026 21:17:20 -0300

prelude: give scripts init, must, die and a log file

Every script starts the same way: pick an allocator, open a log, and
stop on the first error with a message someone can act on. init returns
the context to run under, with a growing arena as the allocator or the
debug allocator when ODIN_SCRIPT_DEBUG=1 asks for it, and finish reports
allocator issues at exit. must unwraps (value, ok) and (value, os.Error)
results. die writes the message and the call site to the script log, to
stderr and to one shared deaths.log, so every failed run across every
script is auditable. env and args read the environment and argv without
ceremony. platform_init makes the Windows console print UTF-8 and honour
ANSI escapes; elsewhere it does nothing.

</content>
</entry>
<entry>
<id>5a3b38321c0ebee3d9d6a5259be5d72069b08cca</id>
<published>2026-09-24T00:15:12Z</published>
<updated>2026-09-24T00:15:12Z</updated>
<title type="text">debug: add the guard-byte allocator copied from sonar</title>
<link rel="alternate" type="text/html" href="commit/5a3b38321c0ebee3d9d6a5259be5d72069b08cca.html" />
<author>
<name>Jack Mordaunt</name>
<email>jackmordaunt.dev@gmail.com</email>
</author>
<content type="text">commit 5a3b38321c0ebee3d9d6a5259be5d72069b08cca
parent 95faf8ee1e90ee174a3a590abafecc77bd6d3af1
Author: Jack Mordaunt &lt;jackmordaunt.dev@gmail.com&gt;
Date:   Wed, 23 Sep 2026 21:15:12 -0300

debug: add the guard-byte allocator copied from sonar

Scripts never free, so the one allocator bug class left is a write past
a block or into a freed one. This debug allocator wraps a backing
allocator with guard bytes, quarantine and poison, and reports overflow,
double free, write after free and leaks with the allocation site, so a
reader can fix the bug from the output alone. It is the copy sonar now
imports; a later commit lets a script swap it in behind one flag.

</content>
</entry>
<entry>
<id>95faf8ee1e90ee174a3a590abafecc77bd6d3af1</id>
<published>2026-09-24T00:10:02Z</published>
<updated>2026-09-24T00:10:02Z</updated>
<title type="text">readme, justfile: shape the jfm collection for Odin scripts</title>
<link rel="alternate" type="text/html" href="commit/95faf8ee1e90ee174a3a590abafecc77bd6d3af1.html" />
<author>
<name>Jack Mordaunt</name>
<email>jackmordaunt.dev@gmail.com</email>
</author>
<content type="text">commit 95faf8ee1e90ee174a3a590abafecc77bd6d3af1
Author: Jack Mordaunt &lt;jackmordaunt.dev@gmail.com&gt;
Date:   Wed, 23 Sep 2026 21:10:02 -0300

readme, justfile: shape the jfm collection for Odin scripts

Scripts written in Odin need a home for shared packages. This is the jfm
collection: eight packages, one runner, tests and a three-target
type-check. The README is the reference the following commits fill in,
the justfile holds the recipes, odinfmt.json and ols.json point the
formatter and the language server at the collection root, and .gitignore
keeps build/ out of the tree.

</content>
</entry>
</feed>
