mutastructura-6977daa0-a0d9

job succeeded

starting new job...
rundir='/etc/byggsteg/job-clone//mutastructura/trunk succeeded43 lines · 0.7s
branch='trunk'
if ! command -v git >/dev/null 2>&1; then
  echo 'byggsteg: git is not on this deployment PATH, so the clone cannot be updated' >&2
  exit 1
fi
if ! cd "$dir"; then
  echo "byggsteg: cannot enter the clone $dir" >&2
  exit 1
fi
root=$(git rev-parse --show-toplevel 2>/dev/null)
if [ "$root" != "$(pwd -P)" ]; then
  echo "byggsteg: $dir is not a git checkout; remove it and the next run clones it again" >&2
  exit 1
fi
if ! origin=$(git remote get-url origin 2>/dev/null); then
  echo "byggsteg: $dir has no origin remote to fetch $branch from" >&2
  exit 1
fi
before=$(git rev-parse --short HEAD 2>/dev/null) || before=none
attempt=1
while :; do
  if GIT_TERMINAL_PROMPT=0 git fetch --quiet origin "+refs/heads/$branch:refs/remotes/origin/$branch"; then
    break
  fi
  if [ "$attempt" -ge 3 ]; then
    echo "byggsteg: could not fetch $branch from $origin after 3 attempts; is the branch still there, and is the remote reachable?" >&2
    exit 1
  fi
  echo "byggsteg: fetching $branch from $origin failed; retrying in $((attempt * 2))s" >&2
  sleep $((attempt * 2))
  attempt=$((attempt + 1))
done
if ! git checkout --quiet --force -B "$branch" FETCH_HEAD; then
  echo "byggsteg: fetched $branch from $origin but could not check it out" >&2
  exit 1
fi
after=$(git rev-parse --short HEAD)
if [ "$before" = "$after" ]; then
  echo "byggsteg: $branch was already at $after"
else
  echo "byggsteg: $branch moved from $before to $after"
fi


byggsteg: trunk moved from 0a90c7a to 728ede7
setupuname -a succeeded1 lines · 0.0s
Linux wolk-jjba 7.2.11-gnu #1 SMP PREEMPT_DYNAMIC 1 x86_64 GNU/Linux
setupgit log --name-status HEAD^..HEAD succeeded5 lines · 0.0s
commit 728ede7ec614437851902e30f629c2f33774b0e4
Author: Josep Bigorra (Joe) <jjbigorra@gmail.com>
Date:   Tue Nov 10 20:47:26 2026 +0100

    chore: show the sponsor button

A       .github/FUNDING.yml
testmaak test succeeded841 lines · 2.0s
scanning directories for unit tests...
scanning present working directory for unit tests...

shuffled the files with seed 283193022 (replay with VERITAS_SEED=283193022)

found unit tests: (core.scm dialect.scm sql-runner.scm sql-parser.scm)


running unit tests from file: core.scm, entrypoint: spec


+----------------------------------------------+
[📑 suite] the engines and the names people call them
  [🧪 test] the engines are listed once each
    [⚙️ assert] in the order help lists them
    ✅ got: (sqlite3 postgresql cockroachdb mysql)
  [🧪 test] every engine takes SQL migration files
    [⚙️ assert] SQL for PostgreSQL
    ✅ got: (.sql)
    [⚙️ assert] and for SQLite, which also knows its own suffix
    ✅ got: (.sql .sqlite.sql)
  [🧪 test] a database reached through another driver keeps its own identity
    [⚙️ assert] CockroachDB is opened by the PostgreSQL driver
    ✅ got: postgresql
    [⚙️ assert] which is what guile-dbi is told
    ✅ got: postgresql
    [⚙️ assert] and MariaDB by the MySQL one
    ✅ got: mysql
  [🧪 test] an unknown name is refused, naming the ones that exist
    [⚙️ assert] there is no such engine
    ✅ got: #f
    [⚙️ assert] and the error says what there is instead
    ✅ got: #t
  [🧪 test] an engine carries the port and the ledger it defaults to
    [⚙️ assert] PostgreSQL's port
    ✅ got: 5432
    [⚙️ assert] MySQL's port
    ✅ got: 3306
    [⚙️ assert] CockroachDB's port
    ✅ got: 26257
    [⚙️ assert] and SQLite has none
    ✅ got: 0
    [⚙️ assert] the SQL ledger's default name
    ✅ got: migrations_history
  [🧪 test] every alias resolves to the engine it names
    [⚙️ assert] pg is PostgreSQL
    ✅ got: postgresql
    [⚙️ assert] and so is psql
    ✅ got: postgresql
    [⚙️ assert] a PostgreSQL-compatible database is reached as one
    ✅ got: postgresql
    [⚙️ assert] mariadb is MySQL's engine
    ✅ got: mysql
    [⚙️ assert] and so is percona
    ✅ got: mysql
    [⚙️ assert] crdb is CockroachDB
    ✅ got: cockroachdb
  [🧪 test] a name is matched without case
    [⚙️ assert] MySQL and mysql are the same request
    ✅ got: mysql
    [⚙️ assert] and so are POSTGRES and postgres
    ✅ got: postgresql
  [🧪 test] every engine is a relational one
    [⚙️ assert] SQLite is relational
    ✅ got: relational
    [⚙️ assert] and so is PostgreSQL
    ✅ got: relational
    [⚙️ assert] which is what the core asks
    ✅ got: #t
+----------------------------------------------+
[📑 suite] connection URLs
  [🧪 test] something that is not a URL is a connection string
    [⚙️ assert] the string is passed through
    ✅ got: alice:secret:appdb:tcp:host:5432
    [⚙️ assert] with the engine it was told
    ✅ got: postgresql
  [🧪 test] a PostgreSQL URL is split into its parts
    [⚙️ assert] the engine
    ✅ got: postgresql
    [⚙️ assert] the host
    ✅ got: db.example.net
    [⚙️ assert] the port
    ✅ got: 5432
    [⚙️ assert] the user
    ✅ got: alice
    [⚙️ assert] and the database
    ✅ got: appdb
  [🧪 test] a SQLite URL is a path, taken verbatim
    [⚙️ assert] the engine
    ✅ got: sqlite3
    [⚙️ assert] the absolute path is kept whole
    ✅ got: /var/lib/app.db
    [⚙️ assert] and is the database
    ✅ got: /var/lib/app.db
    [⚙️ assert] a relative path stays relative
    ✅ got: ./local.db
  [🧪 test] a URL becomes the driver's own connection string
    [⚙️ assert] PostgreSQL's colon-delimited form
    ✅ got: alice:secret:appdb:tcp:db.example.net:5432
    [⚙️ assert] and MySQL's
    ✅ got: alice:secret:appdb:tcp:127.0.0.1:3306
  [🧪 test] a port left out is the engine's own
    [⚙️ assert] PostgreSQL defaults to 5432
    ✅ got: alice:secret:appdb:tcp:db.example.net:5432
    [⚙️ assert] and MySQL to its own port on localhost
    ✅ got: alice:secret:appdb:tcp:localhost:3306
  [🧪 test] a parameter is percent-decoded
    [⚙️ assert] a space written as %20
    ✅ got: a b
  [🧪 test] a password with a colon is refused rather than truncated
    [⚙️ assert] the reason and the way out are both named
    ✅ got: #t
  [🧪 test] the description of a connection never holds the password
    [⚙️ assert] it says where it is going and as whom
    ✅ got: #t
    [⚙️ assert] and not the password
    ✅ got: #f
    [⚙️ assert] and it names the driver when that is the interesting part
    ✅ got: #t
