bewaking-74d24fe3-7e17

job succeeded

starting new job...
rundir='/etc/byggsteg/job-clone//bewaking/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 9749454 to 24e6407
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 succeeded15 lines · 0.0s
commit 24e64079cec96692f804ab9fecc42d80115cdcaa
Author: Josep Bigorra <jjbigorra@gmail.com>
Date:   Tue Nov 10 20:59:18 2026 +0100

    chore: Release v0.5.5
    
    The funding file now carries a custom funding URL that points at Buy Me a
    Coffee (buymeacoffee.com/jjbigorra), so GitHub shows a Sponsor button on
    the repository.
    
    Validation: maak test — the veritas suite finished, all assertions passed.
    
    🤖 Generated with ECA (deepseek/deepseek-flash)
    
    Co-Authored-By: eca-agent <git@eca.dev>

M       .github/FUNDING.yml
testmaak test succeeded1214 lines · 8.0s
scanning directories for unit tests...
scanning present working directory for unit tests...

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

found unit tests: (bewaking-spec.scm crypto-spec.scm cli-spec.scm web-spec.scm password-spec.scm)


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


ℹ️ info - shuffled with seed 571422038 (replay with VERITAS_SEED=571422038)
+----------------------------------------------+
[📑 suite] Sealed envelope (versioned, authenticated)
  [🧪 test] tokens and objects are envelopes and reject tampering
    [⚙️ 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] key rotation with a keyring
    [⚙️ assert] a keyring seals and opens by key id
    ✅ got: #t
  [🧪 test] no reader is left for the earlier formats
    [⚙️ 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] the requested key id is embedded in generated tokens
    [⚙️ assert] the token carries the key id
    ✅ got: #t
  [🧪 test] data that is not a BWK3 envelope is refused
    [⚙️ 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] 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] 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] 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] Payload primitives
  [🧪 test] token-payload-valid?
    [⚙️ assert] a decrypted token payload is valid
    ✅ got: #t
    [⚙️ assert] a missing token type is invalid
    ✅ got: #f
    [⚙️ assert] an empty payload is invalid
    ✅ got: #f
  [🧪 test] unsafe-generate-token
    [⚙️ assert] incomplete owner details raise instead of returning #f
    ✅ success catching error: misc-error: (#f Could not generate token from given payload! () #f)
    [⚙️ assert] valid details produce a token string
    ✅ got: #t
  [🧪 test] encrypt-payload and decrypt-payload
    [⚙️ assert] a payload round-trips under the same key
    ✅ got: mysecretdata
    [⚙️ assert] a supplied salt makes the output reproducible
    ✅ got: U2FsdGVkX18BAQEBAQEBAbwE/xss9SSpZh5gLj72hSU=
    [⚙️ assert] a different key does not open the payload
    ✅ got: #t
+----------------------------------------------+
[📑 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.1794340808.-.01cb223273ca5d26ace3d723614598abe590439e90a035ea09384b73f0a2e1ac96dee013ad83cf4947bd676ce8c0a3d2a394a3abac2ad9b80df26fd7ba0041fc8d083a6777a3afcdf038fe657c2b8245358bb3695bd709f5e60d1489bdd003e24aaa989719546ff5ebe65c53420ce744c21a743293b5f894c32b8fb190ecfd115715ad4778a15d4b1ee7725430707e589b37bad8703026fd96fc803b0e241147d089eee0fb0214c61e4146d2a57d2e2271be06e8cbaa8cb8a5553bef133d92f353edf60b8305f205b0149531a85924d4d072f7d0825272de39993351eb2c31ce2fc0f036abb281e8f118a165666f60c0d7ab57d2082d07a0b408813ece09c4c448246504b6b092c76079a9415a8e5817a6ba2aa5c8e790ad74cd2d682a2ed3f7a031ffe81bf6dc34209e09b8c95d5503eed16396223b1171037548e66e7d548819a85fd40407d90aff81fc5660e3c470238aefd7e8a178d5aba45f57d1afdbe0cd642cddfc1c1fe266fe5ba3a03cf679c13b82e07e5b2e6541ef7bbb1edb48367794b223ac426f967e2b5cffec811d6e9fabed29b90a57a447c9e018219f5be98f407cae31ed7ec58596e70def267cad5865c223ae700878ff5091eabb3199e460cc28d2a355e8119322ecd7e21bcf0ba9fcd52ed5a1f7724413fd41260c1b808074e4027d5ad4d254b0a219359b854b15e6611fc84ca5c42e477aa0286a370d694c956f01974e2d46493e3b1f2b20e3f706019b3113806c8ce3716f3771f7a7452ef900a1b3d0eeef209a21e903b54e8972381c9fda240c3cdd1c8493228b420c8913017b9afbf41d1fe08bff3ddd6a171c33e3da61caa2054b03037d76903218875e3394aba4f0776de297f4725db570be82fdff162cae8cac217798ac63ac1e536637f056f6dde287aeb921aceed043d5f3eb7915a19a1250f79704f1dfcfa833a2b79aad8c3ff18c6fb352b01afef3ab510f38ef72886849f4c0b210cd7d3d960f1cc9715f47ddfb95cb85afcf25414c9e6c80daa4bb9dcc3dce0de1bf8c380b0e5b59a737c15132f3b502adfbfe84b9c2ac2a8efe25925aca983ea6ea833e60ff9ae4d11a5bbd184dc92741b066baeb611d178d773d554eca6f914320c6e8b6ee21d120f7505b939c34101e3d7c613dff2251cf0306a297dfdd8df9e574decf42907269b30989efcc8b319d5969296f9bca07582daf12adbf097314a4dc0d441243637a0ba80abd295fd59c2973da27d686539c759d3e052982f30ef37df61004c4e1ce11dcc4e3c18e59912e02866f3bf0aa0df1d51e

decoded: ((token-type . bewaking-token) (token-id . 52b575d0-4bcf-4fab-b956-f73cc4c2b664) (token-created-at . 1794340808) (token-expires-at . 1794945608) (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] 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.1794340808.-.0133e423ae7fb843dc4f7137ff2e5b5d3d4ef5ff65bc47896b3925103e007e4944cd308210072ad51f008a6fb867d14e10ccda90803551c8618044edd09f6f7cbebdd5913393e76b9335b1be7080568e6badf565e49bc38b0492d152f80b0476d87e1687b295dc5abd994637ba51afdc131ced555cc32fe17022409c8aef94f5df006ea9ec2cb1756297a38961370c4a2bd9bc16c43ee773321e80c891f9509a9b8869f1b615bf3eb9f0473dfe6d91a5890e0c206af0384d2c553d702015877897858ecb685c6b1ec035f087add4ba4bb6858d2322a336b0982bd7407f3b84dc14a31dde91772f28d6b6ac300a919bbf0367f0d0b44989cd6068790369796aeb39d83311d7e1a4e1a18389218d287665683ac5cda1bc032531b8c2ec3a81e10fe87976d36a57be8d6e19f12b41f0d4c76162969ff89c7a2717c059d310954d5c601bf7e78bc0ba3dc2e28d0730ccf0530d7f542934445ca29d3dade5b135fa27e9db6604dd132ff26a5755746c7f5146c5b398657de7192bb1fb6353a43650d3446b2cc4ca5c2b848db3b97280e8546d6aa3f7fd6d217ab4c88ef5f4f65e213574679808cd8290570f30c6e8fada83bb780141f01d16d12a939dcc71cb05e4c0c4ecebf05d3a23e2ea1c03e7ace4bd6e31a6fd0d9802368aacd8f80d9448e706d036357dd2a45ab4a9ab5d6e133335ccea6a730dea7b74da0038265f99ccdf9f6e14ab550ad9aca457dbf029a18d8576ac716705314eb89a81bb2c5c03de7144993a46efe51fca90bc11a6c8cb3a3820312d73754b8a6176f1aab18a69aa567aa3def6c78526e12f2c26dd0ff4e50812adb47edf37b3999117bd8ffba5af538b3ee9b3c4c71f2cc64d5392d6ce32a3533a8572eaa71c197f675ac53b44346b5af5f816862bac99f14937b6dc8f32b44e83a0bae30895a47124a38a7331ed08115318d8ff8c3e54b04ceb674b5567ee3b09c986933b6a57d228418d0af65b4def30db36df92ca38d0325505031808dd11ee4b629623643f1c3dc74bf7523994b6c80df8c0b0dcaca1b2ad05de685fcdf67e818970b7c754336c017646c5258624a18e7653d9f64938ee4f5b72f3f076d45980696a80797ec6f47ec145ac1cf50592417c42991e7eedf7e8327aba18435905904c7125a2d0aa8726edcdf45fbe1588eb03ce70140c2ab8237ad3c6f13ea34598c517a53863a961e64e9ce6b85abf7c0a1195df94fec6aa7ac3adb96f1a4b33439c37376a940e2e2487ef43091c9132f9701d7717f656d839d55c2e8797bd7648b0c07668ec2d6d

    ✅ got: #t
+----------------------------------------------+
[📑 suite] Envelope metadata (key id, created-at, expiry)
  [🧪 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] 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] 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] 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] 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] expired tokens and objects are refused
    [⚙️ 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] 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] Deterministic sealing and diagnostics
  [🧪 test] an explicit clock drives the age policy
    [⚙️ assert] max-age-seconds is measured against the given clock
    ✅ got: #t
  [🧪 test] a failure is reported on standard error
    [⚙️ assert] a failing decrypt-token prints nothing on standard output
    ✅ got: 
    [⚙️ assert] its diagnosis is written to standard error
    ✅ got: #t
  [🧪 test] a fixed clock and random source make sealing reproducible
    [⚙️ assert] two envelopes sealed the same way are identical
    ✅ got: BWK3.default.1800000000.-.01000000000000000000000000000000000000000000000000000000009da1750a6251b3b6c6b6ba54dbac33b0125ee5be5eb41ba79b5ffade9baf14f12d6a300e0976b3ed8fafb1c425514c2c5a76d0bea37f42cf3c826054
    [⚙️ assert] two tokens generated the same way are identical
    ✅ got: BWK3.default.1800000000.-.01000000000000000000000000000000000000000000000000000000009d8f043c7c6f8e8ae2b1837fbcbf03ce3c44d9bf22a7059e992bd2a9b9b805f91d2046061577adbb9bdcdae0799c99928222bf8b58c4f0d1d649cd180ac6161d169d7fd3784605f7fa5aa62f3fa03df80b6883c6fe8536d72d645111c902842637d281224b99702789b051d292b5f60989572576c0700007b9331fd6087f08731644a0252a70b785c94271b8c1def0178d24f612cfa21b54fe644d4d9f6d23c3959162eb2a0df9fc6daf110e677b7a1a219804488fbb5afb1f3707b63ed09c36d485c3b93bea585d4495ebf6c97b6a4276f4365145aadf305c67fe3b107abbbe756be6859ac321bb55cb4441063f2a1e2ee92dbec7cf943cf49d55a2f376bec33028a94a129765ce6aace2254fdb2d65382757c938bfe81bcba8cdaf4589ae4da4241c7db2cdd4d81ae139e7eaee4a273a28cc03f2251616c90e90cff7426bc3c62d042a21b45225d094a73c1da8c857c40373832cda1416244f7527fa8635fa36ea1eadd86c490db1c2731a738baf676fdead89cbeb3eff85d32fdd840dac65f9c82df6ddf035da266d2f0b1106da6cb05b8dcb2cfef6b1ca1334650e2ecef032a00dc3dd9ee419641260132af7d0f627adc1df35a174cff2d803a746f18622bf5e7a5ab68902346982d7489907c1a918402da33fb92bea0b53ed6575b813d280ab59e508d71c4d6481113ed807fdd7b9832be8d8e794068aa8dc9c
    [⚙️ assert] the envelope header records the supplied clock
    ✅ got: 1800000000
    [⚙️ assert] the supplied token id is the one read back
    ✅ got: 123e4567-e89b-12d3-a456-426655440000
    [⚙️ assert] the supplied clock is the payload's creation time
    ✅ got: 1800000000
