pingwing-5e728ad3-05b9

job succeeded

starting new job...
rundir='/etc/byggsteg/job-clone//pingwing/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 f5c24b7 to 8376316
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 succeeded17 lines · 0.0s
commit 837631658ed14f1aed3954dbad8c27ad5df8db44
Author: Josep Bigorra <jjbigorra@gmail.com>
Date:   Tue Nov 10 20:38:01 2026 +0100

    chore: Release v0.6.5
    
    The repository carries .github/FUNDING.yml now, so GitHub shows a Sponsor
    button on it that points at Buy Me a Coffee (buymeacoffee.com/jjbigorra).
    CHANGELOG.org records the release.
    
    Validation: maak test passed, the Veritas suite ran green (8 tasks, no
    failures).
    
    🤖 Generated with ECA (deepseek/deepseek-flash)
    
    Co-Authored-By: eca-agent <git@eca.dev>

A       .github/FUNDING.yml
M       CHANGELOG.org
testmaak test succeeded324 lines · 3.6s
scanning directories for unit tests...
scanning present working directory for unit tests...

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

found unit tests: (task-repository-spec.scm mail-spec.scm templates-spec.scm)


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


ℹ️ info - shuffled with seed 478582606 (replay with VERITAS_SEED=478582606)
+----------------------------------------------+
[📑 suite] binding, not splicing
  [🧪 test] a subject with an apostrophe in it round-trips unchanged
    [⚙️ assert] O'Brien is a name, not the end of a literal
    ✅ got: O'Brien'); DROP TABLE tasks; -- "quoted" & <b>bold</b>
  [🧪 test] a value that spells out SQL changes nothing
    [⚙️ assert] the table the value named is still there
    ✅ got: 3
+----------------------------------------------+
[📑 suite] reading the queue
  [🧪 test] the next task is the oldest pending one
    [⚙️ assert] a queue answers in the order it was filled
    ✅ got: queue-oldest
  [🧪 test] a batch is the oldest first and as long as it was asked for
    [⚙️ assert] a sweep takes the batch it asked for, oldest first
    ✅ got: (queue-oldest queue-middle)
    [⚙️ assert] a batch never carries a column the row does not have
    ✅ got: (queue-oldest queue-middle queue-newest)
  [🧪 test] the list an operator reads carries the metadata and no secrets
    [⚙️ assert] the newest first, and only the columns an operator needs
    ✅ got: (queue-newest queue-middle queue-oldest)
    [⚙️ assert] a reset link must not be on the way to a browser
    ✅ got: #f
+----------------------------------------------+
[📑 suite] updating and deleting
  [🧪 test] an update writes the columns it names
    [⚙️ assert] the status and the attempt count are what was written
    ✅ got: (complete 2)
  [🧪 test] a deleted task is gone
    [⚙️ assert] nothing answers for a task that was deleted
    ✅ got: #f
+----------------------------------------------+
[📑 suite] a worker that stopped
  [🧪 test] the claim of a task in flight is not undone by the sweep
    [⚙️ assert] a claim written a moment ago is newer than the threshold
    ✅ got: processing
  [🧪 test] a task a worker is still on is not taken away from it
    [⚙️ assert] a processing task newer than the threshold is left alone
    ✅ got: processing
  [🧪 test] a task old enough to be abandoned goes back in the queue
    [⚙️ assert] a processing task older than the threshold is pending again
    ✅ got: pending
+----------------------------------------------+
[📑 suite] claiming
  [🧪 test] a claimed task is no longer pending
    [⚙️ assert] claiming takes the task out of the queue
    ✅ got: #f
    [⚙️ assert] and the row says it is being processed
    ✅ got: processing
  [🧪 test] claiming an already claimed task changes nothing
    [⚙️ assert] a task another sweep took is left alone
    ✅ got: processing
ℹ️ info - ran 5 tasks in: 0 seconds, 115415 microseconds

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