+----------------------------------------------+
[📑 suite] a migration file and its order
  [🧪 test] a name is read into a version and a description
    [⚙️ assert] the file it is
    ✅ got: 2026-03-31_20:24:00_init.sql
    [⚙️ assert] the version --target compares against
    ✅ got: 2026-03-31_20:24:00_init
    [⚙️ assert] and that it is versioned
    ✅ got: versioned
  [🧪 test] a dotted version sorts by number, not by text
    [⚙️ assert] 1.2.10 comes after 1.2.9
    ✅ got: #t
    [⚙️ assert] and the order key of the files agrees
    ✅ got: #t
    [⚙️ assert] a version that is not numeric has no such key
    ✅ got: #f
  [🧪 test] Flyway's names are read the way Flyway reads them
    [⚙️ assert] the version is the number
    ✅ got: 1.2
    [⚙️ assert] and the description what follows the underscores
    ✅ got: add-user
    [⚙️ assert] a V file is versioned
    ✅ got: versioned
    [⚙️ assert] an R file is repeatable
    ✅ got: repeatable
    [⚙️ assert] and a U file is a rollback
    ✅ got: undo
  [🧪 test] a `.repeatable.sql' name is a repeatable migration
    [⚙️ assert] even without Flyway's `R__' prefix
    ✅ got: repeatable
    [⚙️ assert] with the marker stripped from the version
    ✅ got: 2026-04-01_21:54:00_test
  [🧪 test] the discovery of a directory is by extension, and sorted
    [⚙️ assert] the SQL directory, in order
    ✅ got: (2026-03-31_20:24:00_init.sql 2026-04-01_21:54:00_test.repeatable.sql)
  [🧪 test] a file is read with its byte order mark removed
    [⚙️ assert] the BOM an editor wrote
    ✅ got: SELECT 1
    [⚙️ assert] and a file that never had one
    ✅ got: SELECT 1
  [🧪 test] a file's checksum is its content's
    [⚙️ assert] a SHA-256 is 64 hexadecimal characters
    ✅ got: 64
    [⚙️ assert] and it is the same on the next read
    ✅ got: 0de08501706302a7910d747dd602b9a974ce4b2fe2580170a70917231a5064e6
    [⚙️ assert] while another file has another
    ✅ got: #f
  [🧪 test] a directory that is not there is refused
    [⚙️ assert] the directory is refused
    ✅ got: #t
  [🧪 test] which files are migrations is the engine's answer
    [⚙️ assert] SQL for PostgreSQL
    ✅ got: #t
    [⚙️ assert] and not JSON
    ✅ got: #f
    [⚙️ assert] nor does an engine take a stray note
    ✅ got: #f
