starting new job...
→ running command: dir='/etc/byggsteg/job-clone//bewaking/trunk'
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 86178d1 to 4f34cba
→ running command: cd /etc/byggsteg/job-clone/bewaking/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/bewaking/trunk && git log --name-status HEAD^..HEAD
commit 4f34cba6f23b75bcd7f77f6922f3384881a65921
Author: Josep Bigorra <jjbigorra@gmail.com>
Date: Wed Nov 4 23:16:29 2026 +0100
docs: Simplify the AI policy wording
M README.org
→ running command: cd /etc/byggsteg/job-clone/bewaking/trunk && maak test
scanning directories for unit tests...
scanning present working directory for unit tests...
shuffled the files with seed 95242164 (replay with VERITAS_SEED=95242164)
found unit tests: (cli-spec.scm bewaking-spec.scm crypto-spec.scm web-spec.scm password-spec.scm)
running unit tests from file: cli-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 95242164 (replay with VERITAS_SEED=95242164)
+----------------------------------------------+
[📑 suite] CLI token commands
[🧪 test] wrong keys are rejected
[⚙️ assert] a wrong signing key exits with 1
✅ got: #t
[🧪 test] key id and keyring rotation
[⚙️ assert] a keyring round-trips and rejects the wrong key
✅ got: #t
[🧪 test] token round-trip
[⚙️ assert] generate then decrypt preserves the payload
✅ got: #t
+----------------------------------------------+
[📑 suite] CLI rotation metadata
[🧪 test] token inspect audits the header without keys
[⚙️ assert] inspect reports version, key id and expiry
✅ got: #t
[⚙️ assert] inspect reports an expired envelope
✅ got: #t
[⚙️ assert] inspect rejects a non-envelope token
✅ got: #t
[🧪 test] expiry is sealed into tokens and enforced
[⚙️ assert] an expired token exits with 1
✅ got: #t
[⚙️ assert] --allow-expired opens it anyway
✅ got: #t
[⚙️ assert] --max-age-seconds accepts fresh data
✅ got: #t
[⚙️ assert] --ttl-seconds and --expires-at conflict
✅ got: #t
+----------------------------------------------+
[📑 suite] CLI dispatch
[🧪 test] help and exit codes
[⚙️ assert] help exits successfully
✅ got: 0
[⚙️ assert] help prints the usage banner
✅ got: #t
[⚙️ assert] help lists the token generate command
✅ got: #t
[⚙️ assert] help documents the keyring option
✅ got: #t
[⚙️ assert] help documents the rotation metadata options
✅ got: #t
[⚙️ assert] help lists the token inspect command
✅ got: #t
[⚙️ assert] version exits successfully
✅ got: 0
[⚙️ assert] an unknown command exits with 1
✅ got: 1
[⚙️ assert] a token subcommand is required
✅ got: 1
[⚙️ assert] decrypting without keys fails
✅ got: 1
+----------------------------------------------+
[📑 suite] CLI object commands
[🧪 test] object expiry policy
[⚙️ assert] an object sealed with a past expiry is refused
✅ got: #t
[🧪 test] object round-trip
[⚙️ assert] object encrypt then decrypt round-trips a datum
✅ got: #t
+----------------------------------------------+
[📑 suite] CLI password commands
[🧪 test] password strength
[⚙️ assert] strength exits successfully
✅ got: 0
[⚙️ assert] a common password is reported weak
✅ got: #t
[🧪 test] password generate
[⚙️ assert] generation exits successfully
✅ got: 0
[⚙️ assert] the requested length is honoured
✅ got: 32
[⚙️ assert] symbols are excluded on request
✅ got: (lowercase uppercase digit)
+----------------------------------------------+
[📑 suite] CLI key derivation and dropped formats
[🧪 test] tampering and missing arguments are refused
[⚙️ assert] an edited token exits with 1
✅ got: #t
[⚙️ assert] generating without an owner id exits with 1
✅ got: #t
[⚙️ assert] sealing an object without keys exits with 1
✅ got: #t
[🧪 test] a refusal does not reveal which key ids the keyring configures
[⚙️ assert] an unknown key id and an edited body give the same message and exit code
✅ got: #t
[🧪 test] envelopes of dropped formats are refused
[⚙️ assert] a BWK1 token cannot be decrypted or inspected
✅ got: #t
[⚙️ assert] a BWK2 token cannot be decrypted or inspected
✅ got: #t
[⚙️ assert] an empty token is refused
✅ got: #t
[🧪 test] the key derivation is chosen when sealing and read back when opening
[⚙️ assert] an Argon2id token round-trips through the CLI
✅ got: #t
[⚙️ assert] a KDF parameter without --kdf is refused
✅ got: #t
ℹ️ info - ran 6 tasks in: 1 seconds, -100987 microseconds
running unit tests from file: bewaking-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 95242164 (replay with VERITAS_SEED=95242164)
+----------------------------------------------+
[📑 suite] Sealed envelope (versioned, authenticated)
[🧪 test] key rotation with a keyringerror decrypting object: misc-error: (#f ~A (bewaking: the sealed data could not be opened: unknown, retired or wrong key, or the data was altered.) #f)error decrypting object: misc-error: (#f ~A (bewaking: the sealed data could not be opened: unknown, retired or wrong key, or the data was altered.) #f)
[⚙️ assert] a keyring seals and opens by key id
✅ got: #t
[🧪 test] no reader is left for the earlier formatserror decrypting token: misc-error: (#f ~A (open-envelope: not a bewaking envelope (expected BWK3).) #f)error decrypting object: misc-error: (#f ~A (open-envelope: not a bewaking envelope (expected BWK3).) #f)
[⚙️ assert] a BWK1 envelope is not recognised
✅ got: #f
[⚙️ assert] a BWK2 envelope is not recognised
✅ got: #f
[⚙️ assert] a BWK2 envelope with no expiry is not recognised
✅ got: #f
[⚙️ assert] no metadata is read out of a BWK1 envelope
✅ got: #f
[⚙️ assert] no metadata is read out of a BWK2 envelope
✅ got: #f
[⚙️ assert] an unreadable envelope is never reported as live
✅ got: #f
[⚙️ assert] a BWK2 token is refused even with the keys that sealed it
✅ got: #f
[⚙️ assert] a BWK1 object is refused likewise
✅ got: #f
[🧪 test] data that is not a BWK3 envelope is refusederror decrypting token: misc-error: (#f ~A (open-envelope: not a bewaking envelope (expected BWK3).) #f)error decrypting object: misc-error: (#f ~A (open-envelope: not a bewaking envelope (expected BWK3).) #f)
[⚙️ assert] arbitrary text is not an envelope
✅ got: #f
[⚙️ assert] an unknown envelope version is not recognised
✅ got: #f
[⚙️ assert] a truncated envelope is refused when it is opened
✅ got: #t
[⚙️ assert] recognition is a version-tag check, not a structure check
✅ got: #t
[⚙️ assert] a BWK3 envelope carrying an extra MAC field is refused
✅ got: #t
[⚙️ assert] an unsupported version raises rather than being guessed at
✅ got: #t
[⚙️ assert] a pre-versioning bare Base16 token is refused
✅ got: #f
[⚙️ assert] a bare Base16 object is refused
✅ got: #f
[🧪 test] the requested key id is embedded in generated tokens
[⚙️ assert] the token carries the key id
✅ got: #t
[🧪 test] knowing the format is not knowing the plaintext
[⚙️ assert] the plaintext, its Base16 and its Base64 do not appear in the envelope
✅ got: #t
[⚙️ assert] the format is not hidden, only the content is
✅ got: #t
[⚙️ assert] the public metadata is exactly what is authenticated
✅ got: (version key-id created-at expires-at)
[🧪 test] the same plaintext never seals to the same value
[⚙️ assert] a fresh salt and nonce make each envelope unique
✅ got: #t
[⚙️ assert] equal objects seal to different strings
✅ got: #f
[🧪 test] seal-envelope and open-envelope
[⚙️ assert] the result is a versioned envelope
✅ got: #t
[⚙️ assert] the envelope starts with the version tag
✅ got: #t
[⚙️ assert] the module exposes the current format version
✅ got: BWK3
[⚙️ assert] BWK3 is the only version produced and read
✅ got: (BWK3)
[⚙️ assert] the plaintext round-trips
✅ got: top secret payload
[⚙️ assert] a tampered envelope fails the integrity check
✅ got: #t
[⚙️ assert] a wrong signing key fails the cipher check
✅ got: #t
[⚙️ assert] passing a MAC key to open-envelope is refused, not ignored
✅ got: #t
[🧪 test] tokens and objects are envelopes and reject tamperingerror decrypting token: misc-error: (#f ~A (bewaking: the sealed data could not be opened: unknown, retired or wrong key, or the data was altered.) #f)error decrypting object: misc-error: (#f ~A (bewaking: the sealed data could not be opened: unknown, retired or wrong key, or the data was altered.) #f)
[⚙️ assert] generated tokens are enveloped
✅ got: #t
[⚙️ assert] sealed objects are enveloped
✅ got: #t
[⚙️ assert] a tampered token is rejected
✅ got: #t
[⚙️ assert] a tampered object is rejected
✅ got: #t
[🧪 test] a refusal never says which key id the keyring configures
[⚙️ assert] the refusal is a refusal, not an accidental success
✅ got: #t
[⚙️ assert] an unknown key id and a tampered body fail identically
✅ got: (misc-error #f ~A (bewaking: the sealed data could not be opened: unknown, retired or wrong key, or the data was altered.) #f)
[⚙️ assert] a keyring with no matching entry fails identically
✅ got: (misc-error #f ~A (bewaking: the sealed data could not be opened: unknown, retired or wrong key, or the data was altered.) #f)
[⚙️ assert] a retired key fails identically
✅ got: (misc-error #f ~A (bewaking: the sealed data could not be opened: unknown, retired or wrong key, or the data was altered.) #f)
[⚙️ assert] a key that is not active yet fails identically
✅ got: (misc-error #f ~A (bewaking: the sealed data could not be opened: unknown, retired or wrong key, or the data was altered.) #f)
[⚙️ assert] the refusal does not name the key id it could not resolve
✅ got: #f
[⚙️ assert] and it does not name a configured key id either
✅ got: #f
[⚙️ assert] nor does it carry any key material
✅ got: #f
[⚙️ assert] an expired genuine envelope is still told apart, since it authenticated first
✅ got: #f
[🧪 test] editing a genuine envelope never yields a plaintext
[⚙️ assert] every single-digit mutation of a real envelope is refused
✅ got: #t
[⚙️ assert] and every mutation of a real sealed object too
✅ got: #t
+----------------------------------------------+
[📑 suite] Generic object encryption
[🧪 test] decrypted data is data, never code
[⚙️ assert] Guile refuses the #. read-eval syntax
✅ got: #t
[⚙️ assert] a sealed payload that looks like code stays a string
✅ got: #.(+ 1 2)
[🧪 test] encrypt-object and decrypt-objecterror decrypting object: misc-error: (#f ~A (bewaking: the sealed data could not be opened: unknown, retired or wrong key, or the data was altered.) #f)
[⚙️ assert] sealing produces a non-empty string
✅ got: #t
[⚙️ assert] round-trips an arbitrary alist payload
✅ got: ((user . joe) (age . 42) (roles admin editor) (tags a b) (nested (x . 1) (y . 2)))
[⚙️ assert] fails gracefully with a wrong signing key
✅ got: #f
[⚙️ assert] round-trips a plain string payload
✅ got: just a string
[⚙️ assert] round-trips a number payload
✅ got: 1234
[⚙️ assert] round-trips a list payload
✅ got: (1 2 3)
+----------------------------------------------+
[📑 suite] generate-token
[🧪 test] works properly with correct details
owner-details: ((id . "444959d3-7a8e-455e-b608-31e35145a2fa") (email . "someone@jointhefreeworld.org") (name . "Legolas son of Thandruil") (permissions byggsteg:modify-permissions byggsteg:job-create byggsteg:job-delete byggsteg:profile-create byggsteg:profile-delete byggsteg:user-create byggsteg:user-delete))
generate-token: BWK3.default.1793831302.-.01393fb91e00b87f23bce4e1cc2fd8f1f828ec123ebfc3d022ae950fb7b0af190123552f5b7df959ac80bb752a6e138c1c2aece3ecc443044e7c35e072c5d7213fb3b1ad1e39e8f854335140bdf68e3ba78107e9c25593454337a12330cde03ee1599defe75bc9d2af36f269ff55379c9485c907d2314404dcd58652738a1cc6c0f4f78617a126232b82a51ffa04fbf078bf6ca9e3d3731ece7bdf2016f986acf660ed08e46bd71b90df5bad7eecfd64aa514fa878fee83cf7a228110f618f62a8a8053cf206687c8de61b7859704b1bcd4bbfe2e4ad4459191980fb154ee39700b83c8b8592938f34792ad1b4a4abb7b1e9b939d8475ba40e06e86b83343c1b2c29de4183f157063fc725818d03bfbebd3d5cf68bf3c99fa4fded4282c8bc14100d251220b63d7c39c9bac9cab2fa84354e84904053a56856debe403ca9101140feccdd960c8e9c89131011eec411fe0b0c79ef0cddcec72db1e10c673bacbc281f0ac5c71696709958d84f1b93d9caad54279061bcc1d4d0f0415f53ad6cf0aa2b6bc8a3ece1f9e1b731f89f12d6d09091fbfaa2d5a947c2e9c6fa56d4e5a6a6856f4a6a9e4e28204167b529f30a7b1379b1d5a8ae4f27b9951ce9e99ceb81bb94ac66df296de10ffe8861726f6b4d1c39ea370663b01c42b651bfb2b4670981dfb4bfc8e5fe358568a24e380c5c0a5c7466197de3f65f1ccd24d239092e162b32fd34f8292f84447f76b8e1a7620adbcca296fa10ec6134187a3ccdb7f79eeba10efbedd7fcddc2b5b1624dd4ffafd65f630abd16f11606d46758732804abe58b8b7faa305c9a25f3297aea15e7e0134cadeb51a5fefa42ed71b4a95f42bf97fd5da3a6332f9a8bb49e37202939277279effd3275ff4ec549d4dc17f2bc1a68333b76bb1e168f541c1963ca4a13a81fbfbd80e06243144b597a6be6cd70c7b4982c1d70dcff0f2feb60c702f0f4c91f25517cb4f4ae38bf1b6009a66a258034a42e82de7a807ed9ac559f233eb3f330b6037e7fae1d1439358a9a049b4bba97392de3349e9ecc21eee902d66e8c7db1fc52cf1636a966f11f2290add063a83c93928deea0fe61c23db6529b59273e2e02f3e5dc787d368902bb40cb576cca079ae57b597f431b29df0dab32fc56ae1260caff7aed812bb8934a56534cc46aaf87adaf6507f7d439c5b211ba574055f6c6e0addc50805605b1c52d8bbba8e0e654fa21836bb61157dab66acf6bbc0afec4f799cec183c5fbd05a127ce9042df35df658b7e4486243642f4759bb5aa3c96795a85a7df1d028
✅ got: #t
+----------------------------------------------+
[📑 suite] Decryption
[🧪 test] decrypt-tokenerror decrypting token: misc-error: (#f ~A (bewaking: the sealed data could not be opened: unknown, retired or wrong key, or the data was altered.) #f)error decrypting token: misc-error: (#f ~A (open-envelope: not a bewaking envelope (expected BWK3).) #f)
[⚙️ assert] fails gracefully when signing key is not valid
✅ got: #f
[⚙️ assert] fails gracefully when token is not valid
✅ got: #f
[⚙️ assert] works properly with valid token
✅ got: #t
+----------------------------------------------+
[📑 suite] Token expiry
[🧪 test] is-token-expired?
[⚙️ assert] a token whose expiry is in the past is expired
✅ got: #t
[⚙️ assert] a token whose expiry is in the future is not expired
✅ got: #f
[⚙️ assert] a far-future token is not expired
✅ got: #f
[⚙️ assert] a missing token-expires-at raises an error
✅ success catching error: misc-error: (#f is-token-expired?: Token is invalid. Missing or malformed 'token-expires-at' () #f)
[⚙️ assert] a malformed token-expires-at raises an error
✅ success catching error: misc-error: (#f is-token-expired?: Token is invalid. Missing or malformed 'token-expires-at' () #f)
+----------------------------------------------+
[📑 suite] Permissions
[🧪 test] has-needed-permissions?
✅ got: #f
✅ got: #t
✅ got: #f
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #f
✅ got: #f
+----------------------------------------------+
[📑 suite] Lossless Token Decryption
[🧪 test] lossless-token
owner-details: ((id . "444959d3-7a8e-455e-b608-31e35145a2fa") (email . "someone@jointhefreeworld.org") (name . "Legolas son of Thandruil") (permissions byggsteg:modify-permissions byggsteg:job-create byggsteg:job-delete byggsteg:profile-create byggsteg:profile-delete byggsteg:user-create byggsteg:user-delete))
generate-token: BWK3.default.1793831302.-.017888930c5ff50c181a8aaec2a3aa99e33ee58a6dfd7249dac08967c5b6dd388bfa42148c0dfa5500d99773fdcc3dda3f52a4864eb4b00bde52cead1a71cf31d2f86732d632e676adb6f7679b0d90c1b4c0e53f218dda22ffa3b1bf5a5acda9f4c745f874f1a647025ac3fb3a056f6368e7c06325916678a9398b12f361d0289c5c3c6c106934fe6ee44060f7da3886d7deabb160eee7497998778f0dade0d8516ab0c3b5cc5898239d3f76fc7619b5fb10a7aa9adfdaa71317e15ecf19525dc83299511a6b2c5e1f3604ed0bb92edb5ce195a1fee5fde011ba4941a1d93ffc3645587be9b9a9c2f5f19105149fda8d26cc3d39378b7db21f84202caae41b3a535a104dddbce413a7078cd7dd3cda0d63be79ea2197e0b6ed7b2256a0d238627e1dc9a8fcacc4a00c0d226b930add951478bc5e66d5d8cd3a8eb6f2557eafa0413c5e5a786c5b0b872bf35ab5d8eecf7cb4c22b4ab278454c6207cacb8a6bc3ec3ecd64662286e86058565a34f00a5f20f0f3286510063882016b6a071041b3f89934b36bc88bb73cba9242301349335ec82326403f77275635b62aa583b9a1ef313ca3c73c66116b2f92537562af44e03dd8f6126ab8866bffb6c2daf3855fc1d146dc8ce38b00a830ac7bae52149fca24836e9d35624849a15c41b01147e0051c2cedd3e43923bd0ee5486e30d1e5399b843f29311e9304f97b38ea81d8ac33c4c894194380976b9698f596b9c3f1e5744f9a34faba9c678cec1bd4bd67db7c02435142b9f6b6c3245672ce5a5fc0e982dfd2684ebfa7e37d3faf5b3fd7baf5da4559107c2bf24f0c8c988b5d5d43b924f8f2f68e9ed7016aa72035e6726e56e32e2d450bdda9b4920a1c9d3733b405ccb41a3f7893b73b5776e97cc06d5e65dcf40c7919b93bc8bc9f63a3602cc340b9e849e1ef25a3f538c3219af33b9b1ee5927ac293556c0f842f657710d89bdce5c9261b6c26f1e933f24580fb697e8dec0e63c2fa29a7926370333a27ed55c4c17c22200d4aba74001fe0783032486a941c0f7620f9d1c6f473af2151e833c12b5f718ee4b264dc3175998dfe2ceab73f2fd405473dba71fdd9a7e728cf48fab01ecb942c5f41c9fcd4139008130940918d2583ee9a7179d06811514ff54836f74a125a8a0891edcfb78626baec10278f7603a1b5723db66da965a94ffe347d937fc2a569667bd2ccbb5a223bccb70c98825fd652508668534644492df49e969fe8e1ea2b5e9750c42613456f30320d142f46836e4e25d93054e1d72804c48f4adf778ee7d13a2f
decoded: ((token-type . bewaking-token) (token-id . f6671c65-80c6-4e0e-8d80-a57766c51378) (token-created-at . 1793831302) (token-expires-at . 1794436102) (owner-id . 444959d3-7a8e-455e-b608-31e35145a2fa) (owner-email . someone@jointhefreeworld.org) (owner-name . Legolas son of Thandruil) (owner-permissions byggsteg:modify-permissions byggsteg:job-create byggsteg:job-delete byggsteg:profile-create byggsteg:profile-delete byggsteg:user-create byggsteg:user-delete))
[⚙️ assert] works properly with correct details
✅ got: #t
+----------------------------------------------+
[📑 suite] Token validity helpers
[🧪 test] token-remaining-seconds and token-valid?
[⚙️ assert] remaining seconds is positive for a live token
✅ got: #t
[⚙️ assert] remaining seconds is negative for an expired token
✅ got: #t
[⚙️ assert] a missing expiry raises an error
✅ success catching error: misc-error: (#f token-remaining-seconds: Token is invalid. Missing or malformed 'token-expires-at' () #f)
[⚙️ assert] a freshly generated token is valid
✅ got: #t
[⚙️ assert] a freshly generated token is not expired
✅ got: #f
+----------------------------------------------+
[📑 suite] Envelope metadata (key id, created-at, expiry)
[🧪 test] invalid sealing options are rejected
[⚙️ assert] expires-at and ttl-seconds are mutually exclusive
✅ got: #t
[⚙️ assert] a non-integer ttl-seconds is rejected
✅ got: #t
[⚙️ assert] a key id containing a dot is rejected
✅ got: #t
[🧪 test] a malformed envelope is rejected
[⚙️ assert] a missing body is refused
✅ got: #t
[⚙️ assert] a non-numeric created-at is refused
✅ got: #t
[⚙️ assert] a body that is not hexadecimal is refused
✅ got: #t
[⚙️ assert] a truncated body is refused
✅ got: #t
[⚙️ assert] sealing a non-string plaintext is refused
✅ got: #t
[⚙️ assert] sealing without a signing key is refused
✅ got: #t
[⚙️ assert] an empty key id is refused
✅ got: #t
[🧪 test] expired tokens and objects are refusederror decrypting token: misc-error: (#f ~A (open-envelope: the sealed data expired at 1000 (key 'default').) #f)error decrypting object: misc-error: (#f ~A (open-envelope: the sealed data expired at 1000 (key 'default').) #f)
[⚙️ assert] a token sealed with a past expiry is refused
✅ got: #t
[⚙️ assert] an object sealed with a past expiry is refused
✅ got: #t
[⚙️ assert] expiry auditing ignores non-envelopes
✅ got: #f
[🧪 test] metadata is reported without any key
[⚙️ assert] created-at is a Unix timestamp
✅ got: #t
[⚙️ assert] the key id is reported
✅ got: default
[⚙️ assert] there is no expiry unless one was requested
✅ got: #f
[⚙️ assert] the format version is reported
✅ got: BWK3
[⚙️ assert] metadata is only read from envelopes
✅ got: #f
[⚙️ assert] an envelope of a dropped format reports no metadata
✅ got: #f
[⚙️ assert] a malformed BWK3 envelope reports no metadata
✅ got: #f
[🧪 test] ttl-seconds records an absolute expiry
[⚙️ assert] created-at is the requested timestamp
✅ got: 1000
[⚙️ assert] expires-at is created-at plus the ttl
✅ got: 4600
[⚙️ assert] the envelope is live before its expiry
✅ got: #f
[⚙️ assert] the envelope is expired after its expiry
✅ got: #t
[🧪 test] the metadata is authenticated
[⚙️ assert] editing created-at in a BWK3 envelope breaks its tag
✅ got: #t
[⚙️ assert] editing the key id breaks the tag
✅ got: #t
[⚙️ assert] extending the expiry in the envelope breaks the tag
✅ got: #t
[⚙️ assert] removing the expiry marker breaks the tag
✅ got: #t
[⚙️ assert] relabelling the version is refused
✅ got: #t
[⚙️ assert] backdating created-at breaks the tag
✅ got: #t
[⚙️ assert] editing the body breaks the tag
✅ got: #t
[🧪 test] expiry is enforced when opening
[⚙️ assert] an expired envelope is refused
✅ got: #t
[⚙️ assert] enforce-expiry? #f still opens it
✅ got: ttl payload
[⚙️ assert] max-age-seconds enforces a rotation policy
✅ got: #t
[🧪 test] keyring validity windows enforce rotation
[⚙️ assert] a key inside its window seals and opens
✅ got: #t
[⚙️ assert] a key past not-after cannot open data
✅ got: #t
[⚙️ assert] a key before not-before cannot seal data
✅ got: #t
[⚙️ assert] no keyring is needed to read metadata
✅ got: #t
+----------------------------------------------+
[📑 suite] Garbling and Ungarbling
[🧪 test] garble-ungarble
✅ got: dlrow
✅ got: lrow
✅ got: row
✅ got: ow
✅ got: w
✅ got: dlrow olleh
✅ got: hello world
✅ got: mxs7tu054hznj486qxtk3swcp0jwpwuy8aghnaf0vtuwt40lkp50odvfn8qtjc3hjeuwxrdjoauwx3m4jp6sxae1mo12pf8ckv1kpeosaklwqiyq89pmddiwc8ole2th
ℹ️ info - ran 10 tasks in: 4 seconds, 500932 microseconds
running unit tests from file: crypto-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 95242164 (replay with VERITAS_SEED=95242164)
+----------------------------------------------+
[📑 suite] Derived key material
[🧪 test] an unknown derivation is refused
[⚙️ assert] no other function can be named
✅ got: #t
[🧪 test] Argon2id key material honours its parameters
[⚙️ assert] the full key-and-IV length can be derived
✅ got: 48
[⚙️ assert] changing the pass count changes the material
✅ got: #t
[⚙️ assert] changing the passphrase changes the material
✅ got: #t
[🧪 test] PBKDF2 key material is the PBKDF2 derivation's
[⚙️ assert] derive-key-material agrees with the primitive it names
✅ got: 25ff9d4852ea4ea0ff9caf7aa33ed9e25f8d8a4eb2915445e0ff398292d67473
[⚙️ assert] the requested length is produced
✅ got: 32
+----------------------------------------------+
[📑 suite] Encodings of raw bytes
[🧪 test] Base64 round-trips arbitrary bytevectors
[⚙️ assert] bytes that are not valid UTF-8 survive the round trip
✅ got: 3
[🧪 test] Base16 round-trips arbitrary bytevectors
[⚙️ assert] the empty bytevector round-trips
✅ got: #t
[⚙️ assert] bytes that are not valid UTF-8 survive the round trip
✅ got: #t
+----------------------------------------------+
[📑 suite] Base16 encoding
[🧪 test] decoding accepts both cases
[⚙️ assert] lowercase hex decodes
✅ got: hello
[⚙️ assert] uppercase hex decodes too
✅ got: hello
[⚙️ assert] encoding then decoding is lossless
✅ got: hello
[🧪 test] known encodings
[⚙️ assert] the empty string encodes to nothing
✅ got:
[⚙️ assert] lowercase hex digits are produced
✅ got: 68656c6c6f
[⚙️ assert] a word encodes byte per byte
✅ got: 626577616b696e67
[🧪 test] malformed input is rejected
[⚙️ assert] an odd number of digits raises
✅ success catching error: misc-error: (#f bewaking: malformed hexadecimal input (odd number of digits). () #f)
[⚙️ assert] non-hexadecimal characters raise
✅ success catching error: misc-error: (#f bewaking: malformed hexadecimal input. () #f)
[⚙️ assert] embedded whitespace raises
✅ success catching error: misc-error: (#f bewaking: malformed hexadecimal input (odd number of digits). () #f)
+----------------------------------------------+
[📑 suite] HMAC-SHA256 (RFC 4231)
[🧪 test] RFC 4231 test case 1
[⚙️ assert] a 20-byte key of 0x0b over "Hi There"
✅ got: b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7
[🧪 test] RFC 4231 test case 2
[⚙️ assert] the key "Jefe" over its test message
✅ got: 5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843
[🧪 test] the digest is 32 bytes and key-sensitive
[⚙️ assert] 64 hexadecimal digits, i.e. 32 bytes
✅ got: 64
[⚙️ assert] changing the key changes the MAC
✅ got: #f
+----------------------------------------------+
[📑 suite] Base64 encoding
[🧪 test] RFC 4648 test vectors
[⚙️ assert] empty
✅ got:
[⚙️ assert] one byte of input
✅ got: Zg==
[⚙️ assert] two bytes of input
✅ got: Zm8=
[⚙️ assert] three bytes of input
✅ got: Zm9v
[⚙️ assert] four bytes of input
✅ got: Zm9vYg==
[⚙️ assert] five bytes of input
✅ got: Zm9vYmE=
[⚙️ assert] six bytes of input
✅ got: Zm9vYmFy
[🧪 test] decoding is tolerant of line breaks
[⚙️ assert] a trailing newline is ignored
✅ got: hello
[⚙️ assert] encoding then decoding is lossless
✅ got: hello world
[🧪 test] malformed input is rejected
[⚙️ assert] non-alphabet characters raise
✅ success catching error: misc-error: (#f bewaking: malformed base64 input. () #f)
[⚙️ assert] a truncated group raises
✅ success catching error: misc-error: (#f bewaking: malformed base64 input (bad length or padding). () #f)
[⚙️ assert] data after padding raises
✅ success catching error: misc-error: (#f bewaking: malformed base64 input (data after padding). () #f)
[⚙️ assert] an impossible amount of padding raises
✅ success catching error: misc-error: (#f bewaking: malformed base64 input (bad length or padding). () #f)
+----------------------------------------------+
[📑 suite] OpenSSL-compatible sealing
[🧪 test] data produced by OpenSSL itself can be opened
[⚙️ assert] an openssl-encrypted string, random salt and all
✅ got: a secret that openssl sealed
[🧪 test] a fixed-salt vector matches OpenSSL byte for byte
[⚙️ assert] the ciphertext of a known salt and key
✅ got: U2FsdGVkX18AAQIDBAUGByAGpafcjamTQUX3eOyvQUE=
[⚙️ assert] and it decrypts back
✅ got: bewaking
[🧪 test] failures are reported rather than ignored
[⚙️ assert] the wrong key raises
✅ got: #t
[⚙️ assert] the wrong key raises for OpenSSL-produced data too
✅ got: #t
[⚙️ assert] input that is not Base64 raises
✅ got: #t
[⚙️ assert] Base64 without a Salted__ header raises
✅ got: #t
[⚙️ assert] a truncated body raises rather than returning garbage
✅ got: #t
[🧪 test] the salt must be 8 bytes
[⚙️ assert] a short salt raises
✅ success catching error: misc-error: (#f bewaking: a PBKDF2 salt must be 8 bytes long. () #f)
[🧪 test] round-trips
[⚙️ assert] the empty string round-trips
✅ got:
[⚙️ assert] a longer payload round-trips
✅ got: a secret that openssl sealed
[⚙️ assert] one padded block follows the 16-byte Salted__ header
✅ got: 32
+----------------------------------------------+
[📑 suite] Key-derivation specifications
[🧪 test] Argon2id defaults are filled in and reported
[⚙️ assert] the defaults are 64 MiB, 3 passes, 1 lane
✅ got: ((kdf . argon2id) (memory-kib . 65536) (iterations . 3) (parallelism . 1))
[⚙️ assert] given parameters are kept and the rest defaulted
✅ got: ((kdf . argon2id) (memory-kib . 262144) (iterations . 4) (parallelism . 1))
[🧪 test] the default is PBKDF2 with its fixed cost
[⚙️ assert] the module default names PBKDF2
✅ got: pbkdf2
[⚙️ assert] its iteration count is the OpenSSL one
✅ got: 10000
[🧪 test] unsupported specifications are refused
[⚙️ assert] an unknown KDF name is refused
✅ got: #t
[⚙️ assert] a string is not a KDF specification
✅ got: #t
[⚙️ assert] a PBKDF2 cost the OpenSSL format cannot record is refused
✅ got: #t
[⚙️ assert] an out-of-range Argon2id memory cost is refused
✅ got: #t
+----------------------------------------------+
[📑 suite] Randomness
[🧪 test] random-bytes returns genuinely fresh bytes
[⚙️ assert] the default length is the OpenSSL salt length
✅ got: 8
[⚙️ assert] the requested length is honoured
✅ got: 32
[⚙️ assert] two calls do not return the same bytes
✅ got: #t
[⚙️ assert] a draw is not a constant
✅ got: #f
[⚙️ assert] a draw is not all zeroes
✅ got: #t
+----------------------------------------------+
[📑 suite] Argon2id key derivation (RFC 9106)
[🧪 test] invalid parameters are rejected
[⚙️ assert] less than 8 KiB per lane is refused
✅ got: #t
[⚙️ assert] zero passes is refused
✅ got: #t
[⚙️ assert] zero lanes is refused
✅ got: #t
[⚙️ assert] a non-integer memory cost is refused
✅ got: #t
[⚙️ assert] a non-bytevector secret is refused
✅ got: #t
[⚙️ assert] this libgcrypt provides Argon2
✅ got: #t
[🧪 test] the RFC 9106 Argon2id vector is reproduced
[⚙️ assert] password 32x01, salt 16x02, secret 8x03, data 12x04, m=32, t=3, p=4
✅ got: 0d640df58d78766c08c037a34a8b53c9d01ef0452d75b65eb52520e96b01e659
❌ error - task raised an error: misc-error: (#f ~A (bewaking: gcry_kdf_final (Argon2id) failed: Unknown algorithm) #f)
+----------------------------------------------+
[📑 suite] Authenticated payloads (AES-256-GCM)
[🧪 test] the tag refuses every alteration
[⚙️ assert] a flipped ciphertext digit is refused
✅ got: #t
[⚙️ assert] a flipped tag digit is refused
✅ got: #t
[⚙️ assert] an unknown key-derivation id is refused, not guessed at
✅ got: #t
[⚙️ assert] different associated data is refused
✅ got: #t
[⚙️ assert] the wrong key is refused
✅ got: #t
[⚙️ assert] a truncated payload is refused
✅ got: #t
[🧪 test] fresh salt and nonce still round-trip
[⚙️ assert] a payload sealed with random salt and nonce opens
✅ got: #t
[⚙️ assert] and so does an Argon2id one
✅ got: #t
[⚙️ assert] associated data is optional: empty means none
✅ got: #t
[⚙️ assert] a payload sealed without it does not open with it
✅ got: #t
[🧪 test] the payload is byte for byte the reference implementation's
[⚙️ assert] PBKDF2-HMAC-SHA256, as Python's cryptography seals it
✅ got: 010707070707070707070707070707070709090909090909090909090915d0ed647041883a00a39c1a98153992e43396ca2ef78f30cf603552820a8ed81e320d38a87aee14ad4a7f41b2df7b369d6614500294e59e58
[⚙️ assert] Argon2id with its parameters recorded, likewise
✅ got: 020000200000000001000000010707070707070707070707070707070709090909090909090909090996b424d50c29974261cc56650752a5237f7528271202d70046f0bc49c8c0bfefb86150c0b946a554a1590bec6d5f03409bbff197af7d782651
[🧪 test] hexadecimal that stands for bytes decodes to bytes
[⚙️ assert] base16->bytes returns a bytevector
✅ got: #t
[⚙️ assert] and keeps the raw octets, which base16-decode cannot
✅ got: 255
❌ error - task raised an error: misc-error: (#f ~A (bewaking: gcry_kdf_final (Argon2id) failed: Unknown algorithm) #f)
+----------------------------------------------+
[📑 suite] Sealed payloads (key derivation is recorded)
[🧪 test] sizes and headers are validated
[⚙️ assert] an 8-byte Argon2id salt is refused
✅ got: #t
[⚙️ assert] a 16-byte PBKDF2 salt is refused
✅ got: #t
[⚙️ assert] a payload with no known header is refused
✅ got: #t
[🧪 test] both readers keep their contracts
[⚙️ assert] openssl-decrypt refuses what the OpenSSL CLI could not read
✅ got: #t
[⚙️ assert] kdf-decrypt opens payloads produced by OpenSSL
✅ got: #t
[🧪 test] tampering with the recorded parameters is not honoured
[⚙️ assert] a changed pass count no longer decrypts
✅ got: #t
❌ error - task raised an error: misc-error: (#f ~A (bewaking: gcry_kdf_final (Argon2id) failed: Unknown algorithm) #f)
[🧪 test] the default KDF keeps the OpenSSL layout byte for byte
[⚙️ assert] a fixed-salt PBKDF2 payload equals OpenSSL's own output
✅ got: #t
[🧪 test] an Argon2id payload round-trips and resists the wrong key
[⚙️ assert] the same key opens it
✅ got: #t
[⚙️ assert] another key does not
✅ got: #t
+----------------------------------------------+
[📑 suite] PBKDF2-HMAC-SHA256
[🧪 test] the key and IV material OpenSSL derives
[⚙️ assert] 48 bytes over 10000 iterations, as `openssl kdf' reports
✅ got: cb14e1e1f7ac2fe57f2fcaa462d4ad903867450d9778d2ccf939d8ad3cb99982e0edbd06820c0a636f6fd53d4be339fd
[🧪 test] published password/salt vectors
[⚙️ assert] one iteration
✅ got: 120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b
[⚙️ assert] two iterations
✅ got: ae4d0c95af6b46d32d0adff928f06dd02a303f8ef3c251dfd6e2d85a95474c43
[⚙️ assert] 4096 iterations
✅ got: c5e478d59288c841aa530db6845c4c8d962893a001ce4e11a4963873aa98134a
+----------------------------------------------+
[📑 suite] Authenticated payload hygiene
[🧪 test] every payload uses a fresh salt and nonce
[⚙️ assert] sealing the same plaintext twice does not repeat the payload
✅ got: #f
[⚙️ assert] the recorded salts differ, so the derived keys do too
✅ got: #f
[⚙️ assert] the recorded nonces differ as well
✅ got: #f
[🧪 test] the salt and the nonce are used, not decorative
[⚙️ assert] editing a salt digit breaks the tag
✅ got: #t
[⚙️ assert] editing a nonce digit breaks the tag
✅ got: #t
[⚙️ assert] editing an Argon2id parameter digit is refused
✅ got: #t
[🧪 test] the recorded derivation cost is bounded before it is spent
[⚙️ assert] a memory cost above the cap is refused, not allocated
✅ got: #t
[⚙️ assert] a lane count above the cap is refused
✅ got: #t
[⚙️ assert] a zero pass count is refused
✅ got: #t
[⚙️ assert] less than 8 KiB per lane is refused
✅ got: #t
[⚙️ assert] a cost inside the bounds is derived, then fails the tag: the bound is not a blanket refusal
✅ got: #t
[🧪 test] an empty plaintext is still authenticated
[⚙️ assert] the empty plaintext round-trips
✅ got: #t
[⚙️ assert] even an empty payload carries a salt, a nonce and a tag
✅ got: #t
[🧪 test] the associated data is authenticated but not stored
[⚙️ assert] the associated data does not appear in the payload
✅ got: #f
[⚙️ assert] one extra associated-data byte breaks the tag
✅ got: #t
ℹ️ info - ran 13 tasks in: 1 seconds, -171197 microseconds
❌ error -
aborting due to error in test case...
;;; note: source file ./test/veritas/unit/cli-spec.scm
;;; newer than compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/bewaking/trunk/test/veritas/unit/cli-spec.scm.go
;;; note: auto-compilation is enabled, set GUILE_AUTO_COMPILE=0
;;; or pass the --no-auto-compile argument to disable.
;;; compiling ./test/veritas/unit/cli-spec.scm
;;; note: source file ./src/bewaking/cli.scm
;;; newer than compiled /gnu/store/0w0f5c8hqiz5a524gzhhs6gakyqsjbd3-profile/lib/guile/3.0/site-ccache/bewaking/cli.go
;;; note: source file ./src/bewaking/cli.scm
;;; newer than compiled /run/current-system/profile/lib/guile/3.0/site-ccache/bewaking/cli.go
;;; note: source file ./src/bewaking/cli.scm
;;; newer than compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/bewaking/trunk/src/bewaking/cli.scm.go
;;; compiling ./src/bewaking/cli.scm
;;; note: source file ./src/bewaking/bewaking.scm
;;; newer than compiled /gnu/store/0w0f5c8hqiz5a524gzhhs6gakyqsjbd3-profile/lib/guile/3.0/site-ccache/bewaking/bewaking.go
;;; note: source file ./src/bewaking/bewaking.scm
;;; newer than compiled /run/current-system/profile/lib/guile/3.0/site-ccache/bewaking/bewaking.go
;;; note: source file ./src/bewaking/bewaking.scm
;;; newer than compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/bewaking/trunk/src/bewaking/bewaking.scm.go
;;; compiling ./src/bewaking/bewaking.scm
;;; note: source file ./src/bewaking/crypto.scm
;;; newer than compiled /gnu/store/0w0f5c8hqiz5a524gzhhs6gakyqsjbd3-profile/lib/guile/3.0/site-ccache/bewaking/crypto.go
;;; note: source file ./src/bewaking/crypto.scm
;;; newer than compiled /run/current-system/profile/lib/guile/3.0/site-ccache/bewaking/crypto.go
;;; found fresh local cache at /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/bewaking/trunk/src/bewaking/crypto.scm.go
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/bewaking/trunk/src/bewaking/bewaking.scm.go
;;; note: source file ./src/bewaking/password.scm
;;; newer than compiled /gnu/store/0w0f5c8hqiz5a524gzhhs6gakyqsjbd3-profile/lib/guile/3.0/site-ccache/bewaking/password.go
;;; note: source file ./src/bewaking/password.scm
;;; newer than compiled /run/current-system/profile/lib/guile/3.0/site-ccache/bewaking/password.go
;;; note: source file ./src/bewaking/password.scm
;;; newer than compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/bewaking/trunk/src/bewaking/password.scm.go
;;; compiling ./src/bewaking/password.scm
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/bewaking/trunk/src/bewaking/password.scm.go
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/bewaking/trunk/src/bewaking/cli.scm.go
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/bewaking/trunk/test/veritas/unit/cli-spec.scm.go
;;; note: source file ./test/veritas/unit/bewaking-spec.scm
;;; newer than compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/bewaking/trunk/test/veritas/unit/bewaking-spec.scm.go
;;; compiling ./test/veritas/unit/bewaking-spec.scm
;;; compiled /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/bewaking/trunk/test/veritas/unit/bewaking-spec.scm.go
--------------------------------------------------------------
Maak 0.14.0: the infinitely extensible command runner
Loading tasks from Maak file: /etc/byggsteg/job-clone/bewaking/trunk/maak.scm
Executing Maak tasks: test
--------------------------------------------------------------
=> Running Maak task: test
Run the project's tests.
Execute command:
guix time-machine --channels=channels.scm -- shell -m manifest.scm -- guile -L ./src -L ./test -c '((@ (veritas runner) run-tests))'
Executing: guix time-machine --channels=channels.scm -- shell -m manifest.scm -- guile -L ./src -L ./test -c '((@ (veritas runner) run-tests))'
maak: error: command failed (exit code 1): guix time-machine --channels=channels.scm -- shell -m manifest.scm -- guile -L ./src -L ./test -c '((@ (veritas runner) run-tests))'
hint: Fix the command above, or run `maak --dry-run' to inspect it first.