ℹ️ info - shuffled with seed 478582606 (replay with VERITAS_SEED=478582606)
+----------------------------------------------+
[📑 suite] headers
  [🧪 test] ASCII is left readable
    [⚙️ assert] nothing is encoded that needs no encoding
    ✅ got: hello
  [🧪 test] a line break cannot inject a header
    [⚙️ assert] a CRLF in a value cannot open a header of its own
    ✅ got: 

  [🧪 test] non-ASCII is encoded
    [⚙️ assert] an accented subject is base64 encoded
    ✅ got: =?UTF-8?B?
  [🧪 test] a bare address carries no angle brackets
    [⚙️ assert] an address with no name is just the address
    ✅ got: j@x.y
  [🧪 test] a display name with a comma is quoted
    [⚙️ assert] a comma in a display name is quoted
    ✅ got: "Doe, Jane" <j@x.y>
  [🧪 test] the date is an RFC 5322 date
    [⚙️ assert] the date header is the shape a mail server reads
    ✅ got: Tue, 10 Nov 2026 20:42:37 +0100
+----------------------------------------------+
[📑 suite] quoted-printable
  [🧪 test] a trailing space is escaped
    [⚙️ assert] a space before a newline cannot be eaten
    ✅ got: tail=20
  [🧪 test] non-ASCII is escaped byte by byte
    [⚙️ assert] an accented character travels as its bytes
    ✅ got: =C3=A9
  [🧪 test] an equals sign is escaped
    [⚙️ assert] the escape character escapes itself
    ✅ got: a=3Db
  [🧪 test] lines are split with CRLF
    [⚙️ assert] a newline becomes CRLF
    ✅ got: one
two
  [🧪 test] a leading dot is escaped so SMTP cannot end DATA early
    [⚙️ assert] a line that starts with a dot is encoded
    ✅ got: =2Edot
+----------------------------------------------+
[📑 suite] the message
  [🧪 test] every line ends CRLF (no bare LF)
    [⚙️ assert] no bare newline survives into the message
    ✅ got: #f
  [🧪 test] the boundary opens and closes
    [⚙️ assert] a multipart message declares its boundary
    ✅ got: #t
  [🧪 test] both parts are present
    [⚙️ assert] the plain and the HTML body are both there
    ✅ got: #t
  [🧪 test] the id is appended to the subject
    [⚙️ assert] a reader can match the mail to the task
    ✅ got: Subject: Hi the-id
  [🧪 test] the HTML part is carried through as the caller wrote it
    [⚙️ assert] the body's own markup survives
    ✅ got: <p>html body</p>
    [⚙️ assert] no doctype is invented here
    ✅ got: 0
  [🧪 test] a Date header is present
    [⚙️ assert] the message is dated
    ✅ got: Date: 
  [🧪 test] a Message-ID is generated
    [⚙️ assert] the message carries an id of its own
    ✅ got: Message-ID: <
ℹ️ info - ran 3 tasks in: 0 seconds, 110157 microseconds

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


ℹ️ info - shuffled with seed 478582606 (replay with VERITAS_SEED=478582606)
+----------------------------------------------+
[📑 suite] password reset template
  [🧪 test] the frosted panel is there
    [⚙️ assert] the panel the family's mails share is drawn
    ✅ got: pw-panel
  [🧪 test] the account is named
    [⚙️ assert] the reader is told whose account this is
    ✅ got: joe@x.y
  [🧪 test] the link is present
    [⚙️ assert] the reset link is in the mail
    ✅ got: https://x/y
  [🧪 test] the style is inline
    [⚙️ assert] a mail client that drops stylesheets still sees it
    ✅ got: style=
+----------------------------------------------+
[📑 suite] what the dark override can reach
  [🧪 test] every class a message wears is a style with a dark reading
    [⚙️ assert] every class of the ticket notice is a named style
    ✅ got: ()
    [⚙️ assert] every class of the reset mail is a named style
    ✅ got: ()
    [⚙️ assert] every class of the alert is a named style
    ✅ got: ()
  [🧪 test] nothing paints a colour without a class to re-point it
    [⚙️ assert] the ticket notice paints only through classes
    ✅ got: ()
    [⚙️ assert] the reset mail paints only through classes
    ✅ got: ()
    [⚙️ assert] the alert paints only through classes
    ✅ got: ()