+----------------------------------------------+
[📑 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] Garbling and Ungarbling
  [🧪 test] garble-ungarble
    ✅ got: dlrow
    ✅ got: lrow
    ✅ got: row
    ✅ got: ow
    ✅ got: w
    ✅ got: dlrow olleh
    ✅ got: hello world
    ✅ got: 7myludvsci59ixumnw54l5v5m81512ap788fe3c5e545e19wu0f2baffrij92ku3zfcm7d9gulk6vqtifiue6x49ej4ncjvuhnkmvx2r4g2106jl80l95s5hvn1f3103
+----------------------------------------------+
[📑 suite] Decryption
  [🧪 test] decrypt-token
    [⚙️ 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] 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-object
    [⚙️ 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)
ℹ️ info - ran 12 tasks in: 2 seconds, 612007 microseconds

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


ℹ️ info - shuffled with seed 571422038 (replay with VERITAS_SEED=571422038)
+----------------------------------------------+
[📑 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
  [🧪 test] every parameter changes the derived bytes
    [⚙️ assert] a different pass count gives a different tag
    ✅ got: #f
    [⚙️ assert] a different salt gives a different tag
    ✅ got: #f
    [⚙️ assert] a different passphrase gives a different tag
    ✅ got: #f
+----------------------------------------------+
[📑 suite] Sealed payloads (key derivation is recorded)
  [🧪 test] tampering with the recorded parameters is not honoured
    [⚙️ assert] a changed pass count no longer decrypts
    ✅ got: #t
  [🧪 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] 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] an Argon2id payload records its derivation and parameters
    [⚙️ assert] the header names the derivation
    ✅ got: Argon2id
    [⚙️ assert] the memory cost is recorded, in KiB
    ✅ got: 8192
    [⚙️ assert] the pass count is recorded
    ✅ got: 1
    [⚙️ assert] the lane count is recorded
    ✅ got: 1
  [🧪 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] Key derivation salt lengths
  [🧪 test] kdf-salt-length
    [⚙️ assert] the OpenSSL-compatible layout uses an 8-byte salt
    ✅ got: 8
    [⚙️ assert] the Argon2id layout uses a 16-byte salt
    ✅ got: 16