+----------------------------------------------+
[📑 suite] placeholders
  [🧪 test] a `key=value' list becomes an alist
    [⚙️ assert] spaces around the parts are not part of them
    ✅ got: ((user . ada) (schema . app))
    [⚙️ assert] a comma-separated string is the same thing
    ✅ got: ((user . ada))
    [⚙️ assert] an empty one defines nothing
    ✅ got: ()
  [🧪 test] a migration's `${name}' becomes what it was given
    [⚙️ assert] both placeholders substituted
    ✅ got: SELECT * FROM app.users WHERE id = 7
  [🧪 test] a placeholder nobody defined is left exactly as it was
    [⚙️ assert] an empty set of placeholders changes nothing
    ✅ got: SELECT '${not-ours}'
    [⚙️ assert] and neither does an incomplete one
    ✅ got: SELECT ${other}
  [🧪 test] the question of whether there is a placeholder at all
    [⚙️ assert] a `${' and a `}' together are one
    ✅ got: #t
    [⚙️ assert] a positional parameter is not
    ✅ got: #f
  [🧪 test] a spec with no `=' is refused
    [⚙️ assert] and the form it wants is named
    ✅ got: #t
+----------------------------------------------+
[📑 suite] the ledger entry the core reads
  [🧪 test] a time is read from either column name
    [⚙️ assert] the server's own stamp
    ✅ got: 1791981743651
    [⚙️ assert] and the older spelling
    ✅ got: 5
  [🧪 test] a success is read whether it is a boolean or 1 and 0
    [⚙️ assert] 1 is a success
    ✅ got: #t
    [⚙️ assert] 0 is a failure
    ✅ got: #f
    [⚙️ assert] and an entry that does not mention it succeeded
    ✅ got: #t
    [⚙️ assert] an explicit #f is a failure
    ✅ got: #f
  [🧪 test] a row is recognised as an entry
    [⚙️ assert] an alist with a name is one
    ✅ got: #t
    [⚙️ assert] a string is not
    ✅ got: #f
    [⚙️ assert] and neither is an alist without a name
    ✅ got: #f
  [🧪 test] a kind is read whether it is a symbol, a string or missing
    [⚙️ assert] the symbol the runner writes
    ✅ got: versioned
    [⚙️ assert] the string a driver answers with
    ✅ got: repeatable
    [⚙️ assert] and a name that says so when the field is gone
    ✅ got: repeatable
  [🧪 test] an entry becomes a migration file for reporting
    [⚙️ assert] with its version
    ✅ got: 2026-04-01_21:54:00_test
    [⚙️ assert] and its kind
    ✅ got: versioned
    [⚙️ assert] and known to be applied
    ✅ got: #t
    [⚙️ assert] an entry with no time took none
    ✅ got: 0
  [🧪 test] the newest applied version is the newest versioned one
    [⚙️ assert] the newest versioned migration
    ✅ got: 2026-03-31_20:24:00_init
    [⚙️ assert] and an empty ledger has none
    ✅ got: #f
+----------------------------------------------+
[📑 suite] comparing a directory with a ledger
  [🧪 test] a file edited after it was applied is a change
    [⚙️ assert] the state is changed
    ✅ got: changed
    [⚙️ assert] which is a warning by default
    ✅ got: warn
    [⚙️ assert] and a problem
    ✅ got: (((name . 2026-03-31_20:24:00_init.sql) (path . migrations/lucidplan/2026-03-31_20:24:00_init.sql) (version . 2026-03-31_20:24:00_init) (description . 2026-03-31_20:24:00_init) (kind . versioned) (order-key . 2026-03-31_20:24:00_init) (checksum . 0de08501706302a7910d747dd602b9a974ce4b2fe2580170a70917231a5064e6) (state . changed) (action . warn) (applied-on . 1791981743651) (execution-time . 12) (success . #t)))
    [⚙️ assert] which validate turns into a refusal
    ✅ got: fail
    [⚙️ assert] the same question asked on its own
    ✅ got: (((name . 2026-03-31_20:24:00_init.sql) (path . migrations/lucidplan/2026-03-31_20:24:00_init.sql) (version . 2026-03-31_20:24:00_init) (description . 2026-03-31_20:24:00_init) (kind . versioned) (order-key . 2026-03-31_20:24:00_init)))
  [🧪 test] an empty ledger makes every file pending
    [⚙️ assert] both files are in the plan
    ✅ got: (((name . 2026-03-31_20:24:00_init.sql) (path . migrations/lucidplan/2026-03-31_20:24:00_init.sql) (version . 2026-03-31_20:24:00_init) (description . 2026-03-31_20:24:00_init) (kind . versioned) (order-key . 2026-03-31_20:24:00_init) (checksum . 0de08501706302a7910d747dd602b9a974ce4b2fe2580170a70917231a5064e6) (state . pending) (action . apply) (applied-on . #f) (execution-time . #f) (success . #t)) ((name . 2026-04-01_21:54:00_test.repeatable.sql) (path . migrations/lucidplan/2026-04-01_21:54:00_test.repeatable.sql) (version . 2026-04-01_21:54:00_test) (description . 2026-04-01_21:54:00_test) (kind . repeatable) (order-key . 2026-04-01_21:54:00_test) (checksum . b937e3b288e0595080667978a5f5e0bf09943508f25a5e90af7483e5a5e4a6b3) (state . repeatable-pending) (action . apply) (applied-on . #f) (execution-time . #f) (success . #t)))
    [⚙️ assert] and both are pending
    ✅ got: (pending repeatable-pending)
    [⚙️ assert] so both would run
    ✅ got: (((name . 2026-03-31_20:24:00_init.sql) (path . migrations/lucidplan/2026-03-31_20:24:00_init.sql) (version . 2026-03-31_20:24:00_init) (description . 2026-03-31_20:24:00_init) (kind . versioned) (order-key . 2026-03-31_20:24:00_init) (checksum . 0de08501706302a7910d747dd602b9a974ce4b2fe2580170a70917231a5064e6) (state . pending) (action . apply) (applied-on . #f) (execution-time . #f) (success . #t)) ((name . 2026-04-01_21:54:00_test.repeatable.sql) (path . migrations/lucidplan/2026-04-01_21:54:00_test.repeatable.sql) (version . 2026-04-01_21:54:00_test) (description . 2026-04-01_21:54:00_test) (kind . repeatable) (order-key . 2026-04-01_21:54:00_test) (checksum . b937e3b288e0595080667978a5f5e0bf09943508f25a5e90af7483e5a5e4a6b3) (state . repeatable-pending) (action . apply) (applied-on . #f) (execution-time . #f) (success . #t)))
    [⚙️ assert] and the summary says so
    ✅ got: ((pending . 1) (repeatable-pending . 1))
  [🧪 test] a ledger entry whose file has gone is missing
    [⚙️ assert] the file and the entry that has none
    ✅ got: (((name . 2026-03-31_20:24:00_init.sql) (path . migrations/lucidplan/2026-03-31_20:24:00_init.sql) (version . 2026-03-31_20:24:00_init) (description . 2026-03-31_20:24:00_init) (kind . versioned) (order-key . 2026-03-31_20:24:00_init) (checksum . 0de08501706302a7910d747dd602b9a974ce4b2fe2580170a70917231a5064e6) (state . applied) (action . skip) (applied-on . 1791981743651) (execution-time . 12) (success . #t)) ((name . 2026-01-01_00:00:00_gone.sql) (path . #f) (version . 2026-01-01_00:00:00_gone) (description . 2026-01-01_00:00:00_gone.sql) (kind . versioned) (order-key . 2026-01-01_00:00:00_gone) (checksum . dead) (applied? . #t) (state . missing) (action . warn) (applied-on . 1791981743651) (execution-time . 12) (success . #t)))
    [⚙️ assert] the entry is missing its file
    ✅ got: missing
    [⚙️ assert] and is reported rather than run
    ✅ got: warn
    [⚙️ assert] the orphans are found on their own too
    ✅ got: (((name . 2026-01-01_00:00:00_gone.sql) (checksum . dead) (description . 2026-01-01_00:00:00_gone.sql) (kind . versioned) (installed-on . 1791981743651) (installed-by . mutastructura) (execution-time . 12) (success . 1)))
  [🧪 test] a file the ledger knows about is skipped
    [⚙️ assert] it is applied
    ✅ got: applied
    [⚙️ assert] and nothing is done
    ✅ got: skip
    [⚙️ assert] so there is nothing to run
    ✅ got: ()
    [⚙️ assert] and nothing to complain about
    ✅ got: ()
  [🧪 test] a target holds back what is above it
    [⚙️ assert] the migration at the target runs
    ✅ got: pending
    [⚙️ assert] and the one after it does not
    ✅ got: beyond-target
    [⚙️ assert] so one of the two would run
    ✅ got: (((name . 2026-03-31_20:24:00_init.sql) (path . migrations/lucidplan/2026-03-31_20:24:00_init.sql) (version . 2026-03-31_20:24:00_init) (description . 2026-03-31_20:24:00_init) (kind . versioned) (order-key . 2026-03-31_20:24:00_init) (checksum . 0de08501706302a7910d747dd602b9a974ce4b2fe2580170a70917231a5064e6) (state . pending) (action . apply) (applied-on . #f) (execution-time . #f) (success . #t)))
    [⚙️ assert] and holding one back is not a problem
    ✅ got: ()
  [🧪 test] repair names the recorded failures and the changed files
    [⚙️ assert] nothing to repair when the ledger agrees
    ✅ got: ()
    [⚙️ assert] a changed file is a candidate
    ✅ got: (((name . 2026-03-31_20:24:00_init.sql) (path . migrations/lucidplan/2026-03-31_20:24:00_init.sql) (version . 2026-03-31_20:24:00_init) (description . 2026-03-31_20:24:00_init) (kind . versioned) (order-key . 2026-03-31_20:24:00_init) (checksum . 0de08501706302a7910d747dd602b9a974ce4b2fe2580170a70917231a5064e6) (state . changed) (action . warn) (applied-on . 1791981743651) (execution-time . 12) (success . #t)))
    [⚙️ assert] which repair realigns
    ✅ got: changed
    [⚙️ assert] and so is a recorded failure
    ✅ got: (((name . 2026-03-31_20:24:00_init.sql) (path . migrations/lucidplan/2026-03-31_20:24:00_init.sql) (version . 2026-03-31_20:24:00_init) (description . 2026-03-31_20:24:00_init) (kind . versioned) (order-key . 2026-03-31_20:24:00_init) (checksum . 0de08501706302a7910d747dd602b9a974ce4b2fe2580170a70917231a5064e6) (state . pending) (action . apply) (applied-on . #f) (execution-time . 0) (success . #f)))
    [⚙️ assert] which repair forgets
    ✅ got: #f
  [🧪 test] a pending migration older than the newest one is held back
    [⚙️ assert] it is superseded by what is applied
    ✅ got: superseded
    [⚙️ assert] unless out-of-order runs are allowed
    ✅ got: apply
+----------------------------------------------+
[📑 suite] rolling back what can be rolled back
  [🧪 test] repeatable migrations are not rollback candidates
    [⚙️ assert] a repeatable is left for migrate to re-run
    ✅ got: ()
    [⚙️ assert] and an empty database has nothing to undo
    ✅ got: ()
  [🧪 test] --to leaves the target applied and undoes what is above it
    [⚙️ assert] newest first, target excluded
    ✅ got: (2026-01-03_c.sql 2026-01-02_b.sql)
    [⚙️ assert] a target that is already the newest undoes nothing
    ✅ got: ()
    [⚙️ assert] and a target that was never applied is refused
    ✅ got: #t
  [🧪 test] --all with nothing reversible is a boundary alone
    ✅ got: (((action . boundary) (name . 2026-01-01_a.sql) (version . 2026-01-01_a) (file (name . 2026-01-01_a.sql) (path . /m/2026-01-01_a.sql) (version . 2026-01-01_a) (description . 2026-01-01_a) (kind . versioned) (order-key . 2026-01-01_a)) (undo . #f) (irreversible . #f) (success . #t) (applied-on . 1791981743651) (reason . no undo script)))
    [⚙️ assert] the newest migration is the wall
    ✅ got: boundary
  [🧪 test] --all stops at the first migration it cannot undo
    [⚙️ assert] newest first, then the boundary
    ✅ got: (undo undo boundary)
    [⚙️ assert] which is the migration without a script
    ✅ got: 2026-01-01_a.sql
  [🧪 test] a file marked irreversible is blocked, until it is not
    [⚙️ assert] the directive stops the default
    ✅ got: blocked
    [⚙️ assert] and says why
    ✅ got: marked irreversible
    [⚙️ assert] while the explicit override runs it
    ✅ got: undo
  [🧪 test] --all is not combined with --steps or --to
    [⚙️ assert] the alternatives are named
    ✅ got: #t
  [🧪 test] a recorded failure blocks its own rollback
    ✅ got: blocked
    ✅ got: records a failure; run repair first
  [🧪 test] one migration is undone by default, and it is the newest
    [⚙️ assert] the safe default is one step
    ✅ got: (((name . 2026-01-03_c.sql) (version . 2026-01-03_c) (file (name . 2026-01-03_c.sql) (path . /m/2026-01-03_c.sql) (version . 2026-01-03_c) (description . 2026-01-03_c) (kind . versioned) (order-key . 2026-01-03_c)) (undo (name . 2026-01-03_c.undo.sql) (path . /m/2026-01-03_c.undo.sql) (version . 2026-01-03_c) (description . 2026-01-03_c) (kind . undo) (order-key . 2026-01-03_c)) (irreversible . #f) (success . #t) (applied-on . 1791981743651) (action . undo) (reason . undo script)))
    [⚙️ assert] the newest applied migration
    ✅ got: 2026-01-03_c.sql
    [⚙️ assert] with the undo script beside it
    ✅ got: undo
    [⚙️ assert] which is the file the runner would apply
    ✅ got: 2026-01-03_c.undo.sql
  [🧪 test] more steps than there are migrations is refused
    [⚙️ assert] rather than quietly undoing fewer
    ✅ got: #t
  [🧪 test] a requested number of steps reports what blocks it
    [⚙️ assert] the two that can, and the one that cannot
    ✅ got: (undo undo blocked)
    [⚙️ assert] and why the last one is blocked
    ✅ got: no undo script
ℹ️ info - ran 7 tasks in: 1 seconds, -844914 microseconds

running unit tests from file: dialect.scm, entrypoint: spec


+----------------------------------------------+
[📑 suite] quoting, per engine
  [🧪 test] a literal is written as its SQL type
    [⚙️ assert] a number as itself
    ✅ got: 36
    [⚙️ assert] true as 1
    ✅ got: 1
    [⚙️ assert] false as 0
    ✅ got: 0
    [⚙️ assert] the null value as NULL
    ✅ got: NULL
    [⚙️ assert] a symbol as a quoted string
    ✅ got: 'versioned'
  [🧪 test] an identifier that holds a quote is still one identifier
    [⚙️ assert] SQLite doubles the inner double quote
    ✅ got: "we""ird"
    [⚙️ assert] MySQL doubles the inner backtick
    ✅ got: `we``ird`
  [🧪 test] MySQL doubles a backslash and SQLite does not
    [⚙️ assert] SQLite leaves a backslash alone
    ✅ got: 'C:\temp'
    [⚙️ assert] MySQL doubles it
    ✅ got: 'C:\\temp'
    [⚙️ assert] and MariaDB doubles it too
    ✅ got: 'C:\\temp'
  [🧪 test] a string is quoted with doubled single quotes
    [⚙️ assert] an ordinary value
    ✅ got: 'users'
    [⚙️ assert] an apostrophe does not end the literal
    ✅ got: 'user''s'
  [🧪 test] an identifier is quoted the way the engine quotes them
    [⚙️ assert] SQLite doubles the double quote
    ✅ got: "users"
    [⚙️ assert] PostgreSQL does the same
    ✅ got: "users"
    [⚙️ assert] MySQL uses backticks
    ✅ got: `users`
    [⚙️ assert] and so does MariaDB, through the same alias
    ✅ got: `users`
+----------------------------------------------+
[📑 suite] transactions and locking
  [🧪 test] every other engine opens a plain transaction
    [⚙️ assert] PostgreSQL
    ✅ got: BEGIN
    [⚙️ assert] MySQL
    ✅ got: BEGIN
  [🧪 test] only MySQL cannot undo a failed migration's DDL
    [⚙️ assert] MySQL commits DDL as it runs
    ✅ got: #f
    [⚙️ assert] SQLite rolls it back
    ✅ got: #t
    [⚙️ assert] and so does PostgreSQL
    ✅ got: #t
  [🧪 test] MySQL asks for a lock and gives up after ten seconds
    [⚙️ assert] the lock is named and timed
    ✅ got: #t
  [🧪 test] a lock is released by the engine that took it
    [⚙️ assert] PostgreSQL unlocks its own advisory lock
    ✅ got: #t
    [⚙️ assert] MySQL releases the named lock
    ✅ got: #t
  [🧪 test] PostgreSQL takes one advisory lock, named for the ledger
    [⚙️ assert] one statement
    ✅ got: (SELECT pg_advisory_lock(7789568688506408848))
    [⚙️ assert] and it is the advisory lock
    ✅ got: #t
    [⚙️ assert] the same ledger gets the same key
    ✅ got: (SELECT pg_advisory_lock(5756235829675053970))
    [⚙️ assert] and another ledger a different one
    ✅ got: #f
  [🧪 test] SQLite takes its write lock with the transaction
    [⚙️ assert] the write lock comes with BEGIN
    ✅ got: BEGIN IMMEDIATE
    [⚙️ assert] and no lock statement is needed
    ✅ got: ()
  [🧪 test] CockroachDB takes no advisory lock at all
    [⚙️ assert] no statement to take a lock that is not there
    ✅ got: ()
    [⚙️ assert] and none to release it
    ✅ got: ()
    [⚙️ assert] and it speaks PostgreSQL's SQL otherwise
    ✅ got: BEGIN
    [⚙️ assert] with transactional DDL
    ✅ got: #t
+----------------------------------------------+
[📑 suite] the ledger's own SQL
  [🧪 test] the catalogue is asked, not the table
    [⚙️ assert] SQLite asks sqlite_master
    ✅ got: #t
    [⚙️ assert] PostgreSQL asks information_schema
    ✅ got: #t
    [⚙️ assert] MySQL asks information_schema too
    ✅ got: #t
  [🧪 test] each engine gets a ledger it can create
    [⚙️ assert] SQLite's own autoincrement
    ✅ got: #t
    [⚙️ assert] PostgreSQL's serial and timestamps
    ✅ got: #t
    [⚙️ assert] MySQL's unique key and engine
    ✅ got: #t
  [🧪 test] creating the ledger twice is not an error
    [⚙️ assert] MySQL
    ✅ got: #t
    [⚙️ assert] PostgreSQL
    ✅ got: #t
  [🧪 test] the ledger has the same columns on every engine
    [⚙️ assert] what the runner reads and writes
    ✅ got: (name checksum description kind installed-on installed-by execution-time success)
    [⚙️ assert] MySQL agrees
    ✅ got: (name checksum description kind installed-on installed-by execution-time success)
    [⚙️ assert] and PostgreSQL
    ✅ got: (name checksum description kind installed-on installed-by execution-time success)
  [🧪 test] the columns are read from the catalogue as well
    [⚙️ assert] SQLite gets the pragma
    ✅ got: #t
    [⚙️ assert] PostgreSQL gets the column names
    ✅ got: #t
+----------------------------------------------+
[📑 suite] reading a ledger column
  [🧪 test] the timestamp column is read as text, where the driver needs it
    [⚙️ assert] PostgreSQL, whose driver cannot unpack a TIMESTAMPTZ
    ✅ got: "installed_on"::text AS "installed_on"
    [⚙️ assert] MySQL, whose driver answers with a number instead
    ✅ got: DATE_FORMAT(`installed_on`, '%Y-%m-%d %H:%i:%s') AS `installed_on`
    [⚙️ assert] and MariaDB through the same alias
    ✅ got: DATE_FORMAT(`installed_on`, '%Y-%m-%d %H:%i:%s') AS `installed_on`
    [⚙️ assert] SQLite, which keeps its timestamps as text already
    ✅ got: "installed_on"
    [⚙️ assert] and CockroachDB, which is reached as PostgreSQL
    ✅ got: "installed_on"::text AS "installed_on"
    [⚙️ assert] including the name the first ledger called it
    ✅ got: "applied_at"::text AS "applied_at"
  [🧪 test] every other column is read as it is named
    [⚙️ assert] a ledger column comes back quoted
    ✅ got: "name"
    [⚙️ assert] with the engine's own quoting
    ✅ got: `success`
+----------------------------------------------+
[📑 suite] dropping what clean found
  [🧪 test] an object type is normalised to one of four
    [⚙️ assert] a base table is a table
    ✅ got: table
    [⚙️ assert] and so is a system table
    ✅ got: table
    [⚙️ assert] a view is a view
    ✅ got: view
    [⚙️ assert] a trigger is a trigger
    ✅ got: trigger
    [⚙️ assert] an index is an index
    ✅ got: index
  [🧪 test] a view, a trigger and an index are dropped as what they are
    [⚙️ assert] a view is dropped as a view
    ✅ got: DROP VIEW IF EXISTS "active_users" CASCADE
    [⚙️ assert] a trigger as a trigger, whatever the case
    ✅ got: DROP TRIGGER IF EXISTS "touch_users"
    [⚙️ assert] an index as an index
    ✅ got: DROP INDEX IF EXISTS `email_1`
  [🧪 test] each engine lists what it can drop
    [⚙️ assert] SQLite, without its own internal tables
    ✅ got: #t
    [⚙️ assert] PostgreSQL, tables and views
    ✅ got: #t
    [⚙️ assert] MySQL, through information_schema
    ✅ got: #t
  [🧪 test] only PostgreSQL drops with CASCADE
    [⚙️ assert] PostgreSQL cascades
    ✅ got: DROP TABLE IF EXISTS "users" CASCADE
    [⚙️ assert] SQLite has nothing to cascade
    ✅ got: DROP TABLE IF EXISTS "users"
    [⚙️ assert] and neither has MySQL
    ✅ got: DROP TABLE IF EXISTS `users`
ℹ️ info - ran 5 tasks in: 0 seconds, 94603 microseconds

running unit tests from file: sql-runner.scm, entrypoint: spec


+----------------------------------------------+
[📑 suite] the ledger, on a real database
  [🧪 test] opening a session creates the ledger
    [⚙️ assert] opening a session creates the ledger
    ✅ got: #t
    [⚙️ assert] and the catalogue agrees it is there
    ✅ got: #t
    [⚙️ assert] and it holds nothing yet
    ✅ got: ()
    [⚙️ assert] with the columns the ledger is made of
    ✅ got: (id name checksum description kind installed-on installed-by execution-time success)
  [🧪 test] forgetting a migration removes its row
    [⚙️ assert] forgetting a migration removes its row
    ✅ got: ()
    [⚙️ assert] and looking it up answers nothing
    ✅ got: #f
  [🧪 test] a session that may not write writes nothing
    [⚙️ assert] a session that may not create the ledger does not
    ✅ got: #f
    [⚙️ assert] and a database with no ledger has no history to report
    ✅ got: ()
    [⚙️ assert] and nothing was written to the database at all
    ✅ got: ()
  [🧪 test] recording the same migration again replaces its row
    [⚙️ assert] recording the same migration again leaves one row
    ✅ got: (((id . 1) (name . 2026-01-01_10:00:00_init.sql) (checksum . d0f1) (description . init) (kind . versioned) (installed-on . 2026-11-10 19:48:09) (installed-by . mutastructura) (execution-time . 99) (success . 1)))
    [⚙️ assert] with what the second recording said
    ✅ got: 99
  [🧪 test] a closed session can be collected without an error
    [⚙️ assert] closing a session twice, and collecting it, is not an error
    ✅ got: #t
  [🧪 test] a recorded migration reads back in full
    [⚙️ assert] one migration is one row
    ✅ got: (((id . 1) (name . 2026-01-01_10:00:00_init.sql) (checksum . d0f1) (description . init) (kind . versioned) (installed-on . 2026-11-10 19:48:09) (installed-by . mutastructura) (execution-time . 12) (success . 1)))
    [⚙️ assert] with the name it was recorded under
    ✅ got: 2026-01-01_10:00:00_init.sql
    [⚙️ assert] and its checksum
    ✅ got: d0f1
    [⚙️ assert] and its kind, which only this ledger version has
    ✅ got: versioned
    [⚙️ assert] and how long it took
    ✅ got: 12
    [⚙️ assert] and a success the core can read
    ✅ got: #t
+----------------------------------------------+
[📑 suite] reading rows
  [🧪 test] every row of a query comes back
    [⚙️ assert] every row of a query comes back
    ✅ got: (((a . 1) (b . x)) ((a . 2) (b . y)))
    [⚙️ assert] with the values the database has
    ✅ got: 1
  [🧪 test] a refused query raises
    [⚙️ assert] a refused query raises the database's own message
    ✅ got: #t
  [🧪 test] an empty answer is not a failure
    [⚙️ assert] a query with no rows answers with no rows, and no error
    ✅ got: ()
+----------------------------------------------+
[📑 suite] running a migration
  [🧪 test] a file becomes its statements, placeholders and all
    [⚙️ assert] a file is read as its statements
    ✅ got: (((index . 0) (line . 2) (sql . -- ${other} is somebody else's
CREATE TABLE app_users (id INTEGER PRIMARY KEY))) ((index . 1) (line . 3) (sql . CREATE INDEX users_idx ON app_users (id))))
    [⚙️ assert] with the placeholder substituted in the first
    ✅ got: #t
    [⚙️ assert] and in the second
    ✅ got: #t
    [⚙️ assert] and a placeholder nobody defined is left alone
    ✅ got: #t
  [🧪 test] a dry run reports and changes nothing
    [⚙️ assert] a dry run reports the statements it would run
    ✅ got: 2
    [⚙️ assert] and runs none of them
    ✅ got: #f
  [🧪 test] applying then cleaning leaves an empty database
    [⚙️ assert] applying a migration creates its table
    ✅ got: #t
    [⚙️ assert] a dry run names what clean would drop
    ✅ got: 3
    [⚙️ assert] and drops nothing
    ✅ got: #t
    [⚙️ assert] clean drops what the catalogue reports
    ✅ got: 3
    [⚙️ assert] and the database, ledger included, is empty
    ✅ got: ()
+----------------------------------------------+
[📑 suite] the vtable
  [🧪 test] the SQL runner and the engines it answers for
    [⚙️ assert] the SQL runner is the relational family
    ✅ got: relational
    [⚙️ assert] and it says what it speaks
    ✅ got: #t
    [⚙️ assert] sqlite3 is spoken to by it
    ✅ got: relational
    [⚙️ assert] and so is an engine reached through MySQL's driver
    ✅ got: relational
ℹ️ info - ran 4 tasks in: 0 seconds, 529458 microseconds

running unit tests from file: sql-parser.scm, entrypoint: spec


+----------------------------------------------+
[📑 suite] a semicolon is not always the end
  [🧪 test] nothing between two semicolons is not a statement
    [⚙️ assert] the empty one is dropped
    ✅ got: (SELECT 1 SELECT 2)
  [🧪 test] a file of whitespace and comments holds no statements
    [⚙️ assert] nothing to run
    ✅ got: ()
    [⚙️ assert] and an empty file has nothing either
    ✅ got: ()
  [🧪 test] the line a statement starts on is reported
    [⚙️ assert] the line of the first real character
    ✅ got: (1 3)
  [🧪 test] a semicolon inside a string is part of the string
    [⚙️ assert] the insert is not cut in half
    ✅ got: (INSERT INTO t VALUES ('a;b') SELECT 1)
    [⚙️ assert] and it still holds its semicolon
    ✅ got: #t
  [🧪 test] a statement that is never terminated is still a statement
    [⚙️ assert] the last statement is read
    ✅ got: (SELECT 1 SELECT 2)
  [🧪 test] a doubled quote does not end the string
    [⚙️ assert] the escaped quote is part of the value
    ✅ got: (SELECT 'it''s; fine')
  [🧪 test] two statements separated by semicolons are two
    [⚙️ assert] both are read
    ✅ got: (((index . 0) (line . 1) (sql . SELECT 1)) ((index . 1) (line . 1) (sql . SELECT 2)))
    [⚙️ assert] and the delimiter is not part of either
    ✅ got: (SELECT 1 SELECT 2)
    [⚙️ assert] each carries its position
    ✅ got: (0 1)
  [🧪 test] a semicolon inside a quoted identifier is part of the identifier
    [⚙️ assert] the identifier is not cut in half
    ✅ got: (CREATE TABLE "we;ird" (x int))
+----------------------------------------------+
[📑 suite] comments
  [🧪 test] a comment can be stripped for the question of what is in a statement
    [⚙️ assert] the SQL is kept
    ✅ got: #t
    [⚙️ assert] and every comment is gone
    ✅ got: #f
    [⚙️ assert] a block comment goes too
    ✅ got: #t
  [🧪 test] what a statement means is decided without its comments
    [⚙️ assert] SQL is meaningful
    ✅ got: #t
    [⚙️ assert] a comment is not
    ✅ got: #f
    [⚙️ assert] and neither is whitespace
    ✅ got: #f
    [⚙️ assert] the two questions are each other's opposite
    ✅ got: #t
  [🧪 test] a `--' comment runs to the end of the line
    [⚙️ assert] the semicolon in it is not one
    ✅ got: (-- a; comment
SELECT 1)
  [🧪 test] a trailing comment neither splits nor extends a statement
    [⚙️ assert] the comment is not a second statement
    ✅ got: (SELECT 1)
  [🧪 test] `#' is a comment for MySQL and an operator for PostgreSQL
    [⚙️ assert] MySQL reads it as a comment
    ✅ got: (# a; comment
SELECT 1)
    [⚙️ assert] PostgreSQL does not, so the `;' splits
    ✅ got: (# a comment
SELECT 1)
  [🧪 test] a block comment nests where the engine nests them
    [⚙️ assert] PostgreSQL reads one comment and one statement
    ✅ got: (/* outer /* inner; */ still; */ SELECT 1)
    [⚙️ assert] with the select after it
    ✅ got: #t
    [⚙️ assert] and the others split at the inner semicolon
    ✅ got: (/* outer /* inner; */ still */ SELECT 1)