+----------------------------------------------+
[📑 suite] the fonts and the glass
  [🧪 test] the panel declares a frost blur
    [⚙️ assert] the panel is frosted, not merely translucent
    ✅ got: backdrop-filter
  [🧪 test] the faces are declared
    [⚙️ assert] Work Sans is declared
    ✅ got: Work Sans
    [⚙️ assert] Source Serif 4 is declared
    ✅ got: Source Serif 4
    [⚙️ assert] JuliaMono is declared
    ✅ got: JuliaMono
  [🧪 test] both colour schemes are offered
    [⚙️ assert] a dark theme is offered
    ✅ got: prefers-color-scheme
+----------------------------------------------+
[📑 suite] readability, in both readings of the palette
  [🧪 test] the measurement is the one WCAG defines
    [⚙️ assert] white on black is the 21:1 a ratio can reach
    ✅ got: 21.0
    [⚙️ assert] a colour on itself is 1:1
    ✅ got: 1.0
  [🧪 test] no message ships a text colour the floor was not applied to
    [⚙️ assert] the ticket notice ships only measured text colours
    ✅ got: ()
    [⚙️ assert] the reset mail ships only measured text colours
    ✅ got: ()
    [⚙️ assert] the alert ships only measured text colours
    ✅ got: ()
  [🧪 test] every colour clears the floor on every surface it is set on
    [⚙️ assert] the light reading clears the floor everywhere it is read
    ✅ got: ()
    [⚙️ assert] the dark reading clears the floor everywhere it is read
    ✅ got: ()
+----------------------------------------------+
[📑 suite] system alert template
  [🧪 test] the system is named
    [⚙️ assert] which system raised it is in the mail
    ✅ got: X
  [🧪 test] the alert is shown
    [⚙️ assert] what went wrong is in the mail
    ✅ got: disk full
+----------------------------------------------+
[📑 suite] the markup a mail client is handed
  [🧪 test] nothing is closed before it has been opened
    [⚙️ assert] the ticket notice is not swallowed by its own accent
    ✅ got: <!DOCTYPE html>