+----------------------------------------------+
[📑 suite] Error handling helper
  [🧪 test] call-with-error-handler
    [⚙️ assert] a successful thunk's value is returned unchanged
    ✅ got: 42
    [⚙️ assert] an ordinary error is handed to the handler as a message
    ✅ got: boom
    [⚙️ assert] a signal is rethrown, so Ctrl-C keeps working
    ✅ got: 2
    [⚙️ assert] a quit is rethrown, so exit codes survive the helper
    ✅ got: rethrown
+----------------------------------------------+
[📑 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] HMAC-SHA256 (RFC 4231)
  [🧪 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
  [🧪 test] RFC 4231 test case 1
    [⚙️ assert] a 20-byte key of 0x0b over "Hi There"
    ✅ got: b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7
+----------------------------------------------+
[📑 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] Base16 encoding
  [🧪 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] 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] 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] Authenticated payload hygiene
  [🧪 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 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] 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] 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 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
+----------------------------------------------+
[📑 suite] Constant-time comparison
  [🧪 test] constant-time-equal?
    [⚙️ assert] equal strings compare equal
    ✅ got: #t
    [⚙️ assert] one differing byte is not equal
    ✅ got: #f
    [⚙️ assert] different lengths are not equal
    ✅ got: #f
    [⚙️ assert] equal bytevectors compare equal
    ✅ got: #t
    [⚙️ assert] a difference in the last byte is still found
    ✅ got: #f
    [⚙️ assert] values that are neither strings nor bytevectors are refused
    ✅ success catching error: misc-error: (#f bewaking: constant-time-equal? compares two strings or two bytevectors. () #f)
+----------------------------------------------+
[📑 suite] OpenSSL-compatible sealing
  [🧪 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] 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] data produced by OpenSSL itself can be opened
    [⚙️ assert] an openssl-encrypted string, random salt and all
    ✅ got: a secret that openssl sealed
  [🧪 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] 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
  [🧪 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] 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))