+----------------------------------------------+
[📑 suite] what only one dialect does
  [🧪 test] an `E' that is the tail of a word is not one
    [⚙️ assert] a word ending in W is not an escape string
    ✅ got: (SELECT W'it\'s fine';)
  [🧪 test] PostgreSQL's `E' strings are escape strings
    [⚙️ assert] the `E' makes the backslash an escape
    ✅ got: (SELECT E'it\'s; fine')
  [🧪 test] a backtick identifier is MySQL's alone
    [⚙️ assert] MySQL reads the backtick as an identifier
    ✅ got: (CREATE TABLE `we;ird` (x int))
    [⚙️ assert] and PostgreSQL does not
    ✅ got: (CREATE TABLE `we ird` (x int))
  [🧪 test] a backslash escapes inside a MySQL string
    [⚙️ assert] MySQL's escaped quote keeps the string open
    ✅ got: (INSERT INTO t VALUES ('a\';b'))
    [⚙️ assert] PostgreSQL's string ends at the quote
    ✅ got: (INSERT INTO t VALUES ('a\' b');)
+----------------------------------------------+
[📑 suite] a body a semicolon must not end
  [🧪 test] a statement that is not a CREATE is left exactly as it is
    [⚙️ assert] three statements, not one
    ✅ got: (BEGIN SELECT 1 COMMIT)
  [🧪 test] an `END IF' inside the body does not close it
    [⚙️ assert] the routine and the select
    ✅ got: (CREATE PROCEDURE p()
BEGIN
  IF x THEN
    SELECT 1;
  END IF;
  SELECT 2;
END SELECT 3)
    [⚙️ assert] the body after the END IF is still in it
    ✅ got: #t
  [🧪 test] SQLite's trigger body is one statement
    [⚙️ assert] the trigger and the select
    ✅ got: (CREATE TRIGGER touch AFTER INSERT ON users
BEGIN
  UPDATE users SET n = 1;
  DELETE FROM tmp;
END SELECT 1)
    [⚙️ assert] the whole body is in the first
    ✅ got: #t
    [⚙️ assert] including its END
    ✅ got: #t