<html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width,initial-scale=1.0" /><meta name="color-scheme" content="light dark" /><meta name="supported-color-schemes" content="light dark" /><title>New ticket in Pingwing</title><style>@font-face{font-family:'Work Sans';font-style:normal;font-weight:100 900;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/work-sans-normal-latin.woff2') format('woff2');}
@font-face{font-family:'Work Sans';font-style:italic;font-weight:100 900;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/work-sans-italic-latin.woff2') format('woff2');}
@font-face{font-family:'Source Serif 4';font-style:normal;font-weight:200 900;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/source-serif-4-normal-latin.woff2') format('woff2');}
@font-face{font-family:'Source Serif 4';font-style:italic;font-weight:200 900;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/source-serif-4-italic-latin.woff2') format('woff2');}
@font-face{font-family:'JuliaMono';font-style:normal;font-weight:300 500;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/juliamono-normal-latin.woff2') format('woff2');}
@font-face{font-family:'JuliaMono';font-style:normal;font-weight:600 900;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/juliamono-bold-latin.woff2') format('woff2');}

@media (prefers-color-scheme: dark) {
  .pw-body { background-color: #232a2e !important; background-image: linear-gradient(160deg,#333b40 0%,#2b3339 55%,#232a2e 100%) !important; color: #d3c6aa !important; }
  .pw-panel { background-color: rgba(51,59,64,0.72) !important; border: 1px solid rgba(211,198,170,0.22) !important; box-shadow: 0 18px 44px rgba(0,0,0,0.38) !important; }
  .pw-inner { background-color: rgba(211,198,170,0.08) !important; border: 1px solid rgba(211,198,170,0.18) !important; }
  .pw-heading { color: #e6ddc4 !important; }
  .pw-text { color: #d3c6aa !important; }
  .pw-muted { color: #adb9ab !important; }
  .pw-mono { color: #e6ddc4 !important; }
  .pw-fact-label { color: #adb9ab !important; }
  .pw-fact-value { color: #e6ddc4 !important; }
  .pw-link { color: #e6ddc4 !important; }
  .pw-footer-link { color: #adb9ab !important; }
  .pw-alert { color: #e6ddc4 !important; }
  .pw-rule { border-top: 1px solid rgba(211,198,170,0.22) !important; }
}
</style></head><body class="pw-body" style="margin: 0; padding: 0; width: 100%; background-color: #e7e4dc; background-image: linear-gradient(160deg,#fbfaf6 0%,#eeece6 45%,#e6e3db 100%); font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 16px; line-height: 1.6; color: #4a5259; -webkit-font-smoothing: antialiased;"><table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;"><tr><td align="center" style="padding:34px 16px;"><table role="presentation" width="600" cellpadding="0" cellspacing="0" border="0" style="width: 100%; max-width: 600px; border-collapse: separate; margin: 0 auto;"><tr><td><table role="presentation" width="600" cellpadding="0" cellspacing="0" border="0" class="pw-panel" style="width: 100%; max-width: 600px; border-collapse: separate; background-color: rgba(252,251,247,0.72); border: 1px solid rgba(120,113,108,0.26); border-radius: 24px; box-shadow: 0 18px 44px rgba(43,51,57,0.14); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); overflow: hidden;"><tr><td style="padding:0;"><div class="pw-accent" style="height: 6px; line-height: 0; font-size: 0; background-image: linear-gradient(90deg,#a7c080,#e67e80,#dbbc7f,#7fbbb3);"></div><div style="padding:38px 34px;"><h1 class="pw-heading" style="color: #2b3339; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 23px; font-weight: 600; line-height: 1.35; margin: 0 0 6px;">New ticket in Pingwing</h1><p class="pw-text" style="color: #4a5259; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 16px; line-height: 1.65; margin: 14px 0;">someone opened a new ticket #12. Here is what it says.</p><div class="pw-inner" style="background-color: rgba(120,113,108,0.08); border: 1px solid rgba(120,113,108,0.18); border-radius: 16px; padding: 18px 20px; margin: 22px 0;"><table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;"><tr><td class="pw-fact-label" style="color: #5c655f; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 13px; padding: 4px 12px 4px 0; vertical-align: top;">Ticket</td><td class="pw-fact-value" style="color: #2b3339; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 14px; padding: 4px 0; text-align: right; vertical-align: top;"><span class="pw-mono" style="color: #2b3339; font-family: 'JuliaMono','DejaVu Sans Mono','Liberation Mono',Menlo,Consolas,'Courier New',monospace; font-size: 13px; word-break: break-all;">#12</span></td></tr><tr><td class="pw-fact-label" style="color: #5c655f; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 13px; padding: 4px 12px 4px 0; vertical-align: top;">Title</td><td class="pw-fact-value" style="color: #2b3339; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 14px; padding: 4px 0; text-align: right; vertical-align: top;">Fix the SMTP retry</td></tr><tr><td class="pw-fact-label" style="color: #5c655f; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 13px; padding: 4px 12px 4px 0; vertical-align: top;">Project</td><td class="pw-fact-value" style="color: #2b3339; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 14px; padding: 4px 0; text-align: right; vertical-align: top;"><span class="pw-mono" style="color: #2b3339; font-family: 'JuliaMono','DejaVu Sans Mono','Liberation Mono',Menlo,Consolas,'Courier New',monospace; font-size: 13px; word-break: break-all;">Pingwing</span></td></tr></table></div><table role="presentation" cellpadding="0" cellspacing="0" border="0" style="border-collapse:separate;margin:24px 0;"><tr><td class="pw-button" style="background-color: #a7c080; border-radius: 999px; padding: 13px 26px;"><a href="https://lucidplan/x/ticket/12" class="pw-button-ink" style="color: #2b3339; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 16px; font-weight: 600; text-decoration: none; display: inline-block;">Open ticket on Lucidplan</a></td></tr></table><p class="pw-muted" style="color: #5c655f; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 13px; line-height: 1.55; margin: 0 0 6px;">If the button does not work, copy and paste this link into your browser:</p><p class="pw-mono" style="color: #2b3339; font-family: 'JuliaMono','DejaVu Sans Mono','Liberation Mono',Menlo,Consolas,'Courier New',monospace; font-size: 13px; word-break: break-all;">https://lucidplan/x/ticket/12</p><div class="pw-rule" style="border-top: 1px solid rgba(120,113,108,0.28); margin: 24px 0; font-size: 0; line-height: 0;"></div><p class="pw-muted" style="color: #5c655f; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 13px; line-height: 1.55; margin: 0 0 6px;">You receive this because you take part in this project; no action is needed to keep receiving them.</p></div></td></tr></table></td></tr><tr><td><table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;"><tr><td align="center" style="padding:20px 12px 10px;"><p class="pw-muted" style="color: #5c655f; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 13px; line-height: 1.55; margin: 0 0 6px;">this e-mail was powered by pingwing - the free and open Lisp-powered notification nexus</p><p class="pw-muted" style="color: #5c655f; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 13px; line-height: 1.55; margin: 0 0 6px;"><a href="https://codeberg.org/jjba23/pingwing" class="pw-footer-link" style="color: #5c655f; text-decoration: underline;">🔗 pingwing's source code on Codeberg</a></p></td></tr></table></td></tr></table></td></tr></table></body></html>
    [⚙️ assert] the reset mail is not swallowed by its own rule
    ✅ got: <!DOCTYPE html>
<html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width,initial-scale=1.0" /><meta name="color-scheme" content="light dark" /><meta name="supported-color-schemes" content="light dark" /><title>Password Reset</title><style>@font-face{font-family:'Work Sans';font-style:normal;font-weight:100 900;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/work-sans-normal-latin.woff2') format('woff2');}
@font-face{font-family:'Work Sans';font-style:italic;font-weight:100 900;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/work-sans-italic-latin.woff2') format('woff2');}
@font-face{font-family:'Source Serif 4';font-style:normal;font-weight:200 900;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/source-serif-4-normal-latin.woff2') format('woff2');}
@font-face{font-family:'Source Serif 4';font-style:italic;font-weight:200 900;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/source-serif-4-italic-latin.woff2') format('woff2');}
@font-face{font-family:'JuliaMono';font-style:normal;font-weight:300 500;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/juliamono-normal-latin.woff2') format('woff2');}
@font-face{font-family:'JuliaMono';font-style:normal;font-weight:600 900;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/juliamono-bold-latin.woff2') format('woff2');}

@media (prefers-color-scheme: dark) {
  .pw-body { background-color: #232a2e !important; background-image: linear-gradient(160deg,#333b40 0%,#2b3339 55%,#232a2e 100%) !important; color: #d3c6aa !important; }
  .pw-panel { background-color: rgba(51,59,64,0.72) !important; border: 1px solid rgba(211,198,170,0.22) !important; box-shadow: 0 18px 44px rgba(0,0,0,0.38) !important; }
  .pw-inner { background-color: rgba(211,198,170,0.08) !important; border: 1px solid rgba(211,198,170,0.18) !important; }
  .pw-heading { color: #e6ddc4 !important; }
  .pw-text { color: #d3c6aa !important; }
  .pw-muted { color: #adb9ab !important; }
  .pw-mono { color: #e6ddc4 !important; }
  .pw-fact-label { color: #adb9ab !important; }
  .pw-fact-value { color: #e6ddc4 !important; }
  .pw-link { color: #e6ddc4 !important; }
  .pw-footer-link { color: #adb9ab !important; }
  .pw-alert { color: #e6ddc4 !important; }
  .pw-rule { border-top: 1px solid rgba(211,198,170,0.22) !important; }
}
</style></head><body class="pw-body" style="margin: 0; padding: 0; width: 100%; background-color: #e7e4dc; background-image: linear-gradient(160deg,#fbfaf6 0%,#eeece6 45%,#e6e3db 100%); font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 16px; line-height: 1.6; color: #4a5259; -webkit-font-smoothing: antialiased;"><table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;"><tr><td align="center" style="padding:34px 16px;"><table role="presentation" width="600" cellpadding="0" cellspacing="0" border="0" style="width: 100%; max-width: 600px; border-collapse: separate; margin: 0 auto;"><tr><td><table role="presentation" width="600" cellpadding="0" cellspacing="0" border="0" class="pw-panel" style="width: 100%; max-width: 600px; border-collapse: separate; background-color: rgba(252,251,247,0.72); border: 1px solid rgba(120,113,108,0.26); border-radius: 24px; box-shadow: 0 18px 44px rgba(43,51,57,0.14); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); overflow: hidden;"><tr><td style="padding:0;"><div class="pw-accent" style="height: 6px; line-height: 0; font-size: 0; background-image: linear-gradient(90deg,#a7c080,#e67e80,#dbbc7f,#7fbbb3);"></div><div style="padding:38px 34px;"><h1 class="pw-heading" style="color: #2b3339; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 23px; font-weight: 600; line-height: 1.35; margin: 0 0 6px;">Reset your password WikiMusic</h1><p class="pw-text" style="color: #4a5259; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 16px; line-height: 1.65; margin: 14px 0;">We received a request to reset the password for your account. If that was you, follow the button below; the link is yours alone, and it is safe to ignore this message if it was not.</p><div class="pw-inner" style="background-color: rgba(120,113,108,0.08); border: 1px solid rgba(120,113,108,0.18); border-radius: 16px; padding: 18px 20px; margin: 22px 0;"><table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;"><tr><td class="pw-fact-label" style="color: #5c655f; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 13px; padding: 4px 12px 4px 0; vertical-align: top;">Account</td><td class="pw-fact-value" style="color: #2b3339; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 14px; padding: 4px 0; text-align: right; vertical-align: top;"><span class="pw-mono" style="color: #2b3339; font-family: 'JuliaMono','DejaVu Sans Mono','Liberation Mono',Menlo,Consolas,'Courier New',monospace; font-size: 13px; word-break: break-all;">joe@x.y</span></td></tr></table></div><table role="presentation" cellpadding="0" cellspacing="0" border="0" style="border-collapse:separate;margin:24px 0;"><tr><td class="pw-button" style="background-color: #a7c080; border-radius: 999px; padding: 13px 26px;"><a href="https://x/y" class="pw-button-ink" style="color: #2b3339; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 16px; font-weight: 600; text-decoration: none; display: inline-block;">Reset your password</a></td></tr></table><p class="pw-muted" style="color: #5c655f; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 13px; line-height: 1.55; margin: 0 0 6px;">If the button does not work, copy and paste this link into your browser:</p><p class="pw-mono" style="color: #2b3339; font-family: 'JuliaMono','DejaVu Sans Mono','Liberation Mono',Menlo,Consolas,'Courier New',monospace; font-size: 13px; word-break: break-all;">https://x/y</p><div class="pw-rule" style="border-top: 1px solid rgba(120,113,108,0.28); margin: 24px 0; font-size: 0; line-height: 0;"></div><p class="pw-muted" style="color: #5c655f; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 13px; line-height: 1.55; margin: 0 0 6px;">This message was sent because someone asked to reset the password on this account. If that was not you, no action is needed and your password has not changed.</p></div></td></tr></table></td></tr><tr><td><table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;"><tr><td align="center" style="padding:20px 12px 10px;"><p class="pw-muted" style="color: #5c655f; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 13px; line-height: 1.55; margin: 0 0 6px;">this e-mail was powered by pingwing - the free and open Lisp-powered notification nexus</p><p class="pw-muted" style="color: #5c655f; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 13px; line-height: 1.55; margin: 0 0 6px;"><a href="https://codeberg.org/jjba23/pingwing" class="pw-footer-link" style="color: #5c655f; text-decoration: underline;">🔗 pingwing's source code on Codeberg</a></p></td></tr></table></td></tr></table></td></tr></table></body></html>
    [⚙️ assert] the alert is not swallowed either
    ✅ got: <!DOCTYPE html>
<html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width,initial-scale=1.0" /><meta name="color-scheme" content="light dark" /><meta name="supported-color-schemes" content="light dark" /><title>System Alert</title><style>@font-face{font-family:'Work Sans';font-style:normal;font-weight:100 900;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/work-sans-normal-latin.woff2') format('woff2');}
@font-face{font-family:'Work Sans';font-style:italic;font-weight:100 900;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/work-sans-italic-latin.woff2') format('woff2');}
@font-face{font-family:'Source Serif 4';font-style:normal;font-weight:200 900;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/source-serif-4-normal-latin.woff2') format('woff2');}
@font-face{font-family:'Source Serif 4';font-style:italic;font-weight:200 900;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/source-serif-4-italic-latin.woff2') format('woff2');}
@font-face{font-family:'JuliaMono';font-style:normal;font-weight:300 500;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/juliamono-normal-latin.woff2') format('woff2');}
@font-face{font-family:'JuliaMono';font-style:normal;font-weight:600 900;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/juliamono-bold-latin.woff2') format('woff2');}

@media (prefers-color-scheme: dark) {
  .pw-body { background-color: #232a2e !important; background-image: linear-gradient(160deg,#333b40 0%,#2b3339 55%,#232a2e 100%) !important; color: #d3c6aa !important; }
  .pw-panel { background-color: rgba(51,59,64,0.72) !important; border: 1px solid rgba(211,198,170,0.22) !important; box-shadow: 0 18px 44px rgba(0,0,0,0.38) !important; }
  .pw-inner { background-color: rgba(211,198,170,0.08) !important; border: 1px solid rgba(211,198,170,0.18) !important; }
  .pw-heading { color: #e6ddc4 !important; }
  .pw-text { color: #d3c6aa !important; }
  .pw-muted { color: #adb9ab !important; }
  .pw-mono { color: #e6ddc4 !important; }
  .pw-fact-label { color: #adb9ab !important; }
  .pw-fact-value { color: #e6ddc4 !important; }
  .pw-link { color: #e6ddc4 !important; }
  .pw-footer-link { color: #adb9ab !important; }
  .pw-alert { color: #e6ddc4 !important; }
  .pw-rule { border-top: 1px solid rgba(211,198,170,0.22) !important; }
}
</style></head><body class="pw-body" style="margin: 0; padding: 0; width: 100%; background-color: #e7e4dc; background-image: linear-gradient(160deg,#fbfaf6 0%,#eeece6 45%,#e6e3db 100%); font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 16px; line-height: 1.6; color: #4a5259; -webkit-font-smoothing: antialiased;"><table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;"><tr><td align="center" style="padding:34px 16px;"><table role="presentation" width="600" cellpadding="0" cellspacing="0" border="0" style="width: 100%; max-width: 600px; border-collapse: separate; margin: 0 auto;"><tr><td><table role="presentation" width="600" cellpadding="0" cellspacing="0" border="0" class="pw-panel" style="width: 100%; max-width: 600px; border-collapse: separate; background-color: rgba(252,251,247,0.72); border: 1px solid rgba(120,113,108,0.26); border-radius: 24px; box-shadow: 0 18px 44px rgba(43,51,57,0.14); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); overflow: hidden;"><tr><td style="padding:0;"><div class="pw-accent" style="height: 6px; line-height: 0; font-size: 0; background-image: linear-gradient(90deg,#a7c080,#e67e80,#dbbc7f,#7fbbb3);"></div><div style="padding:38px 34px;"><h1 class="pw-heading" style="color: #2b3339; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 23px; font-weight: 600; line-height: 1.35; margin: 0 0 6px;">System alert X</h1><p class="pw-text" style="color: #4a5259; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 16px; line-height: 1.65; margin: 14px 0;">An alert has been triggered from a system you watch. The details are below.</p><div class="pw-inner" style="background-color: rgba(120,113,108,0.08); border: 1px solid rgba(120,113,108,0.18); border-radius: 16px; padding: 18px 20px; margin: 22px 0;"><div class="pw-alert" style="color: #2b3339; font-family: 'JuliaMono','DejaVu Sans Mono','Liberation Mono',Menlo,Consolas,'Courier New',monospace; font-size: 13px; white-space: pre-wrap; word-break: break-word;">disk full</div></div><div class="pw-rule" style="border-top: 1px solid rgba(120,113,108,0.28); margin: 24px 0; font-size: 0; line-height: 0;"></div><p class="pw-muted" style="color: #5c655f; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 13px; line-height: 1.55; margin: 0 0 6px;">This message was sent automatically; reply to it if the alert needs a human.</p></div></td></tr></table></td></tr><tr><td><table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;"><tr><td align="center" style="padding:20px 12px 10px;"><p class="pw-muted" style="color: #5c655f; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 13px; line-height: 1.55; margin: 0 0 6px;">this e-mail was powered by pingwing - the free and open Lisp-powered notification nexus</p><p class="pw-muted" style="color: #5c655f; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 13px; line-height: 1.55; margin: 0 0 6px;"><a href="https://codeberg.org/jjba23/pingwing" class="pw-footer-link" style="color: #5c655f; text-decoration: underline;">🔗 pingwing's source code on Codeberg</a></p></td></tr></table></td></tr></table></td></tr></table></body></html>
+----------------------------------------------+
[📑 suite] the document a mail client is handed
  [🧪 test] every message opens with the doctype
    [⚙️ assert] the ticket notice is a whole document
    ✅ got: #t
    [⚙️ assert] the reset mail is a whole document
    ✅ got: #t
    [⚙️ assert] the alert is a whole document
    ✅ got: #t
  [🧪 test] and says it only once
    [⚙️ assert] the doctype is at the front, not repeated
    ✅ got: #f
  [🧪 test] the message a client receives opens with it
    [⚙️ assert] the doctype opens the HTML part of the message
    ✅ got: #t
    [⚙️ assert] the message carries exactly one doctype
    ✅ got: 1
+----------------------------------------------+
[📑 suite] lucidplan new-ticket template
  [🧪 test] the HTML part names the project, the ticket and the link
    [⚙️ assert] the project is named
    ✅ got: Pingwing
    [⚙️ assert] the ticket number reads as #12
    ✅ got: #12
    [⚙️ assert] the title is shown
    ✅ got: Fix the SMTP retry
    [⚙️ assert] the link is shown
    ✅ got: https://lucidplan/x/ticket/12
  [🧪 test] the plain part says the same thing
    [⚙️ assert] the plain part names the ticket too
    ✅ got: Fix the SMTP retry
    [⚙️ assert] the plain part names the project
    ✅ got: Pingwing
ℹ️ info - ran 8 tasks in: 1 seconds, -710182 microseconds

finished running tests


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

Maak 0.14.0: the infinitely extensible command runner

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

Executing Maak tasks: test


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

=> Running Maak task: test

  Run the project's test suite with Veritas.

The runner scans ./test for suites and loads each spec as a module, which is
why the specs under test/veritas/unit are named `(veritas unit ...)': that is
the name the runner derives from where they sit, and `-L ./test' is what lets
Guile resolve it.

  Execute command:
guix time-machine --channels=channels.scm -- shell -m manifest.scm -- guile -L ./lib -L . -L ./test -c '(begin (use-modules (veritas runner)) (run-tests))'

Executing: guix time-machine --channels=channels.scm -- shell -m manifest.scm -- guile -L ./lib -L . -L ./test -c '(begin (use-modules (veritas runner)) (run-tests))'
deploysudo herd restart pingwing-daemon || true succeeded2 lines · 0.2s
Enabled service pingwing-daemon.
Service pingwing-daemon has been started.
inspect the job's lambda expression
(lambda()
  `((task . ("maak-test" "pull-and-restart"))
    (daemon-name . "pingwing-daemon")
    (project . "pingwing")
    (branch-name . "trunk")
    (clone-url . "https://github.com/jjba23/pingwing.git")))