+----------------------------------------------+
[📑 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] Base64 encoding
  [🧪 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)
  [🧪 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
+----------------------------------------------+
[📑 suite] Format constants
  [🧪 test] layout lengths and Argon2id defaults
    [⚙️ assert] the GCM outer layer uses a 16-byte salt
    ✅ got: 16
    [⚙️ assert] the GCM nonce is 12 bytes
    ✅ got: 12
    [⚙️ assert] PBKDF2 keeps the OpenSSL 8-byte salt
    ✅ got: 8
    [⚙️ assert] the default Argon2id spec names its derivation
    ✅ got: argon2id
    [⚙️ assert] the default memory cost is recorded
    ✅ got: 65536
    [⚙️ assert] the default pass count is recorded
    ✅ got: 3
    [⚙️ assert] the default lane count is recorded
    ✅ got: 1
    ✅ got: 65536
    ✅ got: 3
    ✅ got: 1
    [⚙️ assert] argon2id-available? always answers, never raises
    ✅ got: #t
+----------------------------------------------+
[📑 suite] Error messages
  [🧪 test] a diagnostic is the error's own message
    [⚙️ assert] the message's format arguments are applied
    ✅ got: failed: 42
    [⚙️ assert] a message without arguments is returned as is
    ✅ got: boom
    [⚙️ assert] a plain string argument is used when there is no data list
    ✅ got: custom
+----------------------------------------------+
[📑 suite] Encodings of raw bytes
  [🧪 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
  [🧪 test] Base64 round-trips arbitrary bytevectors
    [⚙️ assert] bytes that are not valid UTF-8 survive the round trip
    ✅ got: 3
+----------------------------------------------+
[📑 suite] Authenticated payloads (AES-256-GCM)
  [🧪 test] the payload is byte for byte the reference implementation's;;; note: source file ./src/bewaking/cli.scm
;;;       newer than compiled /gnu/store/21afi1i81zcsj3g9fjxxaxrkfxm3gfjp-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
;;; found fresh local cache at /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/bewaking/trunk/src/bewaking/cli.scm.go
;;; note: source file ./src/bewaking/crypto.scm
;;;       newer than compiled /gnu/store/21afi1i81zcsj3g9fjxxaxrkfxm3gfjp-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
;;; note: source file ./src/bewaking/bewaking.scm
;;;       newer than compiled /gnu/store/21afi1i81zcsj3g9fjxxaxrkfxm3gfjp-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
;;; found fresh local cache at /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/21afi1i81zcsj3g9fjxxaxrkfxm3gfjp-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
;;; found fresh local cache at /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/bewaking/trunk/src/bewaking/password.scm.go
;;; note: source file ./src/bewaking/cli.scm
;;;       newer than compiled /gnu/store/21afi1i81zcsj3g9fjxxaxrkfxm3gfjp-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
;;; found fresh local cache at /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/bewaking/trunk/src/bewaking/cli.scm.go
;;; note: source file ./src/bewaking/crypto.scm
;;;       newer than compiled /gnu/store/21afi1i81zcsj3g9fjxxaxrkfxm3gfjp-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
;;; note: source file ./src/bewaking/bewaking.scm
;;;       newer than compiled /gnu/store/21afi1i81zcsj3g9fjxxaxrkfxm3gfjp-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
;;; found fresh local cache at /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/21afi1i81zcsj3g9fjxxaxrkfxm3gfjp-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
;;; found fresh local cache at /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/bewaking/trunk/src/bewaking/password.scm.go

    [⚙️ assert] PBKDF2-HMAC-SHA256, as Python's cryptography seals it
    ✅ got: 010707070707070707070707070707070709090909090909090909090915d0ed647041883a00a39c1a98153992e43396ca2ef78f30cf603552820a8ed81e320d38a87aee14ad4a7f41b2df7b369d6614500294e59e58
    [⚙️ assert] Argon2id with its parameters recorded, likewise
    ✅ got: 020000200000000001000000010707070707070707070707070707070709090909090909090909090996b424d50c29974261cc56650752a5237f7528271202d70046f0bc49c8c0bfefb86150c0b946a554a1590bec6d5f03409bbff197af7d782651
  [🧪 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 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] the payload records which derivation to use
    [⚙️ assert] PBKDF2 is identified by its own id
    ✅ got: 1
    [⚙️ assert] Argon2id is identified by a different id
    ✅ got: 2
    [⚙️ assert] the memory cost is recorded, in KiB
    ✅ got: 8192
    [⚙️ assert] the pass count is recorded
    ✅ got: 1
    [⚙️ assert] the lane count is recorded
    ✅ got: 1
  [🧪 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
ℹ️ info - ran 18 tasks in: 1 seconds, -309572 microseconds

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


ℹ️ info - shuffled with seed 571422038 (replay with VERITAS_SEED=571422038)
+----------------------------------------------+
[📑 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 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 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 key derivation and dropped formats
  [🧪 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] 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] 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
+----------------------------------------------+
[📑 suite] CLI token commands
  [🧪 test] token round-trip
    [⚙️ assert] generate then decrypt preserves the payload
    ✅ got: #t
  [🧪 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
+----------------------------------------------+
[📑 suite] CLI password hashing
  [🧪 test] password hash and verify
    [⚙️ assert] the command prints a PHC Argon2id hash
    ✅ got: #t
    [⚙️ assert] the same password verifies with exit code 0
    ✅ got: #t
    [⚙️ assert] a wrong password exits with 1
    ✅ got: #t
    [⚙️ assert] a failed verification writes nothing to standard output
    ✅ got: 
    [⚙️ assert] a malformed hash exits with 1
    ✅ got: 1
+----------------------------------------------+
[📑 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 surface
  [🧪 test] help, parallel Argon2id and environment keys
    [⚙️ assert] --help prints the usage text and exits 0
    ✅ got: #t
    [⚙️ assert] --kdf-parallelism seals and opens an Argon2id token
    ✅ got: #t
    [⚙️ assert] BEWAKING_ENCRYPTION_KEY and BEWAKING_SIGNING_KEY are picked up
    ✅ got: #t
ℹ️ info - ran 8 tasks in: 2 seconds, 203131 microseconds

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


ℹ️ info - shuffled with seed 571422038 (replay with VERITAS_SEED=571422038)
+----------------------------------------------+
[📑 suite] Web chapter: sign-in seals a session into a cookie
  [🧪 test] bad credentials get no cookie at all
    [⚙️ assert] a wrong password is a plain 401
    ✅ got: 401
    [⚙️ assert] no session is created for a failed sign-in
    ✅ got: #f
    [⚙️ assert] the failure is reported, not swallowed
    ✅ got: invalid credentials
  [🧪 test] the cookie lifetime can be aligned with the token's
    [⚙️ assert] the requested lifetime is recorded in the cookie
    ✅ got: 86400
    [⚙️ assert] the envelope is sealed now
    ✅ got: #t
    [⚙️ assert] the payload lifetime is separate from the cookie's
    ✅ got: #f
  [🧪 test] a verified user gets a sealed session cookie
    [⚙️ assert] sign-in redirects, as the README's controller does
    ✅ got: 302
    [⚙️ assert] the redirect target is the dashboard
    ✅ got: /dashboard
    [⚙️ assert] the cookie value is a BWK3 envelope
    ✅ got: #t
    [⚙️ assert] the cookie is named as documented
    ✅ got: x-bewaking-session
    [⚙️ assert] (a) HttpOnly puts the cookie out of reach of JavaScript
    ✅ got: #t
    [⚙️ assert] (b) Secure keeps it off plain HTTP
    ✅ got: #t
    [⚙️ assert] (c) path "/" applies the cookie to the whole site
    ✅ got: /
    [⚙️ assert] (d) the cookie's lifetime matches #:token-validity-seconds
    ✅ got: 604800
+----------------------------------------------+
[📑 suite] Web chapter: sealed objects as stateless state
  [🧪 test] a cursor or half-finished form can be sealed the same way
    [⚙️ assert] a sealed cursor round-trips
    ✅ got: page-3
    [⚙️ assert] a client cannot page anywhere it was not sent
    ✅ got: #t
  [🧪 test] an edited link is worth nothing
    [⚙️ assert] the link was edited in the mail client
    ✅ got: #f
    [⚙️ assert] and garbage is not a link either
    ✅ got: #f
  [🧪 test] the link answers only to the key that sealed it
    [⚙️ assert] a link sealed elsewhere does not open here
    ✅ got: #f
  [🧪 test] a password-reset link round-trips with no table row
    [⚙️ assert] the link is a sealed envelope
    ✅ got: #t
    [⚙️ assert] the state comes back as an ordinary alist
    ✅ got: ((user . u-1) (action . reset-password))
    [⚙️ assert] the user id is not readable in the link
    ✅ got: #f
    [⚙️ assert] the hour-long lifetime is recorded in the header
    ✅ got: #t
  [🧪 test] an expired link is refused by the server, not by the client
    [⚙️ assert] before the expiry the link opens
    ✅ got: ((user . u-1) (action . reset-password))
    [⚙️ assert] after the expiry the server refuses it
    ✅ got: #f
    [⚙️ assert] and the header says so, for the log
    ✅ got: #t
+----------------------------------------------+
[📑 suite] Web chapter: protecting a route
  [🧪 test] every failure mode is a refusal, never an admission
    [⚙️ assert] a request with no cookie is redirected to /login
    ✅ got: 302
    [⚙️ assert] an empty cookie is refused
    ✅ got: 302
    [⚙️ assert] a tampered cookie is refused
    ✅ got: 302
    [⚙️ assert] a cookie that is not a real envelope is refused
    ✅ got: 302
    [⚙️ assert] arbitrary text is refused
    ✅ got: 302
    [⚙️ assert] a second user's genuine session is admitted, as that user
    ✅ got: 200
    [⚙️ assert] and it admits the *second* user, not the first
    ✅ got: u-2
    [⚙️ assert] a keyring that cannot resolve the key id is refused
    ✅ got: 302
  [🧪 test] the payload is the identity, not the request
    [⚙️ assert] the owner id comes from the sealed payload
    ✅ got: u-1
    [⚙️ assert] a client-supplied user id is a hint, never the identity
    ✅ got: #f
  [🧪 test] an expired session is refused, and sign-out clears the cookie
    [⚙️ assert] a token whose payload lifetime has passed is refused
    ✅ got: 302
    [⚙️ assert] and the helper returns #f for it
    ✅ got: #f
    [⚙️ assert] sign-out redirects to the login page
    ✅ got: 302
    [⚙️ assert] sign-out deletes the cookie, the only cleanup there is
    ✅ got: 
    [⚙️ assert] the deletion cookie expires immediately
    ✅ got: 0
  [🧪 test] the same session works as an API bearer token
    [⚙️ assert] the API route admits the header token
    ✅ got: 200
    [⚙️ assert] the token identifies the caller without a database round-trip
    ✅ got: u-1
    [⚙️ assert] the granted permissions are what the token says
    ✅ got: (read-only access-files)
  [🧪 test] a permission that was never granted is refused on the server
    [⚙️ assert] the browser route refuses a route the user may not use
    ✅ got: 302
    [⚙️ assert] the API route answers 401 rather than 403-by-omission
    ✅ got: 401
    [⚙️ assert] the helper returns #f, not a partially authorized payload
    ✅ got: #f
    [⚙️ assert] the permission the user does have is enough
    ✅ got: #t
    [⚙️ assert] a route needing nothing is open to a valid session
    ✅ got: #t
  [🧪 test] the signed-in user is admitted
    [⚙️ assert] the browser route runs the handler
    ✅ got: 200
    [⚙️ assert] the handler sees the owner id from the payload
    ✅ got: u-1
    [⚙️ assert] the thunk's 'email' came back as 'owner-email'
    ✅ got: joe@example.com
    [⚙️ assert] and 'name' came back as 'owner-name'
    ✅ got: Joe
    [⚙️ assert] permissions travel inside the token
    ✅ got: (read-only access-files)
    [⚙️ assert] the payload carries its own token id
    ✅ got: #t
    [⚙️ assert] and its own payload expiry
    ✅ got: #t
+----------------------------------------------+
[📑 suite] Web chapter: auditing what is safe to log
  [🧪 test] an expiry is reported as a time, and its absence as nothing
    [⚙️ assert] the audit reports no envelope expiry by default
    ✅ got: #f
    [⚙️ assert] a token sealed with a TTL records its expiry
    ✅ got: #t
    [⚙️ assert] and the audit helper agrees it is live
    ✅ got: #f
  [🧪 test] auditing garbage is #f, not an exception
    [⚙️ assert] an unparseable cookie yields no metadata
    ✅ got: #f
    [⚙️ assert] a missing cookie yields no metadata
    ✅ got: #f
    [⚙️ assert] a malformed BWK3 value yields no metadata
    ✅ got: #f
  [🧪 test] the envelope header is readable with no keys at all
    [⚙️ assert] the version is reported for the log
    ✅ got: BWK3
    [⚙️ assert] the key id is reported, which is what rotation is keyed on
    ✅ got: default
    [⚙️ assert] the creation time is reported
    ✅ got: #t
    [⚙️ assert] a fresh session is not expired
    ✅ got: #f
    [⚙️ assert] the audit output carries no key material
    ✅ got: #f
    [⚙️ assert] not the encryption key and not the signing key either
    ✅ got: #f
+----------------------------------------------+
[📑 suite] Web chapter: keys, rotation and revocation
  [🧪 test] keys stay secret and distinct, which is the whole requirement
    [⚙️ assert] the encryption key does not appear in the token
    ✅ got: #f
    [⚙️ assert] nor does the signing key
    ✅ got: #f
    [⚙️ assert] swapping the two keys does not open the session
    ✅ got: #f
  [🧪 test] rotation needs no downtime and no forced logout
    [⚙️ assert] the key id is embedded in the envelope
    ✅ got: #t
    [⚙️ assert] new logins are sealed with the current key
    ✅ got: #t
    [⚙️ assert] an old token still opens: nothing to migrate
    ✅ got: #t
    [⚙️ assert] and so does a new one
    ✅ got: #t
  [🧪 test] retiring a key logs every session sealed with it out at once
    [⚙️ assert] the retired key id stops opening sessions
    ✅ got: #f
    [⚙️ assert] the current key keeps working
    ✅ got: #t
ℹ️ info - ran 5 tasks in: 1 seconds, 84934 microseconds

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


ℹ️ info - shuffled with seed 571422038 (replay with VERITAS_SEED=571422038)
+----------------------------------------------+
[📑 suite] Password character classes
  [🧪 test] password-character-classes
    [⚙️ assert] lowercase letters only
    ✅ got: (lowercase)
    [⚙️ assert] uppercase letters only
    ✅ got: (uppercase)
    [⚙️ assert] digits only
    ✅ got: (digit)
    [⚙️ assert] all four classes are detected
    ✅ got: (lowercase uppercase digit symbol)
    [⚙️ assert] an empty password has no classes
    ✅ got: ()
+----------------------------------------------+
[📑 suite] Password strength
  [🧪 test] strong-password?
    [⚙️ assert] a common word is not strong
    ✅ got: #f
    [⚙️ assert] a short mixed password is not strong
    ✅ got: #f
    [⚙️ assert] a long passphrase is strong
    ✅ got: #t
    [⚙️ assert] min-score lowers the bar
    ✅ got: #t
  [🧪 test] password-strength
    [⚙️ assert] a common numeric password scores 0
    ✅ got: 0
    [⚙️ assert] score 0 is labelled very weak
    ✅ got: very weak
    [⚙️ assert] a very short password scores 0
    ✅ got: 0
    [⚙️ assert] a long passphrase scores 4
    ✅ got: 4
    [⚙️ assert] score 4 is labelled very strong
    ✅ got: very strong
    [⚙️ assert] an empty password scores 0
    ✅ got: 0
    [⚙️ assert] stronger passwords have higher entropy
    ✅ got: #t
    [⚙️ assert] feedback is returned as a list
    ✅ got: #t
    [⚙️ assert] common passwords get a specific warning
    ✅ got: #t
    [⚙️ assert] sequential runs are flagged in the feedback
    ✅ got: #t
+----------------------------------------------+
[📑 suite] Password hashing
  [🧪 test] hash-password and verify-password
    [⚙️ assert] a hash is a well-formed PHC Argon2id string
    ✅ got: #t
    [⚙️ assert] the requested parameters are the ones recorded
    ✅ got: #t
    [⚙️ assert] the right password verifies
    ✅ got: #t
    [⚙️ assert] a wrong password does not verify
    ✅ got: #f
    [⚙️ assert] plain text is not a password hash
    ✅ got: #f
    [⚙️ assert] a malformed hash verifies nothing
    ✅ got: #f
    [⚙️ assert] the empty string is not a password hash
    ✅ got: #f
  [🧪 test] salts and cost ceilings
    [⚙️ assert] two hashes of one password differ, so each carries a fresh salt
    ✅ got: #f
    [⚙️ assert] a recorded memory cost above the ceiling is refused
    ✅ got: #f
    [⚙️ assert] a recorded pass count above the ceiling is refused
    ✅ got: #f
+----------------------------------------------+
[📑 suite] Password entropy
  [🧪 test] password-entropy
    [⚙️ assert] an empty password has zero entropy
    ✅ got: 0
    [⚙️ assert] eight lowercase letters are ~37.6 bits
    ✅ got: #t
    [⚙️ assert] adding a character class increases the entropy
    ✅ got: #t
+----------------------------------------------+
[📑 suite] Strength labels
  [🧪 test] password-strength-label
    ✅ got: very weak
    ✅ got: weak
    ✅ got: fair
    ✅ got: strong
    ✅ got: very strong;;; note: source file ./src/bewaking/bewaking.scm
;;;       newer than compiled /gnu/store/21afi1i81zcsj3g9fjxxaxrkfxm3gfjp-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
;;; found fresh local cache at /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/crypto.scm
;;;       newer than compiled /gnu/store/21afi1i81zcsj3g9fjxxaxrkfxm3gfjp-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
error decrypting token: bewaking: the sealed data could not be opened: unknown, retired or wrong key, or the data was altered.
error decrypting object: bewaking: the sealed data could not be opened: unknown, retired or wrong key, or the data was altered.
error decrypting object: bewaking: the sealed data could not be opened: unknown, retired or wrong key, or the data was altered.
error decrypting object: bewaking: the sealed data could not be opened: unknown, retired or wrong key, or the data was altered.
error decrypting token: open-envelope: not a bewaking envelope (expected BWK3).
error decrypting object: open-envelope: not a bewaking envelope (expected BWK3).
error decrypting token: open-envelope: not a bewaking envelope (expected BWK3).
error decrypting object: open-envelope: not a bewaking envelope (expected BWK3).
error decrypting token: open-envelope: the sealed data expired at 1000 (key 'default').
error decrypting object: open-envelope: the sealed data expired at 1000 (key 'default').
error decrypting token: bewaking: the sealed data could not be opened: unknown, retired or wrong key, or the data was altered.
error decrypting token: open-envelope: not a bewaking envelope (expected BWK3).
error decrypting object: bewaking: the sealed data could not be opened: unknown, retired or wrong key, or the data was altered.
;;; note: source file ./src/bewaking/cli.scm
;;;       newer than compiled /gnu/store/21afi1i81zcsj3g9fjxxaxrkfxm3gfjp-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
;;; found fresh local cache at /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/bewaking/trunk/src/bewaking/cli.scm.go
;;; note: source file ./src/bewaking/password.scm
;;;       newer than compiled /gnu/store/21afi1i81zcsj3g9fjxxaxrkfxm3gfjp-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
;;; found fresh local cache at /root/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/bewaking/trunk/src/bewaking/password.scm.go
error decrypting object: bewaking: the sealed data could not be opened: unknown, retired or wrong key, or the data was altered.
error decrypting token: bewaking: the sealed data could not be opened: unknown, retired or wrong key, or the data was altered.
error decrypting token: bewaking: malformed BWK3 envelope (created-at)
error decrypting token: open-envelope: not a bewaking envelope (expected BWK3).
error decrypting token: bewaking: the sealed data could not be opened: unknown, retired or wrong key, or the data was altered.
error decrypting token: bewaking: the sealed data could not be opened: unknown, retired or wrong key, or the data was altered.
error decrypting token: bewaking: the sealed data could not be opened: unknown, retired or wrong key, or the data was altered.

    [⚙️ assert] a score outside 0-4 is labelled unknown
    ✅ got: unknown
+----------------------------------------------+
[📑 suite] Password generation
  [🧪 test] generate-strong-password
    [⚙️ assert] default length is 24
    ✅ got: 24
    [⚙️ assert] a generated password is strong
    ✅ got: #t
    [⚙️ assert] default generated passwords use every class
    ✅ got: (lowercase uppercase digit symbol)
    [⚙️ assert] length is respected
    ✅ got: 32
    [⚙️ assert] symbols can be excluded
    ✅ got: (lowercase uppercase digit)
    [⚙️ assert] too-short a request raises an error
    ✅ success catching error: misc-error: (#f ~A (generate-strong-password: length must be at least 4 to cover every character class.) #f)
ℹ️ info - ran 6 tasks in: 1 seconds, -957776 microseconds

finished running tests


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

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))'
deploysudo maak deploy succeeded33 lines · 1.9s
--------------------------------------------------------------

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: deploy


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

=> Running Maak task: deploy

  Generate documentation for the project and publish to the web directory.
API documentation saved to doc/api
index.texi: warning: must specify a title with a title command or @top

  Execute command:
guix time-machine --channels=channels.scm -- shell -m manifest.scm -- documenta api ./src/bewaking

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

  Execute command:
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

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/bewaking/API.html'
removed '/srv/http/jointhefreeworld.org/api-docs/bewaking/bewaking_005fbewaking.html'
removed '/srv/http/jointhefreeworld.org/api-docs/bewaking/bewaking_005fcli.html'
removed '/srv/http/jointhefreeworld.org/api-docs/bewaking/bewaking_005fcrypto.html'
removed '/srv/http/jointhefreeworld.org/api-docs/bewaking/bewaking_005fpassword.html'
removed directory '/srv/http/jointhefreeworld.org/api-docs/bewaking'
mkdir: created directory '/srv/http/jointhefreeworld.org/api-docs/bewaking'
renamed 'doc/api-dist/API.html' -> '/srv/http/jointhefreeworld.org/api-docs/bewaking/API.html'
renamed 'doc/api-dist/bewaking_005fbewaking.html' -> '/srv/http/jointhefreeworld.org/api-docs/bewaking/bewaking_005fbewaking.html'
renamed 'doc/api-dist/bewaking_005fcli.html' -> '/srv/http/jointhefreeworld.org/api-docs/bewaking/bewaking_005fcli.html'
renamed 'doc/api-dist/bewaking_005fcrypto.html' -> '/srv/http/jointhefreeworld.org/api-docs/bewaking/bewaking_005fcrypto.html'
renamed 'doc/api-dist/bewaking_005fpassword.html' -> '/srv/http/jointhefreeworld.org/api-docs/bewaking/bewaking_005fpassword.html'

Executing: mv -v doc/api-dist/* /srv/http/jointhefreeworld.org/api-docs/bewaking
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 . "bewaking")
    (branch-name . "trunk")
    (clone-url . "https://github.com/jjba23/bewaking.git")))