starting new job...
→ running command: cd "/etc/byggsteg/job-clone//torikomi/trunk" && git reset --hard && git pull
HEAD is now at 3d0eb1b ci: Name the byggsteg step with BYGGSTEG_STEP
From https://codeberg.org/jjba23/torikomi
3d0eb1b..f9b5e0b trunk -> origin/trunk
Updating 3d0eb1b..f9b5e0b
Fast-forward
manifest.scm | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
→ running command: cd /etc/byggsteg/job-clone/torikomi/trunk && uname -a
Linux wolk-jjba 7.2.11-gnu #1 SMP PREEMPT_DYNAMIC 1 x86_64 GNU/Linux
→ running command: cd /etc/byggsteg/job-clone/torikomi/trunk && git log --name-status HEAD^..HEAD
commit f9b5e0b47da84ff0f8f6278bbaa793bf4ed731f3
Author: Josep Bigorra <jjbigorra@gmail.com>
Date: Mon Oct 26 10:59:26 2026 +0100
chore: Update guile-veritas
The pin moves to v0.9.0, the release that makes coverage work: the measured
part of a run switches to Guile's debugging VM engine, and a run writes LCOV,
JSON, Cobertura, JaCoCo and a one-page HTML report for the whole project, merged
over the run and over the workers of --jobs. Two fiber-safety fixes for the
test helpers ride along.
🤖 Generated with [ECA](https://eca.dev) (deepseek/deepseek-flash)
Co-Authored-By: eca-agent <git@eca.dev>
M manifest.scm
→ running command: cd /etc/byggsteg/job-clone/torikomi/trunk && maak test
The following derivation will be built:
/gnu/store/ba70v99hx7bxylvz6vci6k27xyfn17ph-profile.drv
building CA certificate bundle...
listing Emacs sub-directories...
building fonts directory...
building directory of Info manuals...
building profile with 19 packages...
;;; note: auto-compilation is enabled, set GUILE_AUTO_COMPILE=0
;;; or pass the --no-auto-compile argument to disable.
;;; compiling ./test/veritas/unit/sink-spec.scm
;;; compiling ./test/veritas/support/temp-dir.scm
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/torikomi/trunk/test/veritas/support/temp-dir.scm.go
;;; compiling ./test/veritas/support/http-server.scm
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/torikomi/trunk/test/veritas/support/http-server.scm.go
;;; compiling ./lib/torikomi/torikomi.scm
;;; compiling ./lib/torikomi/preferences.scm
;;; compiling ./lib/torikomi/version.scm
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/torikomi/trunk/lib/torikomi/version.scm.go
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/torikomi/trunk/lib/torikomi/preferences.scm.go
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/torikomi/trunk/lib/torikomi/torikomi.scm.go
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/torikomi/trunk/test/veritas/unit/sink-spec.scm.go
scanning directories for unit tests...
scanning present working directory for unit tests...
shuffled the files with seed 369449088 (replay with VERITAS_SEED=369449088)
found unit tests: (sink-spec.scm filename-spec.scm url-spec.scm preferences-spec.scm page-spec.scm version-spec.scm http-response-spec.scm)
running unit tests from file: sink-spec.scm, entrypoint: spec
+----------------------------------------------+
[📑 suite] sinking a file
[🧪 test] a file is written under the URL's name
✅ got: #t
✅ got: text.txt
✅ got: https://files.example/sunk/text.txt
✅ got: 14
✅ got: 200
✅ got: #vu8(104 101 108 108 111 32 116 111 114 105 107 111 109 105)
✅ got: https://files.example/sunk/text.txt
✅ got: https://files.example/sunk/text.txt
[🧪 test] the reader's name wins and is made safe
✅ got: #t
✅ got: My-Photo.png
✅ got: /
✅ got: #t
[🧪 test] the server's suggested name is used
✅ got: #t
✅ got: My-Report.pdf
✅ got: 6
[🧪 test] a response without a type still gets an extension
✅ got: #t
✅ got: none.bin
[🧪 test] a redirect is followed and the body of the last response is kept
✅ got: #t
✅ got: redirect.png
✅ got: 4
[🧪 test] the same URL twice does not overwrite the first file
✅ got: #t
✅ got: #t
✅ got: #f
✅ got: #t
✅ got: #t
[🧪 test] a 404 is a failure with the remote's status
✅ got: #f
✅ got: 502
✅ got: 404
✅ got: 404
✅ got: ()
[🧪 test] a transient failure is retried
✅ got: #t
✅ got: 7
✅ got: #vu8(102 105 110 97 108 108 121)
[🧪 test] a body larger than the limit is refused
✅ got: #f
✅ got: 413
✅ got: ()
[🧪 test] a URL that is not one is a 400
✅ got: 400
✅ got: 400
✅ got: 400
[🧪 test] fetch-file keeps the older contract
✅ got: https://files.example/sunk/text.txt
✅ got: #t
✅ got: 502
✅ got: 404
[🧪 test] the sink directory is created on the first fetch
✅ got: #t
✅ got: #t
✅ got: #t
ℹ️ info - ran 1 tasks in: 1 seconds, -914316 microseconds
running unit tests from file: filename-spec.scm, entrypoint: spec
+----------------------------------------------+
[📑 suite] deciding a name
[🧪 test] a name cannot escape the sink directory
✅ got: passwd
✅ got: passwd
✅ got: b.png
✅ got: passwd
[🧪 test] a name cannot be hidden or empty
✅ got: #f
✅ got: #f
✅ got: #f
✅ got: #f
✅ got: #f
✅ got: name
[🧪 test] unsafe characters become one dash
✅ got: a-b.png
✅ got: a-b.png
✅ got: h-llo-w-rld.png
✅ got: photo.JPG
✅ got: a_b-c.txt
[🧪 test] a very long name is shortened but keeps its extension
✅ got: #t
✅ got: #t
[🧪 test] an extension is only letters and digits
✅ got: png
✅ got: jpg
✅ got: #f
✅ got: #f
✅ got: #f
✅ got: #f
✅ got: #f
[🧪 test] a known extension is replaced rather than appended
✅ got: photo.png
✅ got: archive.tar.gz
✅ got: v1.2.txt
✅ got: noext.png
✅ got: photo.bin
✅ got: photo.bin
[🧪 test] the name a Content-Disposition asks for is understood
✅ got: My File.pdf
✅ got: café.png
✅ got: a.png
✅ got: #f
[🧪 test] a malformed escape is left as it is
✅ got: bad%zz.png
[🧪 test] the URL's last segment is a name
✅ got: pic.png
✅ got: a-b.png
✅ got: #f
✅ got: #f
[🧪 test] the URL's extension is read but not trusted
✅ got: gz
✅ got: png
✅ got: #f
✅ got: #f
[🧪 test] the media type decides the extension before the name does
✅ got: png
[🧪 test] a server that only says octet-stream leaves the name to say it
✅ got: pdf
✅ got: gif
[🧪 test] the base name comes from the reader, the server or the URL
✅ got: mine
✅ got: a.png
✅ got: suggested.bin
✅ got: torikomi
[🧪 test] a name already taken is not replaced
✅ got: a.txt
✅ got: #t
✅ got: #f
✅ got: #t
✅ got: #vu8(102 105 114 115 116)
✅ got: #vu8(115 101 99 111 110 100)
[🧪 test] the sink directory is created and left without temporary files
✅ got: #t
✅ got: #vu8(104 101 108 108 111)
✅ got: (. .. note.txt)
[🧪 test] another name can be asked for without writing
✅ got: a-b.png
✅ got: #f
✅ got: #t
ℹ️ info - ran 1 tasks in: 0 seconds, 25043 microseconds
running unit tests from file: url-spec.scm, entrypoint: spec
+----------------------------------------------+
[📑 suite] what a URL may be
[🧪 test] http and https are the protocols torikomi fetches
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
[🧪 test] every other protocol is refused
✅ got: #f
✅ got: #f
✅ got: #f
✅ got: #f
✅ got: #f
[🧪 test] a URL without a host is not a URL to fetch
✅ got: #f
✅ got: #f
✅ got: #f
✅ got: #f
✅ got: #f
✅ got: #f
✅ got: #f
[🧪 test] a valid URL is answered as a URI
✅ got: https
✅ got: example.org
✅ got: 8443
✅ got: /a/b.png
[🧪 test] a missing URL is a 400 that says so
✅ got: #t
✅ got: 400
✅ got: missing-url
✅ got: #t
✅ got: missing-url
[🧪 test] a refused protocol is a 400 that names the alternatives
✅ got: #t
✅ got: 400
✅ got: unsupported-scheme
✅ got: http
[🧪 test] a malformed URL is a 400 and not a crash
✅ got: #t
✅ got: 400
✅ got: invalid-url
ℹ️ info - ran 1 tasks in: 0 seconds, 20915 microseconds
running unit tests from file: preferences-spec.scm, entrypoint: spec
+----------------------------------------------+
[📑 suite] the settings a deployment may give
[🧪 test] the environment is the deployment's word
✅ got: from-the-environment
✅ got: padded
✅ got: default
✅ got: default
[🧪 test] a number is a whole number in range, or the default
✅ got: 42
✅ got: 7
✅ got: 7
✅ got: 7
✅ got: 3
✅ got: 7
[🧪 test] a flag is true unless it is one of the ways of writing no
✅ got: #f
✅ got: #f
✅ got: #f
✅ got: #f
✅ got: #f
✅ got: #f
✅ got: #f
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
[🧪 test] a size may be written with a suffix
✅ got: 1024
✅ got: 1024
✅ got: 2097152
✅ got: 3221225472
✅ got: 1099511627776
✅ got: 10485760
✅ got: #f
✅ got: #f
✅ got: #f
✅ got: #f
✅ got: 9
[🧪 test] a public URL joins the base and the name without doubling slashes
✅ got: https://files.example/base/a.png
✅ got: https://files.example/a.png
[🧪 test] the project's settings are the shapes the code expects
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
ℹ️ info - ran 1 tasks in: 0 seconds, 15434 microseconds
running unit tests from file: page-spec.scm, entrypoint: spec
+----------------------------------------------+
[📑 suite] the page a reader meets
[🧪 test] the page is a document of this program
✅ got: <!DOCTYPE html>
✅ got: lang="en"
✅ got: torikomi
✅ got: 0.1.1
✅ got: torikomi.olive.min.css
✅ got: favicon.ico
[🧪 test] the page is built out of frosted glass
✅ got: hg-frost
✅ got: hg-frost-pill
✅ got: hg-blob
✅ got: hg-glass-layer
✅ got: fixed inset-0
✅ got: bg-jeans-blue-500/25
✅ got: bg-tawny-500/25
✅ got: bg-china-violet-500/25
✅ got: aria-hidden="true"
[🧪 test] the theme is the reader's, and is applied before the first paint
✅ got: data-theme
✅ got: prefers-color-scheme: dark
✅ got: hygguileToggleDarkTheme()
✅ got: switch between the light and dark theme
✅ got: bg-stone-100
✅ got: dark:bg-onyx-900
[🧪 test] a success says where the file is
✅ got: public url
✅ got: https://files.example/pic.png
✅ got: role="status"
✅ got: role="alert"
[🧪 test] a failure is announced, not silent
✅ got: role="alert"
✅ got: the remote answered with HTTP 404.
✅ got: could not sink the file
[🧪 test] what the reader typed comes back
✅ got: https://example.org/missing.png
✅ got: value="holiday"
✅ got: given file-name:
✅ got: given file-url:
[🧪 test] a failure message is escaped
✅ got: <script>alert(1)</script>
✅ got: <script>
[🧪 test] a URL handed to a script cannot end the string early
✅ got: a\'b
✅ got: a\\b
✅ got: a\'b.png
[🧪 test] the form posts where the controller listens
✅ got: action="/sink-file"
✅ got: name="file-url"2026-10-26T11:03:46+0100 torikomi info: fetching http://127.0.0.1:33541/text
2026-10-26T11:03:46+0100 torikomi info: sunk http://127.0.0.1:33541/text (14 bytes) as text.txt
2026-10-26T11:03:46+0100 torikomi info: fetching http://127.0.0.1:33541/png
2026-10-26T11:03:46+0100 torikomi info: sunk http://127.0.0.1:33541/png (4 bytes) as My-Photo.png
2026-10-26T11:03:46+0100 torikomi info: fetching http://127.0.0.1:33541/disposition
2026-10-26T11:03:46+0100 torikomi info: sunk http://127.0.0.1:33541/disposition (6 bytes) as My-Report.pdf
2026-10-26T11:03:46+0100 torikomi info: fetching http://127.0.0.1:33541/none
2026-10-26T11:03:46+0100 torikomi info: sunk http://127.0.0.1:33541/none (14 bytes) as none.bin
2026-10-26T11:03:46+0100 torikomi info: fetching http://127.0.0.1:33541/redirect
2026-10-26T11:03:46+0100 torikomi info: sunk http://127.0.0.1:33541/redirect (4 bytes) as redirect.png
2026-10-26T11:03:46+0100 torikomi info: fetching http://127.0.0.1:33541/text
2026-10-26T11:03:47+0100 torikomi info: sunk http://127.0.0.1:33541/text (14 bytes) as text.txt
2026-10-26T11:03:47+0100 torikomi info: fetching http://127.0.0.1:33541/text
2026-10-26T11:03:47+0100 torikomi info: sunk http://127.0.0.1:33541/text (14 bytes) as text-cbb4b06e-c37d-4bab-9f7e-e2717fc436dc.txt
2026-10-26T11:03:47+0100 torikomi info: fetching http://127.0.0.1:33541/missing
2026-10-26T11:03:47+0100 torikomi warning: http://127.0.0.1:33541/missing answered with HTTP 404.
2026-10-26T11:03:47+0100 torikomi info: fetching http://127.0.0.1:33541/flaky
2026-10-26T11:03:47+0100 torikomi warning: http://127.0.0.1:33541/flaky answered with HTTP 503.; retrying in 0 second(s) (1 of 2)
2026-10-26T11:03:47+0100 torikomi warning: http://127.0.0.1:33541/flaky answered with HTTP 503.; retrying in 0 second(s) (2 of 2)
2026-10-26T11:03:47+0100 torikomi info: sunk http://127.0.0.1:33541/flaky (7 bytes) as flaky.txt
2026-10-26T11:03:47+0100 torikomi info: fetching http://127.0.0.1:33541/big
2026-10-26T11:03:47+0100 torikomi warning: Could not fetch http://127.0.0.1:33541/big: Maximum file size exceeded
2026-10-26T11:03:47+0100 torikomi warning: A file URL is required.
2026-10-26T11:03:47+0100 torikomi warning: Only http and https URLs can be sunk; "file:///etc/passwd" is not one.
2026-10-26T11:03:47+0100 torikomi warning: "not a url" is not a URL.
2026-10-26T11:03:47+0100 torikomi info: fetching http://127.0.0.1:33541/text
2026-10-26T11:03:47+0100 torikomi info: sunk http://127.0.0.1:33541/text (14 bytes) as text.txt
2026-10-26T11:03:47+0100 torikomi info: fetching http://127.0.0.1:33541/missing
2026-10-26T11:03:47+0100 torikomi warning: http://127.0.0.1:33541/missing answered with HTTP 404.
2026-10-26T11:03:47+0100 torikomi info: fetching http://127.0.0.1:33541/text
2026-10-26T11:03:47+0100 torikomi info: sunk http://127.0.0.1:33541/text (14 bytes) as text.txt
;;; compiling ./test/veritas/unit/filename-spec.scm
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/torikomi/trunk/test/veritas/unit/filename-spec.scm.go
;;; compiling ./test/veritas/unit/url-spec.scm
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/torikomi/trunk/test/veritas/unit/url-spec.scm.go
;;; compiling ./test/veritas/unit/preferences-spec.scm
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/torikomi/trunk/test/veritas/unit/preferences-spec.scm.go
;;; compiling ./test/veritas/unit/page-spec.scm
;;; compiling ./app/views/index.scm
WARNING: (hygguile hygguile): imported module (hygguile components select) overrides core binding `select'
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/torikomi/trunk/app/views/index.scm.go
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/torikomi/trunk/test/veritas/unit/page-spec.scm.go
;;; compiling ./test/veritas/unit/version-spec.scm
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/torikomi/trunk/test/veritas/unit/version-spec.scm.go
;;; compiling ./test/veritas/unit/http-response-spec.scm
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/torikomi/trunk/test/veritas/unit/http-response-spec.scm.go
✅ got: name="file-name"
[🧪 test] a first visit is offered the API instead of an old result
✅ got: or from a script
✅ got: /api/v1/sink-file
✅ got: role="alert"
[🧪 test] a result replaces the API card
✅ got: or from a script
✅ got: or from a script
ℹ️ info - ran 1 tasks in: 0 seconds, 96265 microseconds
running unit tests from file: version-spec.scm, entrypoint: spec
+----------------------------------------------+
[📑 suite] the version this program is
[🧪 test] the version is a release number
✅ got: 0.1.1
[🧪 test] the version banner names the program
✅ got: torikomi 0.1.1
✅ got: 0.1.1
[🧪 test] printing the version also names the licence and the homepage
✅ got: 0.1.1
✅ got: AGPL-3.0-or-later
✅ got: https://codeberg.org/jjba23/torikomi
[🧪 test] the homepage is an address
✅ got: https://codeberg.org/jjba23/torikomi
[🧪 test] the manual carries the same version
✅ got: #t
✅ got: torikomi version 0.1.1
ℹ️ info - ran 1 tasks in: 0 seconds, 4949 microseconds
running unit tests from file: http-response-spec.scm, entrypoint: spec
+----------------------------------------------+
[📑 suite] reading a response
[🧪 test] the status line is the last one libcurl returned
✅ got: HTTP/2 200
✅ got: HTTP/1.1 200 OK
✅ got: #f
[🧪 test] the status code is the number in the status line
✅ got: 200
✅ got: 404
✅ got: 503
✅ got: #f
✅ got: #f
[🧪 test] only 2xx is a success
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #f
✅ got: #f
✅ got: #f
✅ got: #f
[🧪 test] the headers read are the ones of the final response
✅ got: image/png
✅ got: #f
✅ got: /pic.png
✅ got: attachment; filename="a.png"
✅ got: #f
[🧪 test] a Content-Type is split into its media type and subtype
✅ got: jpeg
✅ got: plain
✅ got: plain
✅ got: #f
✅ got: #f
[🧪 test] a media type names the extension of the file
✅ got: jpg
✅ got: png
✅ got: svg
✅ got: txt
✅ got: bin
✅ got: weba
✅ got: #f
✅ got: #f
[🧪 test] an unknown subtype still names an extension
✅ got: foo
✅ got: widget
✅ got: bin
✅ got: png
[🧪 test] the retry policy only retries what may be transient
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #f
✅ got: #f
✅ got: #f
[🧪 test] a transport failure and a bad gateway are retryable, a large body is not
✅ got: #t
✅ got: #t
✅ got: #f
✅ got: #f
✅ got: #f
[🧪 test] the wait between attempts doubles and is capped
✅ got: 1
✅ got: 2
✅ got: 4
✅ got: 8
✅ got: 60
✅ got: 0
ℹ️ info - ran 1 tasks in: 0 seconds, 24611 microseconds
finished running tests
--------------------------------------------------------------
Maak 0.14.0: the infinitely extensible command runner
Loading tasks from Maak file: /etc/byggsteg/job-clone/torikomi/trunk/maak.scm
Executing Maak tasks: test
--------------------------------------------------------------
=> Running Maak task: test
Run the project's test suite.
The specs live under test/veritas/unit. That is where the runner looks for
suites -- given the test root ./test, the module prefix `veritas' and the suite
name `unit' -- and it is also what their own `(veritas unit ...)' module names
resolve to on the load path below.
The run names the locale it reads in rather than inheriting one: Guile reads a
page by the locale in force, and this suite reads pages that hold characters
outside ASCII. `C.UTF-8' is that name for a machine that has never generated a
locale -- a service, a container, and this project's own continuous
integration. Naming it here is part of what the pinned manifest is for: a run
of the suite reads the same pages wherever the pipeline runs.
The specs are compiled rather than read from a cache. A compiled spec
outlives the library it was compiled against -- Guile decides a compiled file
is still good by the spec's own text, and not by the macros and procedures that
text was compiled with -- so a spec compiled before `(veritas veritas)' changed
shape is loaded afterwards as if it still had the old one. Compiling from
source every run costs a few seconds and makes the reading depend on the specs
alone, which is what a suite that is run to judge a change has to do.
Executing: guix time-machine --channels=channels.scm -- shell -m manifest.scm -- guile --fresh-auto-compile -L ./lib -L . -L ./test -c '(begin (setlocale LC_ALL "C.UTF-8") (use-modules (veritas runner)) (run-tests))'
;;; note: source file /etc/byggsteg/job-clone/torikomi/trunk/maak.scm
;;; newer than compiled /root/.cache/guile/ccache/3.0-LE-8-4.6/etc/byggsteg/job-clone/torikomi/trunk/maak.scm.go
;;; note: auto-compilation is enabled, set GUILE_AUTO_COMPILE=0
;;; or pass the --no-auto-compile argument to disable.
;;; compiling /etc/byggsteg/job-clone/torikomi/trunk/maak.scm
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.6/etc/byggsteg/job-clone/torikomi/trunk/maak.scm.go
→ running command: cd /etc/byggsteg/job-clone/torikomi/trunk && sudo herd enable torikomi-daemon || true && sudo herd restart torikomi-daemon || true
Enabled service torikomi-daemon.
Service torikomi-daemon has been started.