Revision history for App::Ariza
0.2.4 2026-08-21T22:34:22+01:00
- Require Template::Jinja2 0.3.0 (Python method dispatch, Jinja2-faithful
filters, better template error messages).
0.2.3 2026-08-14T21:54:28+01:00
- Fix Windows Notcurses startup failures with error 0x7e. Bundle
launchers now put the exact staged Notcurses tag/lib directory first
on PATH, followed by SQLCipher, across the .cmd, PowerShell and native
runner sidecar implementations. NOTCURSES_NATIVE_DATA_DIR still points
at the data root so Notcurses::Native retains its terminfo setup.
- Give direct `{raku}` smoke commands the identical Windows PATH order,
built on a minimal System32-only base instead of the CI host's MSYS2,
Visual Studio and vcpkg toolchain PATH. This prevents a missing bundle
dependency from being borrowed from the build machine.
- Automatically exercise Notcurses in every declared bundle smoke without
opening a terminal. The probe calls `nc-lib()` first, which makes the
Windows resolver load the full FFmpeg-linked DLL closure with its sibling
directory, then creates and destroys a one-pixel visual through the core
library as an operational proof. A core-only version query can no longer
hide a broken multimedia DLL closure.
- Correct the Windows PE-audit contract: it proves that import-table
closure is complete and adjacent inside the bundle, not that Win32's
live loader will search beside an absolute top-level DLL. The launcher
PATH and the target-platform full-library smoke are the runtime proof.
- Scaffolded workflows now require
`App::Ariza:ver<0.2.2+>:auth`, preventing a release lane
from resolving an older builder that still emits the broken Windows
launcher environment or lacks the full-library smoke.
0.2.1 2026-08-13T23:28:11+01:00
- Correct the runner-v2 checksums shipped in 0.2.0. Windows bundle
builds now accept the published x86_64 and ARM64 runners while still
refusing any binary whose bytes do not match the embedded pin.
- Verify the pinned runner release, its published checksum document and
both actual binaries during Mi6's pre-upload distribution tests. A stale
or incomplete runner pin can no longer reach Zef through `mi6 release`.
0.2.0 2026-08-13T22:49:37+01:00
- Publish runner-v2
- Added opt-in managed-install update prompts. Apps enable them with
`[updates] enabled = true`; eligible interactive launches check the
configured GitHub repository at most once a week and offer `Install &
use`, `Ask next time`, or `Don't ask again for this version`. Portable
bundles, redirected sessions, CI, help/version invocations and guarded
relaunches bypass the check. Only the exact latest bare ASCII `X.Y.Z`
release is eligible; prereleases, `v` tags and build suffixes are not.
- Update-enabled bundles carry a generated, core-only Raku coordinator
and a trusted snapshot of their platform installer. A chosen update
downloads the exact candidate and mandatory SHA-256, validates the
extracted manifest's application, repository, protocol and paths, then
switches `current` transactionally. Public URL and insecure installer
escape hatches are unavailable to this private path.
- Successful updates retain exactly the new version and the physical
version that was current immediately before the switch, with `previous`
pointing at that rollback target. Windows records bounded deferred
cleanup when the running bundle cannot yet be removed; later installs
retry it without following reparse points or deleting a current,
previous or running version.
- POSIX launchers and the Windows native runner authenticate the installer
handoff with a per-launch 256-bit nonce and a path-free protocol record
before honoring reserved status 75. `Install & use` performs one guarded
relaunch with the original argv; an application's ordinary exit 75 is
returned unchanged. Update-enabled Windows builds require runner-v2 or
later and refuse a script-only or runner-v1 bundle.
- Generated release CI rejects non-bare tags for update-enabled apps, and
bundle manifests, smoke checks and licensing inventories now account for
the coordinator, installer snapshot and runner capability. Update
support remains absent from bundles whose config does not opt in.
- Generated workflows now require
`App::Ariza:ver<0.1.4+>:auth`, so a builder that predates the
`[updates]` table cannot silently ignore an app's opt-in.
0.1.3 2026-08-13T13:22:42+01:00
- Generated CI installs App::Ariza with a 0.1.2+ version floor and
the zef:apogee author pinned, with the full package spec shell-quoted.
0.1.2 2026-08-13T12:43:51+01:00
- Windows PowerShell launchers no longer use PowerShell 7's ternary
operator. The generated `.ps1` now parses under Windows PowerShell
5.1 while retaining its LOCALAPPDATA-to-TEMP state-directory fallback.
- `ariza installers` defaults its `--branch` to `HEAD`. The library
moved to HEAD in 0.1.1 but `bin/ariza` kept declaring `main` as
its own default, which is the value a user who runs the command
actually gets — so the scripts rendered for the first release
after that change still pointed at a branch the repository does
not have, and the fix looked applied while the bug was fully
intact. t/07-cli now renders through the CLI and fails on a
hardcoded branch in any generated script, because a library
default proves nothing about the command.
0.1.1 2026-08-13T01:14:07+01:00
- The POSIX installer's parting message actually tells the user
their current terminal cannot see the command yet, and gives
them the paste-able `export PATH=…` line that fixes it there and
then. The old message keyed on $PATH after ariza_persist_path
had already exported the directory into the installer's own
environment, so the tell-the-user branch was unreachable — the
first real install ended with a command the terminal could not
find and no explanation. The message now keys on what
persist-path found before it touched anything.
- Installer URLs and the generated release notes reference the
`HEAD` ref instead of a hardcoded `main` branch.
raw.githubusercontent resolves `HEAD` to the repository's default
branch whatever it is called; `main` was a 404 for every
`master`-defaulted repository — including the first app to ship
with these installers, whose documented `curl | sh` line failed
on its first real use. `:$branch` still overrides for anyone who
wants a pinned ref.
- The Windows installer smoke keeps Windows' mandatory ambient
environment (SystemRoot, windir, ComSpec, PATHEXT, SystemDrive)
when it builds its cleared-environment child. `env -i` has no
Windows analogue: launching any .cmd resolves the interpreter
through ComSpec, so the fully-stripped environment made the OS
itself malfunction — ERROR_MOD_NOT_FOUND before the launcher ran
a line — and failed the first published Windows installer smoke
against a perfectly good bundle. These variables belong to
Windows, not to the runner's toolchain, so keeping them does not
weaken the isolation.
- The Windows installer smoke also launches the installed
`.exe` when the bundle ships one — the entry point PATHEXT
actually hands a user who types the bare command was otherwise
never exercised post-install, and it fails independently of the
.cmd path (no cmd.exe, no ComSpec involved).
- The scaffolded release workflow triggers on bare version tags
(`[0-9]*.[0-9]*.[0-9]*`) as well as `v*`. mi6 — the release tool
this scaffold's own audience uses — tags `0.3.0`, not `v0.3.0`,
and the first real mi6 release of a scaffolded app pushed a tag
no workflow fired on. The publish job was already prefix-agnostic
(it gates on `refs/tags/` and names the release from the ref),
so only the trigger list needed to learn the second shape.
- A bundle's Raku code is installed into the bundled runtime's own
`vendor` repository — `/rakudo/share/perl6/vendor` — and
no longer into `/site`, which quietly cost every user a
full recompile of the closure on their first launch. Rakudo
records what a precompiled unit depends on relative to the
repository holding it (`vendor#sources/`) for the four
repositories its registry has a *name* for — `core`, `vendor` and
`site` under the running interpreter's own prefix, `home` under
`$HOME` — and as an absolute path on the compiling machine for
every other one, including any directory named in `RAKULIB`. So a
bundle built in CI and unpacked under `~/.local/share` looked for
its own modules in the build agent's workspace, found nothing,
declared all 52 compiled units outdated and rebuilt the lot: 58
seconds on the first launch of the first bundle anyone installed,
against 1.8 seconds now, for a store that shipped complete and
correct. Nothing on the build machine could see it, because there
the paths still resolve.
- `ariza bundle` asks the bundled runtime what it calls that
repository before it warms a single module, and refuses to build
when the answer is nothing. It then reads the store's dependency
records back and fails on any that name a path instead of a
repository. Both checks exist because this failure is otherwise
invisible at build time and expensive exactly once per user: the
store is present, the right size, and worthless.
- `ariza smoke` gained a `precomp-relocatable` check that reads the
same records out of the unpacked archive. It is the one check in
that command which cannot be replaced by running the bundle:
smoke runs on the machine that built it, where a store bound to
the build tree still works perfectly.
- The manifest records `launcher.site`, the repository the launcher
points `RAKULIB` at, so anything inspecting a bundle afterwards
reads the answer out of the bundle rather than out of whichever
ariza happens to be installed. `ariza smoke` uses it, and falls
back to `site/` so that archives published before the move are
still checkable on their own terms.
- New `xt/05-relocation.rakutest`: builds a bundle of a two-module
fixture app, unpacks it somewhere else, deletes the build tree,
and fails if the relocated launcher recompiles anything —
RAKUDO_MODULE_DEBUG for Rakudo's own account of it, and the
store's file list for a second opinion. Deleting the build tree
is the step the whole suite was missing.
- The generated installers warm the app up before they say
goodbye: they run the launcher they just linked, once, with the
output suppressed and a line on screen saying so. Whatever a
first launch has to do that later ones do not — paging a few
hundred megabytes off a cold disk, building a per-user state
directory — now happens while an installer is visibly working
rather than the first time somebody wants the program. Every
path that reaches the parting message goes through it, including
the re-run that found the version already installed, which is
what people try when the last one did not take.
- `installer.warm` in ariza.toml says what to run: `--version` by
default (the one invocation every bundled app answers and
returns from, and already `bundle.smoke`'s canary), a string
split on whitespace, an array taken word for word, or `false` to
skip the step. An empty value is a load-time error rather than
either of the things it might mean: a launcher run with no
arguments starts the application, which does not return, and the
installers impose no timeout on purpose. Arguments are rendered
shell-quoted (and PowerShell-quoted, and splatted rather than
passed as one array-shaped argument), because this is the one
place where somebody else's string becomes script syntax.
- A warm-up that fails warns loudly and the install still
succeeds — no non-zero exit, no rollback. It deliberately
diverges from the fail-closed posture of `ariza bundle` and
`ariza smoke`, and the reason is the failure domain: those run
in our own pipeline before anything is published, where stopping
is cheap and correct, while this runs on a stranger's machine
after the bundle has been downloaded, checksummed and put in
place, where the same signal much more often means the machine
(no terminal, a sandbox, a scanner) than the release. Failing
there would delete a working program from somebody who has one.
The message says what did not complete, and that the app is
installed and worth running. The ruling and its reasoning are in
design.md §6.6.
0.1.0 2026-08-12T22:34:02+01:00
- The bootstrap-rung test captures the "no pinned runner build
yet" notice instead of letting it reach the harness's stderr. A
passing suite printing that line reads as a statement about the
checkout's real pin file, which the test's fixture never
consults — it confused exactly the person it should not have,
twice. The capture also turns the notice into an assertion, so
the loud-rather-than-silent contract is now tested rather than
incidental.
- Changed: the test suite's fixture application is now a fictional
one -- `App::ExampleApp` / `exampleapp` / "Example App" 9.9.9,
publishing from `example-org/App-ExampleApp` -- everywhere it used
to be a real consumer of this tool. ariza is general-purpose, and a
suite whose every golden launcher, installer, workflow and
licensing document is branded for one application reads like that
application is part of the product. It also meant the committed
goldens named a repository that does not exist under an
organisation that does. Illustrative examples in the Pod and in
`docs/` are untouched: those describe using ariza against a real
application, which is what they are for, and they are not fixtures.
Every golden was REGENERATED through its own generator rather than
edited, so what changed is the identity in them and nothing else;
line endings are as they were, CRLF for the Windows launchers and
the PowerShell installers.
- Changed: `t/18-licensing`'s golden takes `ARIZA_REGENERATE_GOLDEN`
like the launcher, installer and workflow goldens already did. It
was the only one that could only be updated by hand, which is the
same as saying it was the only one that could be updated from
something other than the generator.
- Note: the fixture's display name now contains a space ("Example
App"). That is free coverage rather than an accident -- every
template quotes `app_display` where it lands in code, and a
fixture that never exercises a space is one that cannot notice if
one stops being quoted.
- Fixed: `ariza smoke` crashed on Windows whenever the bundle it was
given was not a Windows one. The launcher check asked
`IO::Path.x`, and the platform it branches on comes from the
manifest rather than from the machine -- deliberately, so an
archive can be checked anywhere -- so a Windows box inspecting a
Linux bundle reached that call and died with `rindex search target
requires a concrete string, but got null`. That message is
MoarVM's, and the bug behind it is upstream: its `file-stat`
syscall never populates the stat object's `filename`, and its
Windows `stat-is-executable` reads exactly that field to find the
extension to compare against PATHEXT, so `.x` on Windows does not
return False -- it throws, for every file. Nothing here can fix
that, but nothing here should have been asking: the executable bit
needs both a POSIX bundle to have one and a POSIX host to read
one, and a POSIX bundle unpacked on Windows has no such bit
anyway, because NTFS has none and tar cannot set one. The check
now asks only where there is an answer, and reports the file it
found either way. The case that exposed it was the new
cross-platform smoke fixture, which is the first thing in the
suite to smoke a bundle whose platform disagrees with the host --
and is now the regression test for it.
- Fixed: `App::Ariza::Runner.pins` leaked a file handle on the one
path it is designed to take -- dying part way through a malformed
pin file. It read with `IO::Path.lines`, whose handle is closed
when the sequence is exhausted, and a `die` on line two of ten
exhausts nothing. POSIX never noticed; on Windows the file then
could not be deleted at all (`resource busy or locked`), so the
test that checks the parser's refusal blew up in its own cleanup
several assertions later, naming nothing useful. It slurps and
splits now -- the whole file read and the handle closed before a
single line is examined, which is what every other file read in
ariza already does -- and the test deletes the file it just fed to
the parser, so the leak fails as an assertion about handles rather
than as an explosion in a LEAVE block.
- Added: a Windows bundle now launches from `bin/.exe`, a
compiled launcher of ariza's own -- ~400 lines of C17 in the new
`runner/` directory -- and it is the documented entry point. The
`.cmd` and `.ps1` launchers still ship, still work, and are still
what the bundle falls back to; what changed is which one users
run, which one the installers put on PATH, and which one the smoke
harness proves. Two things a batch trampoline cannot fix are why
it exists. The first is that `%*` is not the user's arguments, it
is the user's arguments after cmd.exe has had a second go at them:
`^` is an escape character, `%VAR%` and `!x!` are expansions, and
a quoted argument gets re-split, so an app taking a prompt, a
regex or a snippet of code as an argument receives something
nobody typed -- and the damage happens inside the substitution,
where nothing can intercept it. The second is that AppLocker and
SRP script rules block a `.cmd` however it is invoked, including
from a parent process, and a locked-down ExecutionPolicy stops the
PowerShell twin on its own, which made a bundle unusable at
exactly the sort of managed desktop these apps are wanted on.
Exec-ing the `.cmd` from an exe would have kept both problems, so
the runner does the whole launch itself: GetModuleFileNameW for
its own path, the directory above `bin/` as the root,
SetEnvironmentVariableW for the environment, CreateProcessW for
the child, and the child's exit code as its own. No cmd.exe is
involved at any point.
- Added: the argument tail is copied, never parsed. The runner
follows the C runtime's argv[0] rule exactly -- leading whitespace
skipped, a quoted argv[0] ending at the next quote with no escape
processing (a path cannot contain one), an unquoted one ending at
the first space or tab -- and hands the child every byte after
that point unchanged. There is no re-quoting step for anything to
be lost in, so what the app's `@*ARGS` sees is what the user's
shell produced. The test table for that rule is the list of things
a batch file damages: carets, `%VAR%`, `!x!`, escaped quotes
inside quoted arguments, trailing backslashes before a closing
quote, unicode, and an unterminated quoted argv[0].
- Added: `bin/.ariza`, the sidecar the runner reads, rendered
like every other launcher and CRLF like its neighbours. It carries
the target script, the exec and display names, and then the
bundle's environment as ORDERED DIRECTIVES -- `set NAME=VALUE`,
`unset NAME`, `prepend-path VALUE`, applied top to bottom, with
`{root}` expanded to the bundle root the executable worked out at
run time. The runner has no idea what any of them mean. That is
the point, and it is the second design ruling here: an earlier
draft had `sqlcipher_dir` / `sqlcipher_lib` keys and a hardcoded
NOTCURSES_NATIVE_DATA_DIR, which put knowledge of every native
dependency a bundle might ever carry inside a pinned binary on its
own release cadence -- so adding a dependency would have meant a
runner release, a checksum commit, and a window in which a new
bundle could not be launched by an old runner. With directives
that knowledge stays in the renderer, beside the `.cmd` template
where it already was, and a future app-declared "recipe" can
contribute env lines without a sidecar format break.
- Added: the parser fails closed, twice over. An unrecognised
directive and an unknown `{token}` are both hard errors naming the
line number, because a bundle whose configuration mentions
something the runner cannot do would otherwise start an app with a
silently incomplete environment -- which fails later, further
away, and much worse than not starting. `#` starts a comment only
in the first non-blank column, since `inst#{root}\site` is a real
value and an inline-comment rule would truncate it into a RAKULIB
naming a relative directory.
- Added: `App::Ariza::Runner`, and the bootstrap ladder it
implements. The runner is built once per release by CI and pinned
by digest rather than compiled during `ariza bundle`, because
compiling it at bundle time would put a C toolchain in the
bundling path, on every machine, for every cross-build, to produce
a file that is identical for every app. `resources/RUNNER_VERSION`
names the release and `resources/runner-checksums.txt` pins the
artefacts, and that pin file has exactly two states: while it
lists nothing, a Windows bundle is built WITHOUT the executable
and the build says so once, loudly, which is byte-for-byte the
output ariza produced before the runner existed; once it lists
anything, a missing entry, a failed download or a digest mismatch
fails the build. That is the same precedent as Notcurses-Native's
checksums.txt, and it is what lets this ship before the first
`runner-v1` artefacts are cut without a placeholder anywhere.
There is deliberately no third rung, no `--no-runner` and no
`--skip-verify`: an unverified executable staged into a bundle is
not a degraded build, it is a different piece of software.
- Added: `.github/workflows/runner-release.yml`. Both lanes in MSYS2
-- UCRT64 for x86_64, CLANGARM64 for aarch64, the same toolchains
that build the notcurses packs a bundle already carries -- ctest
on the x86_64 lane, and publication ONLY on a pushed `runner-v*`
tag validated against `resources/RUNNER_VERSION`. A dispatch
builds and tests and stops, because "validate the recipe" and
"overwrite the artefacts every ariza release verifies against"
should not be one click apart. The executable is linked statically
and the lane then asserts its import table holds nothing but
Windows' own DLLs: a `libgcc_s_seh-1.dll` picked up from the build
environment would work on the runner, work on every machine with a
toolchain, and fail on a clean install -- the identical failure
shape to 0.0.7's vcruntime140.dll.
- Added: the runner is split so the interesting half is portable.
Config parsing, `{root}` expansion, path joining and the
command-line-tail rule are plain C17 over a character type that is
`wchar_t` on Windows and `char` everywhere else; the win32 API
calls live in one file that is not compiled at all off Windows. So
`cmake -B build -S runner && ctest --test-dir build` runs on a Mac
or a Linux CI box and covers the rules that are actually easy to
get wrong. One of those tests parses
`t/golden/launcher-windows-x86_64.ariza` -- the committed output
of the Jinja2 template, compared byte for byte by the Raku suite --
with the real C parser, because the renderer and the reader are in
different languages and nothing else connects them.
- Added: `ariza smoke` runs every `{exec}` command a second time
through `bin/.exe` when a bundle carries one, in addition to
the `.cmd` run and never instead of it, plus a `runner` check for
the executable and its sidecar. The two entry points set the same
environment by completely different means -- batch `set` against
SetEnvironmentVariableW, `%*` against a verbatim command-line tail
-- so one passing says nothing whatever about the other. The
check passes when there is no executable, because that is the
bootstrap state and not a fault; it fails when there is one with
no sidecar beside it, because that is a bundle that cannot start.
- Changed: the generated Windows installer accepts either entry
point. `Ariza-HasEntryPoint` looks for `bin\.exe` and then
`bin\.cmd`, so the "incomplete install" and "that is not a
bundle" checks stay true either side of the bootstrap. Nothing had
to change about PATH: the installer puts `current\bin` on it, and
PATHEXT already prefers an `.exe` to a `.cmd` of the same name.
- Note: the published runner binaries are not code-signed, so
SmartScreen shows its "unrecognised app" prompt on first run.
Signing needs a certificate and a signing story ariza does not
have today; saying so in the README, in the release notes and in
the design record is better than a prompt nobody warned about, and
the `.cmd` launcher is the way past one somebody would rather not
click through.
- Added: a bundle now says what it redistributes. Every build writes
`THIRD-PARTY.md` at the bundle root -- one row per component with
its version, its licence, its copyright and where that fact came
from -- and a `LICENSES/` directory holding the text of every
licence those rows cite. What it replaces was a table of filename
prefixes inside `App::Ariza::Bundle` mapping `libav*` to FFmpeg
and so on, which was wrong twice over: it had gone stale (it still
declared the notcurses pack's FFmpeg a GPL build because of
libx264, which that pack no longer ships) and it was knowledge
about other people's software living in the source of a tool that
bundles anybody's application. A stranger's app with a native
dependency ariza had never heard of got a row saying
`(unclassified)` and no way to fix it short of patching ariza.
- Added: the new `App::Ariza::Licensing` reads all of it rather than
remembering any of it, from four sources, and every row records
which one it came from. A native pack's own licensing kit --
`third-party.json` where it ships one, filtered to the platform
being built, and its generated `THIRD-PARTY.md` where it does not.
`resources/runtime-third-party.json`, ariza's maintained record of
the components that have nowhere else to speak from. The `license`
field of every distribution installed into the bundle, from its
own site repository AND from the one inside the vendored runtime,
which is where the zef that came down with Rakudo lives. And the
app's `ariza.toml`, for what it ships that ariza cannot see. The
row schema is deliberately the one Notcurses-Native's
`third-party.json` already uses, because that format is an
interface rather than something to reimplement, and the 0.2.0
recipe work contributes rows in the same shape without a break.
- Added: `resources/runtime-third-party.json` -- the vendored
Rakudo, NQP and MoarVM (Artistic-2.0), and the C libraries MoarVM
vendors under its own `3rdparty/`, which are the reason the file
exists: they are compiled into `libmoar` rather than shipped as
files, so no audit of a bundle's contents can find them and
nothing in the bundle can be asked about them. libuv (MIT, plus
BSD-2-Clause for `tree.h` and ISC for the inet_pton pair), dyncall
(ISC), DynASM (MIT), LibTomMath (public domain), cmp (MIT),
libatomic_ops (MIT -- its GPL files are build and test tooling
that MoarVM's own 3rdparty/README states are in no built binary),
mimalloc, rapidhash and zmij (MIT), musl's memmem (MIT, in a
directory called `freebsd`), Steve Reid's SHA-1 (public domain)
and msinttypes (BSD-3-Clause). Each was checked against the
LICENSE file of the submodule at the commit MoarVM pins, not
recalled. That set moves between MoarVM releases and which members
are compiled in depends on the platform, so the file documents
itself as a superset for the pinned runtime and names the
`[rakudo]` pin as the trigger to re-check it: naming a library
that did not make the binary costs a paragraph, omitting one that
did is a notice not given.
- Added: licence texts ship in `resources/licenses/` for the
identifiers ariza can be expected to resolve -- Artistic-2.0, MIT,
Apache-2.0, BSD-2-Clause, BSD-3-Clause, LGPL-2.1, LGPL-3.0,
GPL-2.0, GPL-3.0, Zlib, ISC, Unlicense and a public-domain
statement -- and are cited by SPDX identifier, with `-or-later`,
`-only` and `+` resolving to the text they qualify and `AND`, `OR`
and `WITH` treated as separators so every arm of an expression
travels with the binary. Anything else comes from a pack's own
`LICENSES/` or from a `license-files` entry in the app's config.
- Added: `[licensing]` in `ariza.toml`. `strict` promotes an
unattributed native pack from a warning to a failed build;
`[licensing.app]` overrides any field of the application's own
row (which otherwise derives from its META6 and its LICENSE file);
`[[licensing.third-party]]` declares what ariza cannot see -- a
font, a dataset, artwork -- and `[[licensing.dists]]` corrects a
dependency whose own metadata is absent or wrong. Unknown keys
warn and wrong types die, as everywhere else. `license-files`
entries are paths inside the app's repository and an absolute one
is refused: a licence text belongs to the repository that declares
it, and an absolute path in a committed config file is a path that
exists on one machine.
- Added: the fail-closed rulings, and the asymmetry in them. A Raku
distribution with no `license` field, or one whose licence has no
shipped text, FAILS the build -- naming every such distribution in
the closure at once rather than the first, since three unlicensed
dependencies should cost one build and not three -- with both
fixes: the field itself, or a `[[licensing.dists]]` row. The field
exists, most of the ecosystem fills it in, and an "unknown" row
there would be a hole nobody ever closes. A cited
licence text that is nowhere fails the build for the same reason.
A native pack with no licensing kit is a VISIBLE row saying so
plus a warning, because there ariza has nothing true to say: the
pack is somebody else's artefact, refusing to build would punish
an app for its dependency's packaging, and dropping it would hide
a redistributed binary. Whether that is fatal is the app's call,
which is what `licensing.strict` is.
- Added: a fallback for the pack every app actually has. The
upstream generator writes `THIRD-PARTY.md` into the archive and
keeps `third-party.json` in its own repository, so no pack in
circulation carries the manifest. Rather than report a complete
notice sitting inside the bundle as "unattributed", the document
is parsed -- but only on its own terms: the Summary table's exact
generated header is required, four columns and two labelled
Details bullets are read, and anything else returns NOTHING and
falls through to the unattributed row. Rows assembled out of the
wrong columns would be worse than no rows.
- Added: `ariza-manifest.json` gains a `licensing` object -- the row
count, how many were unattributed, and the sorted set of SPDX
identifiers the bundle is conveyed under -- so a release gate
downstream can test it without parsing prose. `unknown > 0` means
something in the bundle is unnamed, and the identifier set is
where a copyleft component that arrived inside a native pack
becomes visible to a policy that cares about one.
- Added: `components.runner` in the manifest, closing the last gap
in it. `bin/.exe` was the only artefact a bundle downloads
with no URL and no digest recorded anywhere, while the runtime
archive and SQLCipher both had one -- and it is the file a Windows
user actually runs. `App::Ariza::Runner.stage` now returns what it
staged (path, artefact, tag, URL and the digest it was verified
against) rather than only where it put it, and
`App::Ariza::Launcher.write` returns `{ written, runner }` so that
account reaches the manifest.
- Added: the merged document is deterministic. Rows sort by kind,
then folded name, then id -- never by the order a directory was
read in -- and nothing in it is a timestamp or a path from the
build machine, so two builds of the same inputs produce
byte-identical output and a golden test means something. Licence
texts are deduplicated by filename in a fixed priority -- the
app's copy, then a native pack's, then ariza's own -- and two
sources offering the same name with different bytes warn, naming
both, rather than resolving by whichever directory was walked
first. Nearest to the software wins because ariza's texts are SPDX
TEMPLATES, carrying ` ` where a real notice carries a
name: an OFL font's text has its Reserved Font Name filled in, and
a pack's own copy is the notice shipped beside its binaries, so
the generic text is the fallback for a licence nobody supplied and
never a replacement for one somebody did. A text no row cites is
removed from `LICENSES/` rather than left over from an earlier
build to mislead.
- Added: three more licence texts -- AGPL-3.0, X11 and OFL-1.1 --
taken from SPDX's own license-list-data, bringing the shipped set
to sixteen. All three came out of a scan of real installed
distributions rather than a guess at what might be wanted, and the
X11 text is byte-identical to the one the notcurses pack carries,
so a bundle with that pack merges the two without a word. A test
enumerates the whole set: adding or losing a text is a decision,
not a side effect.
- Added: `NOASSERTION` support, as a declaration and only as a
declaration. An app may write `spdx-license = "NOASSERTION"` in a
`[[licensing.dists]]` or `[[licensing.third-party]]` row after it
has looked and found nothing -- SPDX's own spelling for exactly
that -- and the row is rendered distinctly, called out at the top
of THIRD-PARTY.md beside the unattributed, and carries a GENERATED
sentence (not the app's wording) saying licensing was not asserted
and pointing at the component's own repository. No licence text is
looked up, because there is none. `ariza-manifest.json` counts
them in `licensing.noassertion`, apart from `spdx-ids`, so a gate
reading the identifier set never sees NOASSERTION sitting in it
looking like a permissive licence.
- Added: three rulings that keep NOASSERTION from becoming a place
to put things. A distribution whose OWN metadata says NOASSERTION
still fails the build -- that is not a declaration anybody made
about this bundle, it is "nobody has looked" spelled differently,
and the fix is for somebody to look and then say so in the app's
config with the evidence in `notes`. An application may not say it
about itself: NOASSERTION means a third party's licensing could
not be determined, and there is nobody to look on behalf of the
thing being built. And `licensing.strict` refuses a bundle that
contains one, because strict is the statement that this bundle
ships nothing it cannot name and "we could not find out" is not a
name -- the same rule the unattributed-pack check enforces,
applied to the other way a bundle can carry something nameless.
- Fixed: nothing derives a name from a resource's `.basename` any
more. `zef install` stages every META6 resource under a
content-hashed filename, so an installed ariza's `MIT.txt` is
`0E9B31....txt` and its data file is `A3FCDF....json` -- which
made the licence-text pool key on digests (every build failing on
a row citing `LICENSES/MIT.txt`) and every runtime row cite a
digest as its provenance. Both work perfectly from a checkout,
which is the only place the suite runs, so
`xt/04-installed-resources` now installs ariza into a throwaway
repository and asks the installed copy for both. A resource's name
is its META6 key; the file is only where the bytes are.
- Fixed: `App::Ariza::Config` validated arrays of tables lazily, so
a `[[licensing.third-party]]` row missing its required
`spdx-license` did not complain until something happened to
consume the result -- which was outside the caller's `try` and a
long way from the file that was wrong. The rows are built
eagerly now.
0.0.7 2026-08-12T15:58:10+01:00
- Fixed, and it is the same class of bug as 0.0.6's missing OpenSSL
one level further down: the Windows bundle was built against
vcpkg's SQLCipher, which is an MSVC build, which imports
`vcruntime140.dll`. That DLL is not part of Windows. It arrives
with Visual Studio, with the Visual C++ Redistributable installer,
or dragged in by some other application that shipped it --
`kernel32.dll` and `ucrtbase.dll` are in System32 on every Windows
10 machine ever installed, and `vcruntime140.dll` is on none of
them by default. So the bundle loaded on the machine that built
it, loaded on every CI runner, loaded on most developer laptops,
and refused to load on a clean install, which is the only machine
a bundle exists for. Every machine likely to notice was a machine
where it was invisible; the release lane in particular could not
have caught it, because a GitHub runner has the redistributable.
The Windows lane now installs MSYS2's
`mingw-w64-ucrt-x86_64-sqlcipher` instead, which imports
`ucrtbase.dll` -- the UCRT, which Windows 10 and later ship.
- Added: the redistributable gate. `PE-REDIST-DLLS` and
`pe-redist-dll` name the family -- `vcruntime*.dll`, `msvcp*.dll`,
`concrt*.dll`, `vcomp*.dll` -- and the PE audit now reports any
staged file that imports one the bundle does not carry. They stay
on `PE-SYSTEM-DLLS`, so nothing copies one in: they are
Microsoft's to redistribute, on Microsoft's terms, and app-local
deployment of them is a decision that belongs to whoever signs the
release rather than to ariza. The finding carries the whole
argument -- the clean-machine consequence, the UCRT-built library
that makes the question go away, and the fact that carrying the
runtime yourself satisfies the check too -- because the person
reading it has just watched the bundle work. `ucrtbase.dll` is
deliberately not in the family, which is the entire reason a UCRT
build is the answer and not another instance of the question.
- Added: Windows sourcing accepts either package manager's name for
the library. vcpkg's port produces `sqlcipher.dll` and MSYS2's
produces `libsqlcipher-0.dll`, so the canonical name wins where it
is found -- per directory, in priority order, matched
case-insensitively as the loader matches it -- and where no
directory holds it the search widens to `libsqlcipher*.dll`,
newest first by the same numeric-aware comparison the Linux pass
uses for EPEL's renamed soname. Whatever is found is staged under
the canonical name, which is safe for exactly the reason the Linux
rename is: `LoadLibrary` resolves a DLL by the leaf name on disk
and never consults the module's internal name, just as `dlopen`
never consults `DT_SONAME`. `origin` records what was actually
found, so the manifest never quietly renames anything.
- Added: MSYS2 to the Windows search. `MSYSTEM_PREFIX/bin` first
(whichever environment the shell is in), then a `VCPKG_ROOT` tree,
then `C:\msys64\ucrt64\bin` and `C:\msys64\mingw64\bin`. vcpkg is
still probed rather than dropped -- a machine that has only that
should build rather than stop -- and the audit above is what
refuses to ship the result.
- Changed: the Windows CI lane lost three steps and a cache. The
vcpkg port was a fifteen-minute source build, which is why the
lane carried an actions/cache restore/save pair wrapped around it;
the MSYS2 package is prebuilt, so both are gone and the install is
one `pacman` call against the MSYS2 that every windows runner
already has at `C:\msys64`. `-Sy` refreshes the package databases
first, deliberately: the runner image's copy is as old as the
image and MSYS2's mirrors keep only current versions, so a stale
database sends pacman after a file that is no longer there.
- Docs: the README is now about using ariza, and the reasoning that
produced it moved to docs/design.md. One document was doing both
jobs -- a section per verb interleaved with the ELF and PE
walk-and-copy arguments, the vcruntime analysis and the no-mirror
ruling -- so someone looking up `--sqlcipher-archive` read three
pages about DT_SONAME on the way. docs/Readme.rakudoc is
usage-first now: synopsis, what a build machine needs, a quick
start, one section per verb with its options and a worked example,
then reference material -- the bundle layout with what each
directory is for, the complete annotated ariza.toml schema, the
pin file, platform slugs, where SQLCipher comes from, what the
generated installers and workflows do, and an environment index
covering the five variables ariza reads while building, the five
its launcher exports to the app, the two the installers honour and
the three the test suite takes. Inline rationale survives only
where it changes what a user does -- smoke runs with a replaced
environment, so a bundle cannot pass by borrowing your shell's.
Nothing was deleted: every relocated paragraph is in
docs/design.md, organised as a design record and date-stamped
against the release that produced each ruling, from 0.0.1's
one-distribution-mechanism decision through 0.0.6's PE dependency
walk to this release's redistributable gate.
- Fixed: App::Ariza::CI's Pod still described the Windows build lane
as `vcpkg install sqlcipher:x64-windows`, which this release's
lane template replaced with MSYS2's UCRT package for the reason
the same release's audit now enforces. Corrected in both the lane
description and SEE ALSO, since a Pod that names the wrong package
manager is how the next person reintroduces an MSVC build.
0.0.6 2026-08-12T11:45:28+01:00
- Fixed, and this is the one that was actually breaking Windows:
`App::Ariza::Native.stage-sqlcipher` had a macOS branch and a Linux
branch for making the staged library self-contained, and no Windows
branch at all. A Windows bundle therefore got `sqlcipher.dll` on
its own, without the vcpkg OpenSSL it imports
(`libcrypto-3-x64.dll`), so `LoadLibrary` failed while resolving
imports on every machine that ran it -- surfacing as
"DBDish::SQLCipher needs 'sqlcipher.dll', not found", a message
about the DLL that *is* there produced by the DLL that is not. The
two previous fixes for that symptom (0.0.5's `runtime-env` PATH,
0.0.4's `zef.raku`) were both real bugs and neither could have made
it go away, because there was no OpenSSL in the bundle for any
PATH to point at. `!stage-pe-deps` now does for PE what
`!stage-elf-deps` does for ELF: read the imports, skip the ones
Windows provides, copy the rest in beside the library out of the
source library's own directory, and recurse. A vcpkg port installs
its whole runtime closure into one `installed//bin`, so
that directory is the search space and the contract -- an import
that is not in it fails the build naming the import, the directory,
and the one other thing it could have been.
- Added: `pe-imports`, a PE import-table reader in pure Raku (~80
lines) -- DOS header to `PE\0\0`, COFF header, optional header in
both PE32 and PE32+ shapes, data directory 1, the import
descriptors, and the section table that maps their RVAs back to
file offsets. Written out rather than shelled out to because there
is nothing to shell out *to*: `dumpbin` ships with Visual Studio
and `objdump` with neither, and a dependency walk that quietly
finds nothing when its helper is absent is precisely how the hole
above stayed open. Anything that does not parse dies naming the
file and what was wrong with it; there is no "I don't know" return,
because both callers would pass on one. Delay-load imports (data
directory 13) are deliberately not read, and the Pod says why.
- Fixed: the Windows half of the audit was a presence check --
"the payload is there and is not empty" -- on the reasoning that a
PE records no rpath to inspect. What it records is its import
table, which is the entire question, and a bundle holding only
`sqlcipher.dll` passed that check with two files and no findings.
The PE audit now resolves every non-system import of every staged
`.dll` against the directory the file sits in (which is how Windows
resolves it, and where ariza stages everything) and requires the
result to be inside the bundle, sharing the ELF audit's
separator-normalised containment check. `not found` is a finding, so
is a resolution outside the bundle, so is a `.dll` that is not a PE
or is empty, and a PE the parser cannot read stops the audit rather
than counting as checked. None of it needs Windows: the file is
read, not run, so the Windows verdict is as strong from a Mac as it
is on the machine the bundle is for.
- `PE-SYSTEM-DLLS`, the Windows skiplist, is measured rather than
guessed: the core Win32/UCRT/VC++/api-set names plus exactly the
DLLs that the 119 DLLs of the published notcurses Windows pack
import and do not carry (`gdi32`, `dwrite`, `secur32`, `winmm`,
`bcryptprimitives` and a dozen more). Without them the new audit
would have failed the Windows release lane on notcurses rather than
on SQLCipher. `xt/03-pe-imports.rakutest` (network-gated, like
xt/01) downloads that pack and re-measures the list against it,
parses all 119 DLLs, and runs the real audit over them staged the
way a bundle stages them.
- t/11-native.rakutest builds real PE files byte by byte -- a
hand-packed PE32/PE32+ with a live import table, which a downloaded
DLL could never be malformed enough to replace -- and drives the
parser, the skiplist, `pe-strays`, the staging walk and the audit
off them: 28 subtests, up from 24.
0.0.5 2026-08-12T10:44:52+01:00
- Fixed: `App::Ariza::Smoke.runtime-env` never gave a Windows
`{raku}` smoke command SQLCipher's location, so it died with
"DBDish::SQLCipher needs 'sqlcipher.dll', not found" even though
the DLL was staged correctly and the launcher templates already
set the same two variables. Windows resolves a DLL off `PATH`
rather than a linker environment variable, so `runtime-env` now
prepends the bundle's `native/sqlcipher` onto `PATH` and sets
`DBIISH_SQLCIPHER_LIB` to the DLL's absolute path there, mirroring
what `launcher-windows.ps1.j2` / `launcher-windows.cmd.j2` already
export before exec'ing raku (those templates needed no change —
verified against their goldens). t/14-smoke.rakutest gained
Windows-slug assertions for `runtime-env` alongside the existing
Linux ones.
0.0.4 2026-08-12T10:05:02+01:00
- Fixed: bundling for Windows died before it got near zef --
`App::Ariza::Rakudo.zef-bin` assumed a `zef.bat` in the runtime's
site `bin/`, and the official Windows zip ships no such thing --
only `zef.exe`, `zef.raku` and the `-m`/`-j`/`-js` variants.
`zef-bin` now returns `zef.raku` on Windows: the `.raku` stub
(`sub MAIN(*@, *%) { CompUnit::RepositoryRegistry.run-script("zef") }`)
is byte-identical across every platform zef ships for, so running it
under the bundled `raku` resolves zef purely out of that raku's own
site repository, with no assumption about a wrapper's format.
`zef.exe` would work too, but a compiled wrapper's own
raku-discovery behaviour inside a relocated bundle is unverified.
App::Ariza::Site gained `zef-cmd`, the pure function that decides
how to invoke whichever `zef` it is handed -- exec the shell wrapper
directly on POSIX (unchanged), or run the `.raku` stub under `raku`
on Windows -- read off the file itself, not off a platform slug.
- Fixed: the Linux release lane's SQLCipher build step died on its
own last line. `ldconfig -p | grep -i sqlcipher` ran under
`set -euo pipefail` after a build that had fully succeeded, and
exited 1 -- `ldconfig` indexes by SONAME, which
`--dll-basename=libsqlcipher --soname=legacy` deliberately sets to
`libsqlite3.so.0`, so the grep can never match. This is the exact
trap xxt/linux-selfcontain-proof.sh's `find-sqlcipher-lib` already
worked around; the CI template's sanity check now asks the
filesystem the same way (`test -f
/usr/local/lib/libsqlcipher.so.0` + `ls -l`) instead of the linker
cache.
0.0.3 2026-08-12T09:38:21+01:00
- `scaffold-ci` now gives every declared platform with a GitHub-hosted
runner its own clean-machine installer smoke, not just Linux:
`smoke-installer-macos-arm64` (the only coverage install.sh's BSD
branches -- `shasum -a 256`, bsdtar -- ever get), the renamed
`smoke-installer-linux-x86_64-glibc`, and a new
`smoke-installer-windows-x86_64` that downloads the published
windows-x86_64 bundle, runs the committed install.ps1 against it
with `-Url`, and launches the installed `.cmd` under a
from-scratch environment built with `System.Diagnostics.Process`
(PowerShell has no `env -i`). The macOS/Linux/Windows split lives in
one place, `%LANES` in App::Ariza::CI, alongside each platform's
build lane; a platform with a build lane but no smoke recipe yet
renders no smoke job and a rendered comment says so, instead of
silently having none.
- Fixed: the Linux release lane's SQLCipher build, broken by the first
real `release.yml` dispatch. `--enable-tempstore` is gone in 4.14 --
SQLCipher moved to SQLite's autosetup configure, which spells the
same flag `--with-tempstore` -- and 4.14 links OpenSSL 3 (its codec
calls `EVP_MAC_*`, absent from EL8's default OpenSSL 1.1), which EL8
parallel-ships under /usr/include/openssl3 and /usr/lib64/openssl3
rather than as the system OpenSSL. The build now installs
openssl3-devel, points CFLAGS/LDFLAGS at that tree, and passes
`--dll-basename=libsqlcipher --soname=legacy` -- autosetup otherwise
names the library libsqlite3, not the libsqlcipher.so.0 every
consumer here looks for -- plus an /etc/ld.so.conf.d entry, since
EL8's ldconfig does not search /usr/local/lib by default.
xxt/linux-selfcontain-proof.sh carried the same dead flag and an
OpenSSL 1.1 recipe; it now derives its source-build version from
resources/versions.toml's `sqlcipher` pin instead of a stale
hardcoded default, and shares the fixed recipe.
- Fixed: the Windows release lane installed ariza (and everything else
in the job) under `defaults: run: shell: bash`, which made zef --
including the one `ariza bundle` runs internally for the bundled
site install -- resolve `tar` off Git Bash's MSYS build. GNU tar
reads a leading `C:` in a path as a remote hostname, so any
mixed-separator temp path zef handed it aborted the extract. The
lane now runs under the runner's default pwsh instead (proven
working already by App-Moneymoor's test.yml Windows job), with the
bundle and smoke steps ported to PowerShell and their exit codes
checked explicitly against `$LASTEXITCODE`, the same pattern the
installer-smoke workflow already uses -- PowerShell does not fail a
step on a nonzero native exit code the way bash's `set -e` does.
0.0.2 2026-08-12T03:02:19+01:00
- Every Windows CI failure across the suite's first two runs on a
real Windows machine was a genuine portability bug rather than a
test being fussy, and four of them were in lib/. All are fixed
below; the fixes are fixture-tested from every platform.
- `sha256-file` now falls through instead of stopping at the first
tool that disappoints it. sha256sum, `shasum -a 256` and
`certutil -hashfile` are each tried in turn, and absence, an
unspawnable binary, a non-zero exit and output that holds no digest
are all "try the next one"; only an exhausted list dies, naming
what every attempt did. The Windows failure this fixes: a GitHub
runner has a `shasum` on PATH that is a Perl script with no
interpreter association, so `where` finds it and CreateProcess
refuses it -- which took down every digest on the machine, and with
it t/08, t/09 and t/11 mid-file, while certutil sat untried behind
it. certutil stays last because Linux distributions ship an
unrelated NSS tool of that name; being last means its refusal is
simply one more failed attempt. There is still no "skip
verification" path. `sha256-file` takes the usual `:&run` seam now,
so the output parsers -- including certutil's banner, its
upper-case hex and the spaced-out byte pairs older builds print --
are covered by unit tests on machines that have never seen one.
- `resource-list` returns forward-slash paths on every platform. In a
checkout it walks the filesystem, and `.absolute` speaks the native
separator, so Windows was handing back `templates\ci\...` for what
is a META6 `resources` key -- the string that gets fed back through
`resource` and compared against the manifest, and forward-slash by
the packaging spec. Normalised at the one seam where the filesystem
is consulted, so a checkout and an installed run agree.
- New .gitattributes declaring `* -text`. The golden files in t/ are
compared byte for byte, read with `slurp(:bin)` precisely so a
line-ending difference cannot be shrugged off; Windows CI images
set `core.autocrlf=true`, which rewrote every LF golden on checkout
and failed t/12, t/15 and t/16 against output that was rendered
correctly. `-text` turns conversion off in both directions, which
also preserves the goldens that are deliberately CRLF -- the
Windows launcher twins and the PowerShell installers, which exist
because cmd.exe mis-parses an LF-only batch file. The templates
those goldens are rendered from are covered by the same rule: a
template rewritten on checkout would silently change what ariza
ships. The three golden comparisons now also diag when the only
difference is CRLF, so a stale working tree says so instead of
printing two identical-looking scripts.
- Test fixtures that spoke POSIX now speak whatever the platform
does. t/01 and t/09 and t/12 normalise the separator before
comparing a relative path against the bundle-internal spelling it
is asserting about; t/14 derives its expected environment values
from the same IO::Path it hands the code under test, since a bare
`/b` acquires a drive letter on Windows and the assertion is that
the environment names the bundle root rather than that it spells it
the way macOS would; t/08 writes its digest fixtures as bytes,
because a digest is a statement about exact bytes and a text write
is entitled to opinions about line endings.
- `sha256-file` no longer asks any tool to hash an empty file; the
SHA-256 of empty input is a constant, and both certutil and
sha256sum answer it badly. certutil cannot map a zero-byte file at
all and fails outright (`ERROR_FILE_INVALID`, a documented quirk of
the tool rather than a transient), and sha256sum, given a Windows
path (backslash separators), switches to GNU coreutils' "escaped
output" format, fusing a leading backslash onto the digest -- which
a parser anchoring the hex at the start of the line then misses
entirely, reading "exit 0, but no digest in its output". The digest
parser is fixed too, independent of the short-circuit: it now takes
the first 64-character run of lowercase hex anywhere on the line,
so a leading backslash simply isn't part of the match.
- The Linux ELF audit's containment check normalises both sides of
the comparison to forward slashes before deciding whether a
resolved dependency lands inside the bundle. `ldd`-style resolution
hands back a target spelled in the host's native separators, while
the `:@inside` bundle-root prefixes may not share that spelling --
and on Windows CI a bundled `libcrypto.so.3` resolving to a path
genuinely inside `native/sqlcipher` was flagged as a stray purely
because one side had backslashes and the other did not.
Containment is a path-identity question, not a byte question.
0.0.1 2026-08-12T02:13:21+01:00
- The Windows release lane caches SQLCipher instead of building it
from vcpkg on every run. `actions/cache/restore@v4` restores
`C:\vcpkg\installed` under the key `vcpkg-sqlcipher-x64-windows-v1`
before the build, and the build step itself is gated on the miss.
The save is a separate `actions/cache/save@v4` step immediately
after the build, not the combined `actions/cache` action, which
only saves in a post step gated on the job succeeding -- a failed
smoke later in the same run would otherwise discard a fifteen-
minute build every single time. The dll-locating step that writes
SQLCIPHER_LIB_DIR stays unconditional, since it has to run whether
the library was just built or restored from cache.
- The legacy installer machinery is retired. App::Ariza::Legacy, the
eight Jinja2 templates and two shell partials under
resources/legacy, the per-app configs, the SQLCipher mirror
reference material, `ariza render-legacy` and the parity test are
all gone. The ruling behind it: apps get retired rather than
carried, and an app that is ready to ship ships the way Moneymoor
does -- a bundle, four generated installer scripts and two
workflows. Keeping a second distribution mechanism alive for one
unreleased app meant two ways to install everything, two things to
keep true of every pin bump, and a parity gate that could only ever
say "the old thing still renders the old bytes".
- The absorption was not wasted. The parts of the legacy partials
that a bundle installer still needs -- the marker-block PATH
persistence, the shell-rc detection -- were adapted into
resources/templates/installer-common-{posix.sh,windows.ps1} when
`ariza installers` was written, and those stay. What has been
deleted is only what drove package managers, refcounted a shared
registry and installed a terminal emulator: everything a bundle
exists to not do.
- versions.toml loses `wezterm`, `wezterm-macos-sha256` and
`sqlcipher-release-base`. Each was read by exactly one consumer,
the legacy render context, and by nothing else in lib/ or in any
live template. `sqlcipher` stays (the advisory pin a bundle is
expected to carry), `rakudo-star` stays, and `[rakudo]` -- the
runtime a bundle actually embeds -- is untouched. The matching
accessors and their tests go with the keys.
- `rakudo-star` follows in the same cleanup: the legacy renderer was
its only consumer, and with that gone the pin named a Rakudo
release nothing in ariza installs on anyone's behalf. `sqlcipher`
and `[rakudo]` are unaffected; App::Ariza::Versions drops the
accessor and the parse arm, and their tests go with it.
- Documentation stops naming the directory it was developed in. The
distribution description, the NAME line and the example output
paths no longer carry a private path segment, or imply that the
apps live together in one tree: ariza is a bundler for Raku
terminal applications, not for one person's directory of them. The `releases/latest` URL in the
installer section now uses the C<< ... >> Pod form, since the
E/E escapes rendered literally through C<>.
- Phase four: `ariza scaffold-ci`. App::Ariza::CI writes the two
GitHub Actions workflows an app needs into its own
.github/workflows -- a test.yml in the house shape, and a
release.yml that builds one bundle per platform the app declares,
smokes each one, and publishes them on a tag. `ariza bundle`
builds a bundle on the machine it is standing on; a release needs
one per platform, on machines that are actually those platforms,
because SQLCipher comes off the build host and there is no
cross-build to fall back on. That is a CI problem, so this is CI.
- One build lane per declared slug, each installing what ariza needs
before running the same three steps (bundle, smoke, upload):
macos-latest with `brew install sqlcipher`; ubuntu-latest inside
quay.io/pypa/manylinux_2_28_x86_64, so the archive's glibc floor
is 2.28 (RHEL 8+, Ubuntu 18.10+, Debian 10+) rather than whatever
the runner image ships this month; windows-latest with
`vcpkg install sqlcipher:x64-windows` and SQLCIPHER_LIB_DIR
pointed at the result, which is the sourcing contract
App::Ariza::Native documents for Windows.
- The manylinux lane installs Rakudo by hand, out of the same JSON
release index App::Ariza::Rakudo reads and at the same [rakudo]
pin: Raku/setup-raku installs into the runner's tool cache, which
is not in the container's filesystem, and there is no URL pattern
to construct because upstream filenames carry a toolchain suffix.
It builds SQLCipher from source at the pinned version too --
EPEL's package is a 3.34.1-era build with a renamed soname, which
ariza can now find but which is years behind the pin.
- Every lane's Raku is there to RUN ariza and is not what ends up in
the bundle; ariza downloads the pinned runtime for itself. Which
is why setup-raku asks for `latest` and nothing in the workflow
pretends otherwise.
- publish is tag-gated, collects every lane's artefact, recomputes a
combined checksums.txt and then checks it against the .sha256
sidecars ariza wrote -- a release whose digest refuses its own
archive is worse than one with no digest -- before cutting the
release with a body that says what a bundle is, which machines
each archive runs on, and how to verify a download.
- smoke-installer is the job that matters: on a plain ubuntu-latest
runner with nothing installed on it, it downloads the archive that
was JUST published, installs it with the repository's own
committed install.sh, and runs the installed launcher under
`env -i`. It is the only check that exercises the artefact a user
will actually receive by the path they will actually take, and it
reads install.sh out of the checkout rather than from a raw
GitHub URL so that it works for the first release.
- workflow_dispatch triggers the build lanes and stops there, with
an optional `ref` input. That is the iteration loop and the reason
the trigger exists: a recipe that has gone stale costs a run and a
push to a branch rather than a burnt tag and a deleted release.
- release.yml is derived from bundle.platforms and is rewritten in
place on every run. test.yml is written only when it is ABSENT --
a test workflow acquires system dependencies, extra jobs and skip
conditions no generator can infer from a manifest -- unless
--force says otherwise, and each generated header says which of
the two it is. The scaffolded test.yml names the app's
bundle.native libraries and says it is deliberately not installing
them: what a bundle carries and what a test run needs on a runner
are different questions.
- A declared platform with no lane (musl, aarch64 Linux, the Intel
Mac, Windows on ARM) is a hard error rather than a skipped job,
the same reasoning as an unknown slug in bundle.platforms:
silently dropping one produces a release quietly missing a
platform the author asked for. The three that do have lanes are
the ones with both a GitHub-hosted runner and an official Rakudo
build behind them, which is deliberately the same shape as
App::Ariza::Rakudo's partial platform map.
- ariza.toml gains an optional [ci] table with one key,
`ariza-source`: "fez" (the default) or anything zef can install
from, for an app whose release workflow has to exist before ariza
is published. Whichever is not in use is rendered beside it as a
comment, along with the ariza version that scaffolded the file, so
switching is an uncomment rather than a remembering exercise. Not
a closed set, so only an empty value is an error -- it would
render a step that installs nothing and succeeds.
- Golden-file tests cover both rendered workflows, with the pins and
the ariza version supplied as fixtures so a golden fails when a
template changes rather than when a version is bumped, and
structural tests assert the things a golden diff would not
explain: the job list matches the declared platforms, publish
names every lane in `needs:`, both publishing jobs are tag-gated,
the container line is there, and no unrendered Jinja2 delimiter
survives (GitHub's own `${{ }}` does, which is exactly the
collision the templates escape). Verified with actionlint,
shellcheck and pyflakes: zero findings on both files.
- Sourcing SQLCipher on Linux no longer requires the canonical
libsqlcipher.so.0 soname. EPEL's `sqlcipher` package renames it
per version -- libsqlcipher-3.34.1.so.0, and nothing literally
called libsqlcipher.so.0 at all -- which failed sourcing on every
RHEL-family host. The exact canonical name still wins outright
when it is present; where it is not, any libsqlcipher*.so* from
the same two sources (ldconfig -p, then the standard library
directories) is considered, and the newest of several candidates
is taken by a numeric-aware comparison rather than a plain string
sort, with the manifest's origin naming what was actually found.
What lands in the bundle is unaffected either way: stage-sqlcipher
always copies to the canonical name, which is safe because
`is native('sqlcipher')` dlopens by leaf name through
LD_LIBRARY_PATH -- filename, never the file's own DT_SONAME.
- The Fedora/RHEL install remedy in the "no SQLCipher on this
machine" message now names the real package, `dnf install
sqlcipher` -- `sqlcipher-libs` does not exist and was never
installable advice.
- A Linux bundle now carries its own OpenSSL. It did not, and the
audit could not tell: a distro libsqlcipher.so.0 names OpenSSL as a
bare `NEEDED libcrypto.so.3` -- no path, no RPATH, nothing in the
file to object to -- which at run time means "whatever libcrypto
this machine has", and on a user's machine may mean nothing at all.
Staging an ELF now does what staging a Mach-O has always done:
walks the library's dependencies with ldd (the loader itself, so
the answer is the one the machine will really give), copies every
non-system one in beside it under its soname, recurses, and runs
`patchelf --set-rpath '$ORIGIN'` over the library and every copy.
The skiplist -- what stays dynamic -- is Notcurses-Native's
bundle-elf.sh list (the loader, libc, libm, libpthread, libdl,
librt, libgcc_s, libstdc++ and friends), with one deliberate
difference: libcrypto and libssl are NOT on it. That script leaves
them dynamic because ffmpeg's use of them is optional. SQLCipher
without OpenSSL is SQLite.
- The ELF audit grew the two checks that can actually see this, and
they only run on a Linux host because both tools answer for the
machine they are on: `patchelf --print-rpath` must be
$ORIGIN-relative (and present, on any file with a non-system
NEEDED), and `ldd` in a REPLACED environment -- bare PATH, i.e.
`env -i`, which is the user's view rather than the build's -- must
resolve every non-system dependency to a path inside the bundle,
with "not found" a finding. From a Mac, cross-inspecting an ELF is
the static half and says so rather than pretending to more.
- xxt/linux-selfcontain-proof.sh proves the above in a manylinux
container rather than asserting it: real SQLCipher, real Rakudo,
App::Ariza's whole test suite run on Linux, a real bundle staged
and audited through App::Ariza::Native, the result re-checked
independently in shell, and three negative controls (a planted
absolute NEEDED, a deleted dependency, a stripped rpath) that must
each make the audit fail. A proof that only ever passes proves
nothing. It is xxt/, so nothing in t/ needs docker.
- Binary formats are now read from the file's own magic number
instead of shelling out to `file`. A format probe that answers "not
a binary" when its helper is missing turns the audit into a loop
that checks nothing and passes silently, and `file` is exactly the
sort of tool a minimal build image leaves out.
- SQLCipher now comes from the build machine's package manager, and
there is no mirror to download it from -- the one named by
`sqlcipher-release-base` never existed, and standing one up would
have been a second piece of release infrastructure with its own
signing story, staleness and outages for a library whose ABI moves
about once a year. macOS takes the installed Homebrew keg (or, if
it is not installed, the bottle `brew fetch` puts in the cache);
Linux takes whatever `ldconfig -p` resolves libsqlcipher.so.0 to,
falling back to the Debian multiarch, Red Hat and Alpine library
directories; Windows takes a vcpkg tree under VCPKG_ROOT. CI
installs the distro package before it calls `ariza bundle`.
- What makes a package-manager library safe to ship is unchanged and
untouched: the self-containment pass still copies every dependency
that resolves outside the bundle in beside it, rewrites it to
@loader_path, re-signs, and the audit still refuses to package a
bundle where anything points off the build machine.
- --sqlcipher-archive is unchanged and still beats everything;
SQLCIPHER_LIB_DIR joins it as a second explicit override, naming a
directory rather than an archive. Both bypass the new cross-build
guard, which refuses to hand a `linux-x86_64-glibc` build the
macOS keg it is standing on -- a library installed here is built
for here, and an artefact made that way fails at dlopen on every
machine it was named for. Every "not installed" path dies naming
the package to install AND the override to pass.
- The `sqlcipher` pin is now advisory. The package manager decides
what is installed, so ariza reads the version out of the staged
library's own bytes (SQLCipher keeps CIPHER_VERSION as a
NUL-terminated X.Y.Z constant; filenames are no use -- a Homebrew
keg's real file is libsqlcipher.3.51.3.dylib, which is SQLite's
version, and Debian's is libsqlcipher.so.0.8.6, which is libtool's
current.revision.age) and warns on a mismatch rather than failing.
ariza-manifest.json records the version STAGED, the pin beside it
as `pinned`, the digest of the library as it came off the machine,
and which keg, bottle or archive it came from. It used to record
the pin, which was a number nobody had verified.
- Every slug ariza can name can now stage SQLCipher, musl and
windows-arm64 included: with the library coming off the machine
there is no per-slug publication question left to answer, only
"is it installed".
- Phase three: `ariza installers`. App::Ariza::Installer renders the
four scripts an end user actually runs -- install.sh, install.ps1,
uninstall.sh, uninstall.ps1 -- from the app's ariza.toml, into the
app's own repository by default, because `curl … | sh` has to be
able to fetch them from somewhere.
- One POSIX script covers macOS and Linux, rather than the legacy
pair. A bundle installer drives no package manager, so the only
per-platform decision left is which asset to download, and that is
a uname call away at run time: one curl line in a README, and
nobody has to work out which of two files applies to them. The
Linux branch probes for a ld-musl-*.so.1 loader before choosing
between -glibc and -musl, exactly as App::Ariza::Platform does.
- Detection produces one of the slugs the app *declares* in
bundle.platforms or nothing at all -- never a nearest match, since
a glibc bundle does not run on Alpine. An unnamed platform gets
"no prebuilt bundle for yet" and a link
to the releases page. An app that declares no Windows platform gets
no install.ps1 at all.
- Versions resolve from the `location:` header of the
releases/latest redirect: one HEAD request, no API token and no jq
on a machine that may have neither. --version names a tag instead.
- Every download is verified against the .sha256 asset published
beside it, and a missing or wrong digest is fatal. --url (or
_BUNDLE_URL, e.g. MONEYMOOR_BUNDLE_URL) installs from a
source the user names and bypasses GitHub entirely -- it takes a
plain file path as readily as a URL, which is what makes an
air-gapped install and ariza's own network-free end-to-end test
possible. --insecure-no-verify applies to that path only; a
published release always has a digest, so its absence there stays
fatal.
- The layout is $XDG_DATA_HOME//versions// with a
`current` symlink and a ~/.local/bin/ link. The new version
is unpacked into a staging directory on the same filesystem and
renamed into place before anything is flipped, so a failed or
interrupted download cannot damage a working install. One previous
version is kept to roll back to and anything older is pruned, by
name. Version directories are stamped at install time, not build
time: tar restores the original mtime, which is identical for every
bundle cut from one source tree.
- Re-running for a version already installed downloads nothing. It
re-points both links, re-checks the PATH block and exits 0 saying
"already installed", which makes "run the installer again" the
correct advice for the most common breakage.
- PATH persistence is the legacy dp_persist_path machinery, adapted
into an installer partial rather than sourced whole: a marker block
named after the executable (so uninstalling one app never removes
the line another needs), added only when ~/.local/bin is not
already on PATH, and only once per rc file. The uninstaller removes
exactly that block, rewriting through the existing file so its mode
and ownership survive, and leaves the state directory and the app's
own data alone -- naming them rather than deciding for you.
- The generated scripts are curl-pipeable: entirely definitions with
a single `main "$@"` on the last line, so a truncated download
cannot half-run one, and nothing reads standard input, because when
the script arrives *on* standard input there is nothing left to
read from.
- Windows mirrors all of it in PowerShell: %LOCALAPPDATA%\\
versions\, a `current` junction (a symlink needs
administrator rights or Developer Mode, which a per-user install
has no business demanding), and ...\current\bin added once to the
user PATH in HKCU\Environment -- which means an upgrade needs no
PATH change at all. Archives are unpacked with tar.exe, which has
shipped since Windows 10 1803 and reads the .tar.gz ariza actually
publishes for every platform; Expand-Archive handles a .zip if one
is ever pointed at with -Url.
- ariza.toml gains an [installer] table with one key, `repo`, the
GitHub owner/name releases come from. Optional in the schema and
required by `ariza installers`, which says so rather than
rendering a script that 404s. Its shape is closed like a platform
slug's: a full URL or a bare name is a hard error.
- Golden-file tests cover all four rendered scripts, `sh -n` checks
the POSIX pair, and a quoting gate walks both with the shell's own
quoting rules -- command substitutions and here-documents included
-- to prove every path expansion is inside double quotes. A new
xt/02-installer-e2e.rakutest drives a real bundle end to end
against a scratch HOME: install, launcher runs and reports its own
version, re-install repairs, upgrade flips current, a third install
prunes to one previous, a missing or wrong digest is refused
without disturbing the working install, and uninstall leaves both
rc files byte-identical to what they were.
- Phase two: `ariza bundle` and `ariza smoke`. A bundle is one
directory and one archive containing the app, a Rakudo runtime,
every Raku dependency with warm bytecode, and every native library
it loads. Nothing is installed, nothing is written outside it (bar
a one-line first-run marker under XDG_STATE_HOME), and deleting the
directory is the uninstall.
- App::Ariza::Rakudo resolves the [rakudo] pin against rakudo.org's
JSON release index, downloads the archive once into
$XDG_CACHE_HOME/ariza/rakudo and unpacks it into the bundle. The
cache records a sha256 on first download and verifies it on every
reuse; a mismatch discards the copy and re-fetches rather than
baking a bad byte into every bundle built on that machine.
rakudo.org publishes binaries for four of ariza's eight slugs, and
asking for one of the other four dies naming the four that work.
- App::Ariza::Site installs the app and its whole closure into the
bundle's own repository using the *bundled* zef, so the bytecode
matches the runtime that ships. It then warms the precompilation
store, which zef does not do: measured on Moneymoor, a cold bundle
spends ~55s compiling on first launch (and again on every launch,
if the bundle is somewhere unwritable) against ~0.5s warm. The
store is position-independent, so warming it at build time survives
being unpacked anywhere, including a path with spaces.
- Site checks what it built: the app distribution is present, every
installed distribution's dependencies are satisfied from inside the
bundle, every module the app provides loads, the precomp store is
not empty, and notcurses staged a non-empty library directory under
the BINARY_TAG it actually used (read off disk, never hardcoded).
- App::Ariza::Native stages SQLCipher — into rakudo/lib on macOS,
which is on the bundled interpreter's own LC_RPATH and so resolves
`is native('sqlcipher')` with no launcher involvement at all; into
native/sqlcipher elsewhere, where the launcher names it explicitly.
On macOS the staged library is made self-contained: every absolute
dependency (a Homebrew OpenSSL, say) is copied in beside it,
rewritten to @loader_path, and everything touched is re-signed,
because install_name_tool invalidates the signature and arm64 macOS
will not load a library whose signature is wrong.
- Native then audits every binary ariza put into the bundle and fails
the build if any of them would load a library from outside it:
otool -L on Mach-O, readelf -d on ELF, a presence check on PE. The
verdict functions are pure and take tool output as text, and the
tool call itself is an injectable seam, so every branch — including
the Linux and Windows verdicts — is testable from any machine.
- App::Ariza::Launcher renders bin/ from a Jinja2 template
(plus the .ps1 and .cmd twins for Windows, CRLF). It resolves its
own path through a readlink loop rather than `readlink -f`, which
is a GNU extension a bundle cannot rely on; exports RAKULIB and
NOTCURSES_NATIVE_DATA_DIR (never NOTCURSES_NATIVE_LIB_DIR, which
suppresses the terminfo setup) and unsets PERL6LIB; adds
LD_LIBRARY_PATH and DBIISH_SQLCIPHER_LIB on Linux only; prints a
one-line first-run notice, once, guarded by a sentinel outside the
bundle; warns without refusing when TERM is dumb or absent; and
quotes every expansion, so a bundle unpacked into a path with
spaces works. Golden-file tests cover all three platform slugs.
- App::Ariza::Bundle orchestrates the four, then writes VERSION (one
screen: app version and component pins), ariza-manifest.json (the
same plus every source URL, every sha256, every installed
distribution and the smoke commands) and LICENSES/ — the app's
licence, Rakudo's, and a COMPONENTS.md whose native inventory is
read off the staged libraries rather than written down, including
an explicit note that this notcurses pack's FFmpeg is a GPL build.
Building a platform the app does not declare in bundle.platforms is
an error.
- App::Ariza::Smoke unpacks a finished archive into a scratch
directory with a space in its name and runs it with a *replaced*
environment — PATH, HOME, TERM and nothing else — so a bundle
cannot pass by quietly using the developer's system Rakudo or their
DBIISH_SQLCIPHER_LIB. It re-runs the native audit over the unpacked
tree and reports pass or fail per check, keeping the tree on
failure. Smoke commands come from the bundle's own manifest, so an
archive can be checked with no access to the app's repository.
- bundle.smoke now accepts a list, and each entry may be argv rather
than a command line, so a smoke test can carry an entire program
with no shell and no quoting layer. Placeholders: {exec}, {raku},
{site}, {native}, {bundle}, {tmp}. App-Moneymoor uses this to
smoke-test the thing --version never touches: creating, writing and
reopening an encrypted database from inside the bundle.
- App::Ariza::Tools is the new shell-out layer — run, download,
digest, extract — so no other module contains a bare `run` and
every failure reads the same way. No new dependencies: ariza
fetches three artefacts from static hosts with curl, exactly as
every Build.rakumod in this tree already does.
- Fixed: current-slug() and detect-slug() died with a type-check
failure when ARIZA_PLATFORM was unset, because %*ENV returns Any
for a missing key and the parameter is typed Str. That is every
machine that has never heard of the variable.
- Initial release. Phase one of ariza: the distribution scaffold and
the shared machinery a bundler needs, with no bundling yet and no
placeholder pretending otherwise — three verbs, all of which do
their whole job.
- App::Ariza::Platform names the platform an artefact is built for.
The eight slugs (macos-arm64, macos-x86_64, linux-{x86_64,aarch64}-
{glibc,musl}, windows-{x86_64,arm64}) are Notcurses-Native's,
character for character, because a bundle carries that
distribution's prebuilt libraries and the two have to agree about
what platform they are on. Detection probes $*KERNEL plus, on
Linux, the libc: a ld-musl-*.so.1 loader first, then ldd --version
for glibc. ARIZA_PLATFORM forces a slug for cross-builds and CI
matrices, and is validated against the known set, because an
override is the one input that is definitely a human mistake if it
is wrong.
- App::Ariza::Versions parses resources/versions.toml, the single pin
file every artefact is built against. A new [rakudo] section
(version 2026.07, revision 01) names the runtime a bundle embeds,
which is deliberately not the same pin as rakudo-star: that one is
the release an installer tells a package manager to fetch onto the
user's machine.
- App::Ariza::Config parses the per-app ariza.toml manifest, which
lives in the app's own repository so ariza never grows a list of
special cases about specific apps. [app] name/exec/display are
required; [bundle] declares platforms, native libraries and a smoke
command whose {exec} expands to the launcher name.
- Unknown keys warn and never die, in both config files, so one
manifest can serve several ariza versions; wrong types die naming
the dotted path; //-prefixed keys are ignored. The one exception is
an unknown platform slug in bundle.platforms, which dies: the
supported set is closed, so a typo cannot be a future feature, and
ignoring it would ship a release quietly missing a platform.
- App::Ariza::Legacy absorbs installers/generate.raku intact — the
Jinja2 templates, the per-app configs, the sqlcipher mirror notes
and the pin file are all ariza resources now. It renders the eight
install/uninstall scripts per app, defaulting to each app's own
repository exactly as the old script did, with an --out-dir
override so a render can be inspected before it lands.
- The absorption is gated by a byte-for-byte parity test: all eight
App-Mindmoor scripts are rendered to a scratch directory and
compared against the copies committed in that repository, with a
unified diff excerpt on any mismatch. Drift in a template, a
context key or the Jinja2 dependency fails the suite instead of
reaching a user.
- App::Ariza::Resources finds ariza's own data through %?RESOURCES
when installed and the checkout tree when not, including directory
listing, which %?RESOURCES cannot do — installed runs enumerate the
distribution manifest, checkouts read the working tree.
- bin/ariza: render-legacy [--app] [--out-dir], version, help.
- App-Moneymoor gains the first ariza.toml.