+----------------------------------------------+
[📑 suite] dollar quoting
  [🧪 test] a positional parameter is not a tag
    [⚙️ assert] the statement still ends at its semicolon
    ✅ got: (SELECT $1, $2 SELECT 3)
  [🧪 test] a function body written with `$$' is one statement
    [⚙️ assert] the function and the select
    ✅ got: (CREATE FUNCTION f() RETURNS int AS $$
BEGIN
  RETURN 1;
END;
$$ LANGUAGE plpgsql SELECT 1)
    [⚙️ assert] the body is in the first statement
    ✅ got: #t
  [🧪 test] a tag makes the quoting explicit, and can appear in the body
    [⚙️ assert] the function and the select
    ✅ got: (CREATE FUNCTION f() RETURNS int AS $body$
  SELECT 1; -- $$ is just text in here
$body$ LANGUAGE sql SELECT 2)
    [⚙️ assert] and the `$$' inside is data
    ✅ got: #t
+----------------------------------------------+
[📑 suite] the mysql client's own directive
  [🧪 test] a DELIMITER line is recognised, and only when it is one
    [⚙️ assert] the delimiter is reported
    ✅ got: (delimiter . $$)
    [⚙️ assert] in any case
    ✅ got: (delimiter . //)
    [⚙️ assert] a word that only starts with it is not it
    ✅ got: #f
    [⚙️ assert] nor is one with nothing to set
    ✅ got: #f;;; note: source file ./src/mutastructura/mutastructura.scm
;;;       newer than compiled /gnu/store/21afi1i81zcsj3g9fjxxaxrkfxm3gfjp-profile/lib/guile/3.0/site-ccache/mutastructura/mutastructura.go
;;; found fresh local cache at /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/mutastructura/trunk/src/mutastructura/mutastructura.scm.go
;;; note: source file ./src/mutastructura/persistence/migration-runner.scm
;;;       newer than compiled /gnu/store/21afi1i81zcsj3g9fjxxaxrkfxm3gfjp-profile/lib/guile/3.0/site-ccache/mutastructura/persistence/migration-runner.go
;;; found fresh local cache at /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/mutastructura/trunk/src/mutastructura/persistence/migration-runner.scm.go
;;; note: source file ./src/mutastructura/persistence/util.scm
;;;       newer than compiled /gnu/store/21afi1i81zcsj3g9fjxxaxrkfxm3gfjp-profile/lib/guile/3.0/site-ccache/mutastructura/persistence/util.go
;;; found fresh local cache at /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/mutastructura/trunk/src/mutastructura/persistence/util.scm.go
;;; note: source file ./src/mutastructura/persistence/sql-parser.scm
;;;       newer than compiled /gnu/store/21afi1i81zcsj3g9fjxxaxrkfxm3gfjp-profile/lib/guile/3.0/site-ccache/mutastructura/persistence/sql-parser.go
;;; found fresh local cache at /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/mutastructura/trunk/src/mutastructura/persistence/sql-parser.scm.go

    [⚙️ assert] nor one asking for two things
    ✅ got: #f
    [⚙️ assert] nor a commented-out one
    ✅ got: #f
  [🧪 test] the directive retargets the splitter
    [⚙️ assert] the trigger and the select
    ✅ got: (CREATE TRIGGER t AFTER INSERT ON u
BEGIN
  SELECT 1;
END SELECT 2)
    [⚙️ assert] the body survived the new delimiter
    ✅ got: #t
    [⚙️ assert] and the directive is not sent anywhere
    ✅ got: #f
    [⚙️ assert] nor is the one that restored it
    ✅ got: #f
+----------------------------------------------+
[📑 suite] the entry points agree with each other
  [🧪 test] an alias resolves to the dialect it names
    [⚙️ assert] MariaDB lexes as MySQL
    ✅ got: (# a; comment
SELECT 1)
  [🧪 test] the string-in/list-of-strings-out entry point
    [⚙️ assert] the simple entry point splits the same way
    ✅ got: (SELECT 1 SELECT 2)
    [⚙️ assert] and knows the engine it was given
    ✅ got: (# a; comment
SELECT 1)
    [⚙️ assert] with the `#' line read as a comment
    ✅ got: #t
  [🧪 test] the byte order mark some editors write is not part of the SQL
    [⚙️ assert] the BOM is dropped
    ✅ got: SELECT 1
  [🧪 test] the first statement of a file, and the line it is on
    [⚙️ assert] the first statement is the first
    ✅ got: SELECT 1
    [⚙️ assert] and it knows which line it starts on
    ✅ got: 3
ℹ️ info - ran 7 tasks in: 0 seconds, 87891 microseconds

finished running tests


--------------------------------------------------------------

Maak 0.14.0: the infinitely extensible command runner

Loading tasks from Maak file: /etc/byggsteg/job-clone/mutastructura/trunk/maak.scm

Executing Maak tasks: test


--------------------------------------------------------------

=> Running Maak task: test

  Run the unit test suite with Veritas.

  Execute command:
env VERITAS_TEST_TYPE=unit VERITAS_RUN_BENCHMARKS=false guix shell -m manifest.scm -- guile -L ./src -L ./test -L . -c '((@ (veritas runner) run-tests))'

Executing: env VERITAS_TEST_TYPE=unit VERITAS_RUN_BENCHMARKS=false guix shell -m manifest.scm -- guile -L ./src -L ./test -L . -c '((@ (veritas runner) run-tests))'
deploysudo maak deploy succeeded34 lines · 1.6s
--------------------------------------------------------------

Maak 0.14.0: the infinitely extensible command runner

Loading tasks from Maak file: /etc/byggsteg/job-clone/mutastructura/trunk/maak.scm

Executing Maak tasks: deploy


--------------------------------------------------------------

=> Running Maak task: deploy

API documentation saved to doc/api
index.texi: warning: must specify a title with a title command or @top

Executing: guix time-machine --channels=channels.scm -- shell -m manifest.scm -- documenta api ./src/mutastructura

Executing: guix time-machine --channels=channels.scm -- shell -m manifest.scm -- texi2any -v --html --css-ref=https://jointhefreeworld.org/static/css/jointhefreeworld.css ./doc/api/index.texi
renamed './index' -> './doc/api-dist'
removed '/srv/http/jointhefreeworld.org/api-docs/mutastructura/API.html'
removed '/srv/http/jointhefreeworld.org/api-docs/mutastructura/mutastructura_005fmain.html'
removed '/srv/http/jointhefreeworld.org/api-docs/mutastructura/mutastructura_005fmutastructura.html'
removed '/srv/http/jointhefreeworld.org/api-docs/mutastructura/mutastructura_005fpersistence_005fdialect.html'
removed '/srv/http/jointhefreeworld.org/api-docs/mutastructura/mutastructura_005fpersistence_005fmigration_002drunner.html'
removed '/srv/http/jointhefreeworld.org/api-docs/mutastructura/mutastructura_005fpersistence_005frunners.html'
removed '/srv/http/jointhefreeworld.org/api-docs/mutastructura/mutastructura_005fpersistence_005fsql_002dparser.html'
removed '/srv/http/jointhefreeworld.org/api-docs/mutastructura/mutastructura_005fpersistence_005futil.html'
removed directory '/srv/http/jointhefreeworld.org/api-docs/mutastructura'
mkdir: created directory '/srv/http/jointhefreeworld.org/api-docs/mutastructura'
renamed 'doc/api-dist/API.html' -> '/srv/http/jointhefreeworld.org/api-docs/mutastructura/API.html'
renamed 'doc/api-dist/mutastructura_005fmain.html' -> '/srv/http/jointhefreeworld.org/api-docs/mutastructura/mutastructura_005fmain.html'
renamed 'doc/api-dist/mutastructura_005fmutastructura.html' -> '/srv/http/jointhefreeworld.org/api-docs/mutastructura/mutastructura_005fmutastructura.html'
renamed 'doc/api-dist/mutastructura_005fpersistence_005fdialect.html' -> '/srv/http/jointhefreeworld.org/api-docs/mutastructura/mutastructura_005fpersistence_005fdialect.html'
renamed 'doc/api-dist/mutastructura_005fpersistence_005fmigration_002drunner.html' -> '/srv/http/jointhefreeworld.org/api-docs/mutastructura/mutastructura_005fpersistence_005fmigration_002drunner.html'
renamed 'doc/api-dist/mutastructura_005fpersistence_005frunners.html' -> '/srv/http/jointhefreeworld.org/api-docs/mutastructura/mutastructura_005fpersistence_005frunners.html'
renamed 'doc/api-dist/mutastructura_005fpersistence_005fsql_002dparser.html' -> '/srv/http/jointhefreeworld.org/api-docs/mutastructura/mutastructura_005fpersistence_005fsql_002dparser.html'
renamed 'doc/api-dist/mutastructura_005fpersistence_005futil.html' -> '/srv/http/jointhefreeworld.org/api-docs/mutastructura/mutastructura_005fpersistence_005futil.html'

Executing: mv -v doc/api-dist/* /srv/http/jointhefreeworld.org/api-docs/mutastructura
removed 'doc/api/index.texi'
removed directory 'doc/api'
removed directory 'doc/api-dist'
removed directory 'doc'
inspect the job's lambda expression
(lambda()
  `((task . ("maak-test" "maak-deploy"))
    (project . "mutastructura")
    (branch-name . "trunk")
    (clone-url . "https://github.com/jjba23/mutastructura.git")))