guix-commits
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[no subject]


From: Ludovic Courtès
Date: Fri, 18 Aug 2023 10:16:21 -0400 (EDT)

branch: wip-record-types
commit e7b27b98e636688f983b5ff6f6791a734efef1e8
Author: Ludovic Courtès <ludo@gnu.org>
AuthorDate: Mon Aug 14 23:44:51 2023 +0200

    DRAFT database: Use records instead of alists.
---
 src/cuirass/base.scm                  |  61 +--
 src/cuirass/database.scm              | 672 ++++++++++++++++++++++------------
 src/cuirass/http.scm                  | 142 +++----
 src/cuirass/notification.scm          |  12 +-
 src/cuirass/rss.scm                   |  12 +-
 src/cuirass/scripts/evaluate.scm      |  41 ++-
 src/cuirass/scripts/remote-server.scm |   8 +-
 src/cuirass/templates.scm             | 278 +++++++-------
 tests/database.scm                    | 449 +++++++++++++----------
 tests/http.scm                        |  50 +--
 tests/remote.scm                      |  41 ++-
 11 files changed, 1049 insertions(+), 717 deletions(-)

diff --git a/src/cuirass/base.scm b/src/cuirass/base.scm
index 99b3ea1..fc1b34d 100644
--- a/src/cuirass/base.scm
+++ b/src/cuirass/base.scm
@@ -358,7 +358,7 @@ build products."
   (let* ((build (db-get-build drv))
          (spec  (and build
                      (db-get-specification
-                      (assq-ref build #:specification)))))
+                      (build-specification-name build)))))
     (when (and spec build)
       (create-build-outputs build
                             (specification-build-outputs spec))))
@@ -510,8 +510,8 @@ updating the database accordingly."
 
 (define (build-derivation=? build1 build2)
   "Return true if BUILD1 and BUILD2 correspond to the same derivation."
-  (string=? (assq-ref build1 #:derivation)
-            (assq-ref build2 #:derivation)))
+  (string=? (build-derivation build1)
+            (build-derivation build2)))
 
 (define (clear-build-queue)
   "Reset the status of builds in the database that are marked as \"started\".
@@ -540,19 +540,23 @@ started)."
         (spawn-builds store valid))
       (log-info "done with restarted builds"))))
 
-(define (create-build-outputs build build-outputs)
-  "Given BUILDS a list of built derivations, save the build products described
-by BUILD-OUTPUTS."
+(define (create-build-outputs build outputs)
+  "Given BUILDS, a list of <build> records, save the build products described 
by
+OUTPUTS, a list of <build-output> records."
   (define (build-has-products? job-regex)
-    (let ((job-name (assq-ref build #:job-name)))
-      (string-match job-regex job-name)))
+    (let ((job-name (build-job-name build)))
+      (pk 'has-prod? job-regex build
+          (string-match job-regex job-name))))
 
   (define* (find-product build build-output)
-    (let* ((outputs (assq-ref build #:outputs))
+    (let* ((outputs (build-outputs build))
            (output (build-output-output build-output))
            (path (build-output-path build-output))
-           (root (and=> (assoc-ref outputs output)
-                        (cut assq-ref <> #:path))))
+           (root (and=> (find (lambda (o)
+                                (string=? (output-name o)
+                                          output))
+                              (pk 'looking-for output 'outputs outputs))
+                        output-item)))
       (and root
            (if (string=? path "")
                root
@@ -562,19 +566,19 @@ by BUILD-OUTPUTS."
     (stat:size (stat file)))
 
   (for-each (lambda (build-output)
-              (let ((product (and (build-has-products?
-                                   (build-output-job build-output))
-                                  (find-product build build-output))))
-                (when (and product (file-exists? product))
-                  (log-info "Adding build product ~a" product)
+              (let ((file (and (build-has-products?
+                                (build-output-job build-output))
+                               (find-product build build-output))))
+                (when (and file (file-exists? file))
+                  (log-info "Adding build product ~a" file)
                   (db-add-build-product
-                   `((#:build . ,(assq-ref build #:id))
-                     (#:type . ,(build-output-type build-output))
-                     (#:file-size . ,(file-size product))
-                     ;; TODO: Implement it.
-                     (#:checksum . "")
-                     (#:path . ,product))))))
-            build-outputs))
+                   (build-product
+                    (build-id (build-id build))
+                    (type (build-output-type build-output))
+                    (file file)
+                    (file-size (file-size file))
+                    (checksum ""))))))            ;TODO: Implement it.
+            outputs))
 
 (define (build-packages store eval-id)
   "Build JOBS and return a list of Build results."
@@ -582,7 +586,7 @@ by BUILD-OUTPUTS."
     (db-get-builds `((evaluation . ,eval-id))))
 
   (define derivations
-    (map (cut assq-ref <> #:derivation) builds))
+    (map built-derivations builds))
 
   ;; Register a GC root for each derivation so that they are not garbage
   ;; collected before getting built.
@@ -597,15 +601,12 @@ by BUILD-OUTPUTS."
     (spawn-builds store derivations)
 
     (let* ((results (filter-map (cut db-get-build <>) derivations))
-           (status (map (cut assq-ref <> #:status) results))
+           (status (map build-current-status results))
            (success (count (lambda (status)
                              (= status (build-status succeeded)))
                            status))
-           (outputs (map (cut assq-ref <> #:outputs) results))
-           (outs (append-map (match-lambda
-                               (((_ (#:path . (? string? outputs))) ...)
-                                outputs))
-                             outputs))
+           (outputs (map build-outputs results))
+           (outs (append-map build-output-path outputs))
            (fail (- (length derivations) success)))
 
       (log-info "outputs:\n~a" (string-join outs "\n"))
diff --git a/src/cuirass/database.scm b/src/cuirass/database.scm
index a3e8b19..155cc37 100644
--- a/src/cuirass/database.scm
+++ b/src/cuirass/database.scm
@@ -30,6 +30,7 @@
   #:use-module (cuirass remote)
   #:use-module (cuirass specification)
   #:use-module (cuirass utils)
+  #:use-module (guix records)
   #:use-module (guix channels)
   #:use-module (squee)
   #:use-module ((fibers scheduler) #:select (current-scheduler))
@@ -45,7 +46,94 @@
   #:use-module (srfi srfi-26)
   #:use-module (system foreign)
   #:use-module (rnrs bytevectors)
-  #:export (;; Procedures.
+  #:export (;; Data types.
+            output?
+            output-item
+            output-derivation
+            output-name
+
+            job?
+            job-build-id
+            job-status
+            job-name
+
+            output?
+            output
+            output-name
+            output-derivation
+            output-item
+
+            evaluation?
+            evaluation-id
+            evaluation-specification-name
+            evaluation-current-status
+            evaluation-start-time
+            evaluation-checkout-time
+            evaluation-completion-time
+            evaluation-checkouts
+
+            build?
+            build
+            build-id
+            build-derivation
+            build-dependencies
+            build-job-name
+            build-system
+            build-nix-name
+            build-log
+            build-priority
+            build-max-silent-time
+            build-timeout
+            build-outputs
+            build-evaluation-id
+            build-specification-name
+            build-current-status
+            build-last-status
+            build-current-weather
+            build-creation-time
+            build-start-time
+            build-completion-time
+            build-worker
+            build-products
+            build-dependencies/id
+
+            build-product
+            build-product-id
+            build-product-type
+            build-product-file
+            build-product-file-size
+            build-product-checksum
+
+            checkout?
+            checkout-commit
+            checkout-channel
+            checkout-directory
+
+            build-summary?
+            build-summary-evaluation-id
+            build-summary-status
+            build-summary-checkouts
+            build-summary-succeeded
+            build-summary-failed
+            build-summary-scheduled
+
+            evaluation-summary?
+            evaluation-summary-id
+            evaluation-summary-status
+            evaluation-summary-total
+            evaluation-summary-succeeded
+            evaluation-summary-failed
+            evaluation-summary-scheduled
+            evaluation-summary-start-time
+            evaluation-summary-checkout-time
+            evaluation-summary-completion-time
+
+            dashboard?
+            dashboard-id
+            dashboard-specification-name
+            dashboard-job-ids
+
+            ;; Procedures.
             db-init
             db-open
             db-close
@@ -628,44 +716,109 @@ RETURNING id;"))
    ((and (> status 0) (> last-status 0))
     (build-weather still-failing))))
 
-(define (db-add-output derivation output)
+(define-record-type* <build> build make-build
+  build?
+  this-build
+  (id              build-id (default 0))
+  (derivation      build-derivation)
+  (dependencies    build-dependencies             ;list of ".drv" file names
+                   (thunked)
+                   (default
+                     (if (= 0 (build-id this-build))
+                         '()                      ;not yet in database
+                         (db-get-build-dependencies/derivation
+                          (build-id this-build)))))
+  (job-name        build-job-name)
+  (system          build-system)
+  (nix-name        build-nix-name)
+  (log             build-log (default ""))
+  (priority        build-priority (default max-priority))
+  (max-silent-time build-max-silent-time (default 3600))
+  (timeout         build-timeout (default (* 12 3600)))
+  (outputs         build-outputs)
+  (evaluation-id   build-evaluation-id)
+  (specification-name build-specification-name)
+
+  (status          build-current-status
+                   (default (build-status scheduled)))
+  (last-status     build-last-status
+                   (default (build-status scheduled)))
+  (weather         build-current-weather
+                   (default (build-weather unknown)))
+  (creation-time   build-creation-time
+                   (default (time-second (current-time time-utc))))
+  (start-time      build-start-time (default 0))
+  (completion-time build-completion-time (default 0))
+  (worker          build-worker (default #f))
+  (products        build-products
+                   (thunked)
+                   (default
+                     (if (= 0 (build-id this-build))
+                         '()
+                         (db-get-build-products (build-id this-build))))))
+
+(define (set-build-id b id)
+  (build (inherit b) (id id)))
+
+(define-record-type* <build-product> build-product make-build-product
+  build-product?
+  (id         build-product-id (default 0))
+  (build-id   build-product-build-id)
+  (type       build-product-type)
+  (file       build-product-file)
+  (file-size  build-product-file-size)
+  (checksum   build-product-checksum))
+
+(define-record-type* <job> job make-job
+  job?
+  (build-id       job-build-id)                   ;integer
+  (evaluation-id  job-evaluation-id)              ;integer
+  (status         job-status)                     ;integer
+  (system         job-system)                     ;string
+  (name           job-name))                      ;string
+
+(define-record-type* <output> output make-output
+  output?
+  (item        output-item)                       ;string
+  (derivation  output-derivation)                 ;string
+  (name        output-name (default "out")))      ;string
+
+(define (db-add-output output)
   "Insert OUTPUT associated with DERIVATION."
   (with-db-worker-thread db
-    (match output
-      ((name . path)
-       (exec-query/bind db "\
+    (exec-query/bind db "\
 INSERT INTO Outputs (derivation, name, path) VALUES ("
-                        derivation ", " name ", " path ")
-ON CONFLICT ON CONSTRAINT outputs_pkey DO NOTHING;")))))
+                     (output-derivation output) ", " (output-name output)
+                     ", " (output-item output) ")
+ON CONFLICT ON CONSTRAINT outputs_pkey DO NOTHING;")))
 
 (define (db-add-build build)
   "Store BUILD in database the database only if one of its outputs is new.
 Return #f otherwise.  BUILD outputs are stored in the OUTPUTS table."
-  (with-db-worker-thread db
-    (exec-query/bind db "
+  (match (with-db-worker-thread db
+           (exec-query/bind db "
 INSERT INTO Builds (derivation, evaluation, job_name, system, nix_name, log,
 status, priority, max_silent, timeout, timestamp, starttime, stoptime)
 VALUES ("
-                     (assq-ref build #:derivation) ", "
-                     (assq-ref build #:eval-id) ", "
-                     (assq-ref build #:job-name) ", "
-                     (assq-ref build #:system) ", "
-                     (assq-ref build #:nix-name) ", "
-                     (assq-ref build #:log) ", "
-                     (or (assq-ref build #:status)
-                         (build-status scheduled)) ", "
-                         (or (assq-ref build #:priority) max-priority) ", "
-                         (or (assq-ref build #:max-silent) 0) ", "
-                         (or (assq-ref build #:timeout) 0) ", "
-                         (or (assq-ref build #:timestamp) 0) ", "
-                         (or (assq-ref build #:starttime) 0) ", "
-                         (or (assq-ref build #:stoptime) 0) ")
+                            (build-derivation build) ", "
+                            (build-evaluation-id build) ", "
+                            (build-job-name build) ", "
+                            (build-system build) ", "
+                            (build-nix-name build) ", "
+                            (build-log build) ", "
+                            (build-current-status build) ", "
+                            (build-priority build) ", "
+                            (build-max-silent-time build) ", "
+                            (build-timeout build) ", "
+                            (build-creation-time build) ", "
+                            (build-start-time build) ", "
+                            (build-completion-time build) ")
 ON CONFLICT ON CONSTRAINT builds_derivation_key DO NOTHING;"))
-  (let* ((derivation (assq-ref build #:derivation))
-         (outputs (assq-ref build #:outputs))
-         (new-outputs (filter-map (cut db-add-output derivation <>)
-                                  outputs)))
-    derivation))
+    (0                             ;a build for this derivation already exists
+     #f)
+    ((? integer? id)
+     (for-each db-add-output (build-outputs build))
+     id)))
 
 (define (db-add-build-product product)
   "Insert PRODUCT into BuildProducts table."
@@ -673,11 +826,11 @@ ON CONFLICT ON CONSTRAINT builds_derivation_key DO 
NOTHING;"))
     (exec-query/bind db "\
 INSERT INTO BuildProducts (build, type, file_size, checksum,
 path) VALUES ("
-                     (assq-ref product #:build) ", "
-                     (assq-ref product #:type) ", "
-                     (assq-ref product #:file-size) ", "
-                     (assq-ref product #:checksum) ", "
-                     (assq-ref product #:path) ")
+                     (build-product-build-id product) ", "
+                     (build-product-type product) ", "
+                     (build-product-file-size product) ", "
+                     (build-product-checksum product) ", "
+                     (build-product-file product) ")
 ON CONFLICT ON CONSTRAINT buildproducts_pkey DO NOTHING;")))
 
 (define (db-get-output path)
@@ -688,8 +841,7 @@ WHERE path =" path "
 LIMIT 1;")
       (() #f)
       (((derivation name))
-       `((#:derivation . ,derivation)
-         (#:name . ,name))))))
+       (output (item path) (derivation derivation) (name name))))))
 
 (define (db-get-outputs derivation)
   "Retrieve the OUTPUTS of the build identified by DERIVATION in the
@@ -704,7 +856,7 @@ WHERE derivation =" derivation ";"))
         (((name path)
           . rest)
          (loop rest
-               (cons `(,name . ((#:path . ,path)))
+               (cons (output (name name) (item path) (derivation derivation))
                      outputs)))))))
 
 (define (db-get-time-since-previous-eval specification)
@@ -717,19 +869,23 @@ WHERE specification = " specification
 "ORDER BY Evaluations.timestamp DESC LIMIT 1"))
       ((time)
        (string->number time))
-      (else #f))))
+      (_ #f))))
 
 (define (db-get-build-percentages builds)
+  "Return the list of \"build percentages\" for BUILDS."
   (define build-ids
     (format #f "{~a}"
             (string-join
-             (map number->string
-                  (map (cut assq-ref <> #:id) builds))
+             (map (compose number->string build-id) builds)
              ",")))
 
   (with-db-worker-thread db
     (let loop ((rows
-                (exec-query/bind db "
+                (map (match-lambda
+                       ((id percentage)
+                        (list (string->number id)
+                              (string->number percentage))))
+                     (exec-query/bind db "
 SELECT id, CASE WHEN last_duration = 0 THEN
 0 ELSE LEAST(duration::float/last_duration * 100, 100)::int END AS percentage
 FROM (SELECT  DISTINCT ON (b1.id) b1.id AS id,
@@ -738,34 +894,30 @@ COALESCE((b2.stoptime - b2.starttime), 0) AS 
last_duration,
 LEFT JOIN builds AS b2 ON b1.job_name = b2.job_name
 AND b2.status >= 0 AND b2.status < 2 WHERE b1.id IN
 (SELECT id FROM builds WHERE id = ANY(" build-ids "))
-ORDER BY b1.id,  b2.id DESC) d;"))
+ORDER BY b1.id,  b2.id DESC) d;")))
                (percentages '()))
-      (match rows
-        (() (reverse percentages))
-        (((id percentage) . rest)
-         (let ((build
-                (find (lambda (build)
-                        (eq? (assq-ref build #:id)
-                             (string->number id)))
-                      builds)))
-           (loop rest
-                 (cons `(,@build
-                         (#:percentage . ,(string->number percentage)))
-                       percentages))))))))
+      (map (lambda (build)
+             (match (assoc (build-id build) rows)
+               ((_ percentage)
+                percentage)))
+           builds))))
 
-(define (db-add-job job eval-id)
+(define (db-add-job-for-build build)
   "Insert JOB into Jobs table for the EVAL-ID evaluation.  It is possible that
 another already built derivation has the same build outputs that the JOB
 derivation.  In that case, the JOB DERIVATION field is set to the existing
 derivation sharing the same build outputs, otherwise it is set to the given
 JOB derivation."
-  (let* ((name       (assq-ref job #:job-name))
-         (derivation (assq-ref job #:derivation))
-         (outputs    (assq-ref job #:outputs))
-         (output     (match outputs
-                       (((name . path) _ ...)
-                        path)))
-         (system     (assq-ref job #:system)))
+  (let* ((job        (job (build-id (build-id build))
+                          (evaluation-id (build-evaluation-id build))
+                          (status (build-current-status build))
+                          (system (build-system build))
+                          (name (build-job-name build))))
+         (name       (job-name job))
+         (derivation (build-derivation build))
+         (outputs    (build-outputs build))
+         (output     (output-item (first outputs)))
+         (system     (job-system job)))
     (with-db-worker-thread db
       (exec-query/bind db "\
 WITH b AS
@@ -773,9 +925,12 @@ WITH b AS
 (SELECT COALESCE((SELECT derivation FROM Outputs WHERE
 PATH = " output "), " derivation ")))
 INSERT INTO Jobs (name, evaluation, build, status, system)
-(SELECT " name ", " eval-id ", b.id, b.status," system " FROM b)
+(SELECT " name ", " (build-evaluation-id build) ", b.id, b.status," system " 
FROM b)
 ON CONFLICT ON CONSTRAINT jobs_pkey DO NOTHING;"))))
 
+(define (job-build job)
+  (db-get-build (job-build-id job)))
+
 (define (db-get-jobs eval-id filters)
   "Return the jobs inside Jobs table for the EVAL-ID evaluation that are
 matching the given FILTERS.  FILTERS is an assoc list whose possible keys are
@@ -785,7 +940,7 @@ the symbols system and names."
 
   (with-db-worker-thread db
     (let ((query "
-SELECT build, status, name FROM Jobs
+SELECT build, status, name, evaluation, system FROM Jobs
 WHERE Jobs.evaluation = :evaluation
 AND ((Jobs.system = :system) OR :system IS NULL)
 AND ((Jobs.name = ANY(:names)) OR :names IS NULL)
@@ -799,14 +954,30 @@ ORDER BY Jobs.name")
                  (jobs '()))
         (match rows
           (() (reverse jobs))
-          (((id status name)
-            . rest)
+          (((id status name evaluation system) . rest)
            (loop rest
-                 (cons `((#:build . ,(string->number id))
-                         (#:status . ,(string->number status))
-                         (#:name . ,name))
+                 (cons (job (build-id (string->number id))
+                            (status (string->number status))
+                            (name name)
+                            (evaluation-id evaluation)
+                            (system system))
                        jobs))))))))
 
+(define-record-type* <evaluation> evaluation make-evaluation
+  evaluation?
+  this-evaluation
+  (id                  evaluation-id)
+  (specification-name  evaluation-specification-name)
+  (status              evaluation-current-status
+                       (default (evaluation-status started)))
+  (start-time          evaluation-start-time)
+  (checkout-time       evaluation-checkout-time (default 0))
+  (completion-time     evaluation-completion-time (default 0))
+  (checkouts           evaluation-checkouts
+                       (thunked)
+                       (default (db-get-checkouts
+                                 (evaluation-id this-evaluation)))))
+
 (define* (db-get-jobs-history names
                               #:key spec (limit 10))
   "Return the list of jobs from Jobs table which name is a member of the given
@@ -848,6 +1019,7 @@ AND Jobs.name = ANY(:names);")
                        (begin
                          (assq-set! eval #:jobs (cons job jobs))
                          evaluations)
+                       ;; TODO: Define a record type.
                        (cons `((#:evaluation . ,(string->number evaluation))
                                (#:checkouts . ,(db-get-checkouts evaluation))
                                (#:jobs . ,(list job)))
@@ -880,6 +1052,22 @@ SELECT target FROM BuildDependencies WHERE source = " 
build))
          (loop rest
                (cons (string->number target) dependencies)))))))
 
+(define (db-get-build-dependencies/derivation build)
+  "Return the list of derivations (\".drv\" file names) BUILD depends on."
+  (with-db-worker-thread db
+    (let loop ((rows (exec-query/bind db "
+SELECT Builds.derivation FROM Builds
+INNER JOIN BuildDependencies AS dep ON dep.target = Builds.id
+WHERE dep.source = " build))
+               (dependencies '()))
+      (match rows
+        (() (reverse dependencies))
+        (((target) . rest)
+         (loop rest
+               (cons target dependencies)))))))
+
+(define build-dependencies/id (compose db-get-build-dependencies build-id))
+
 (define (db-update-resumable-builds!)
   "Update the build status of the failed-dependency builds which all
 dependencies are successful to scheduled."
@@ -909,13 +1097,14 @@ INNER JOIN Builds AS dep ON bd.target = dep.id AND 
dep.status > 0
 WHERE Builds.status = " (build-status scheduled)
 " GROUP BY Builds.id) AS deps WHERE deps.id = Builds.id")))
 
-(define (db-register-builds jobs eval-id specification)
+(define (db-register-builds builds specification)
   (define (new-outputs? outputs)
     (let ((new-outputs
-           (filter-map (match-lambda
-                         ((name . path)
-                          (let ((drv (db-get-output path)))
-                            (and (not drv) path))))
+           (filter-map (lambda (output)
+                         (let ((drv (db-get-output
+                                     (output-item output))))
+                           (and (not drv)
+                                (output-item output))))
                        outputs)))
       (not (null? new-outputs))))
 
@@ -923,59 +1112,31 @@ WHERE Builds.status = " (build-status scheduled)
     (let ((spec-priority (specification-priority specification)))
       (+ (* spec-priority 10) priority)))
 
-  (define (register job)
-    (let* ((drv        (assq-ref job #:derivation))
-           (job-name   (assq-ref job #:job-name))
-           (system     (assq-ref job #:system))
-           (nix-name   (assq-ref job #:nix-name))
-           (log        (assq-ref job #:log))
-           (priority   (or (assq-ref job #:priority) max-priority))
-           (max-silent (assq-ref job #:max-silent-time))
-           (timeout    (assq-ref job #:timeout))
-           (outputs    (assq-ref job #:outputs))
-           (cur-time   (time-second (current-time time-utc)))
-           (result
-            (and (new-outputs? outputs)
-                 (let ((build `((#:derivation . ,drv)
-                                (#:eval-id . ,eval-id)
-                                (#:job-name . ,job-name)
-                                (#:system . ,system)
-                                (#:nix-name . ,nix-name)
-
-                                ;; XXX: We'd leave LOG to #f (i.e., NULL) but
-                                ;; that currently violates the non-NULL
-                                ;; constraint.
-                                (#:log . ,(or log ""))
-
-                                (#:status . ,(build-status scheduled))
-                                (#:priority . ,(build-priority priority))
-                                (#:max-silent . ,max-silent)
-                                (#:timeout . ,timeout)
-                                (#:outputs . ,outputs)
-                                (#:timestamp . ,cur-time)
-                                (#:starttime . 0)
-                                (#:stoptime . 0))))
-                   (db-add-build build)
-                   job))))
+  (define (register build)
+    (let ((result (and (new-outputs? (build-outputs build))
+                       (and=> (db-add-build build)
+                              (cut set-build-id build <>)))))
 
       ;; Always register JOB inside the Jobs table.  If there are new outputs,
       ;; JOB will refer to the newly created build.  Otherwise, it will refer
       ;; to the last build with the same build outputs.
-      (db-add-job job eval-id)
+      (db-add-job-for-build (or result build))
       result))
 
-  (define (register-dependencies job)
-    (let ((drv    (assq-ref job #:derivation))
-          (inputs (or (assq-ref job #:inputs) '())))
+  (define (register-dependencies build)
+    (let ((drv    (build-derivation build))
+          (inputs (build-dependencies build)))
       (db-add-build-dependencies drv inputs)))
 
   (with-db-worker-thread db
-    (log-info "Registering builds for evaluation ~a." eval-id)
+    (log-info "Registering builds for evaluation~{ ~a~}."
+              (delete-duplicates
+               (map build-evaluation-id builds)))
     (exec-query db "BEGIN TRANSACTION;")
-    (let ((new-jobs (filter-map register jobs)))
+    (let ((builds (filter-map register builds)))
       ;; Register build dependencies after registering all the evaluation
       ;; derivations.
-      (for-each register-dependencies new-jobs)
+      (for-each register-dependencies builds)
       (exec-query db "COMMIT;")
       #t)))
 
@@ -1042,7 +1203,7 @@ UPDATE Builds SET stoptime =" now
 " AND status != " status ";")))
           (when (positive? rows)
             (let* ((build (db-get-build drv))
-                   (spec (assq-ref build #:specification))
+                   (spec (build-specification-name build))
                    (specification (db-get-specification spec))
                    (notifications
                     (specification-notifications specification)))
@@ -1052,7 +1213,7 @@ UPDATE Builds SET stoptime =" now
                                     (eq? weather
                                          (build-weather new-failure)))
                             (db-push-notification notif
-                                                  (assq-ref build #:id))))
+                                                  (build-id build))))
                         notifications)))))))
 
 (define* (db-update-build-worker! drv worker)
@@ -1138,14 +1299,14 @@ WHERE build = " build-id))
                (products '()))
       (match rows
         (() (reverse products))
-        (((id type file-size checksum path)
-          . rest)
+        (((id type file-size checksum path) . rest)
          (loop rest
-               (cons `((#:id . ,(string->number id))
-                       (#:type . ,type)
-                       (#:file-size . ,(string->number file-size))
-                       (#:checksum . ,checksum)
-                       (#:path . ,path))
+               (cons (build-product (id (string->number id))
+                                    (build-id build-id)
+                                    (type type)
+                                    (file-size (string->number file-size))
+                                    (checksum checksum)
+                                    (file path))
                      products)))))))
 
 (define (db-get-builds-by-search filters)
@@ -1157,7 +1318,8 @@ border-low-id, border-high-id, and nr."
 SELECT * FROM
 (SELECT Builds.id, Builds.timestamp,
 Builds.starttime,Builds.stoptime, Builds.log, Builds.status,
-Builds.job_name, Builds.system, Builds.nix_name, Specifications.name
+Builds.job_name, Builds.system, Builds.nix_name, Builds.evaluation,
+Builds.derivation, Specifications.name, Builds.worker
 FROM Builds
 INNER JOIN Evaluations ON Builds.evaluation = Evaluations.id
 INNER JOIN Specifications ON Evaluations.specification = Specifications.name
@@ -1187,20 +1349,28 @@ ORDER BY Builds.id DESC;"))
         (match builds
           (() (reverse result))
           (((id timestamp starttime stoptime log status job-name
-                system nix-name specification)
+                system nix-name evaluation-id derivation specification
+                worker)
             . rest)
            (loop rest
-                 (cons `((#:id . ,(string->number id))
-                         (#:timestamp . ,(string->number timestamp))
-                         (#:starttime . ,(string->number starttime))
-                         (#:stoptime . ,(string->number stoptime))
-                         (#:log . ,log)
-                         (#:status . ,(string->number status))
-                         (#:job-name . ,job-name)
-                         (#:system . ,system)
-                         (#:nix-name . ,nix-name)
-                         (#:specification . ,specification)
-                         (#:buildproducts . ,(db-get-build-products id)))
+                 (cons (build (id (string->number id))
+                              (derivation derivation)
+                              (creation-time (string->number timestamp))
+                              (start-time (string->number starttime))
+                              (completion-time (string->number stoptime))
+                              (log log)
+                              (status (string->number status))
+                              (job-name job-name)
+                              (system system)
+                              (nix-name nix-name)
+                              (evaluation-id evaluation-id)
+                              (specification-name specification)
+                              (worker worker)
+                              (max-silent-time *unspecified*)
+                              (timeout *unspecified*)
+                              ;; FIXME: The following one would require an
+                              ;; extra JOIN.
+                              (outputs *unspecified*))
                        result))))))))
 
 (define (db-get-builds filters)
@@ -1273,9 +1443,11 @@ OR :borderhightime IS NULL OR :borderhighid IS NULL)")))
       '()
       (map car filters))))
 
-  (define (format-outputs names paths)
+  (define (format-outputs names paths derivation)
     (map (lambda (name path)
-           `(,name . ((#:path . ,path))))
+           (output (name name)
+                   (item path)
+                   (derivation derivation)))
          (string-split names #\,)
          (string-split paths #\,)))
 
@@ -1286,11 +1458,12 @@ OR :borderhightime IS NULL OR :borderhighid IS NULL)")))
           '()))
 
     (map (lambda (id type file-size checksum path)
-           `((#:id . ,(string->number id))
-             (#:type . ,type)
-             (#:file-size . ,(string->number file-size))
-             (#:checksum . ,checksum)
-             (#:path . ,path)))
+           (build-product (id (string->number id))
+                          (build-id *unspecified*) ;FIXME
+                          (type type)
+                          (file-size (string->number file-size))
+                          (checksum checksum)
+                          (file path)))
          (split ids)
          (split types)
          (split file-sizes)
@@ -1298,6 +1471,7 @@ OR :borderhightime IS NULL OR :borderhighid IS NULL)")))
          (split paths)))
 
   (define (format-build-dependencies dependencies)
+    ;; FIXME: Should return a list of derivations.
     (if dependencies
         (map string->number (string-split dependencies #\,))
         '()))
@@ -1359,8 +1533,8 @@ ORDER BY ~a;"
                                       (map number->string value)
                                       ",")))
                             ('nr value)
-                            ('order #f) ; Doesn't need binding.
-                            ('status #f) ; Doesn't need binding.
+                            ('order #f)           ; Doesn't need binding.
+                            ('status #f)          ; Doesn't need binding.
                             (else value)))))
                  filters))
            (builds (exec-query/bind-params db query params)))
@@ -1376,37 +1550,38 @@ ORDER BY ~a;"
                         products-checksum products-path dependencies)
             . rest)
            (loop rest
-                 (cons `((#:derivation . ,derivation)
-                         (#:id . ,(string->number id))
-                         (#:timestamp . ,(string->number timestamp))
-                         (#:starttime . ,(string->number starttime))
-                         (#:stoptime . ,(string->number stoptime))
-                         (#:log . ,log)
-                         (#:status . ,(string->number status))
-                         (#:last-status . ,(and last-status
+                 (cons (build (derivation derivation)
+                              (id (string->number id))
+                              (creation-time (string->number timestamp))
+                              (start-time (string->number starttime))
+                              (completion-time (string->number stoptime))
+                              (log log)
+                              (status (string->number status))
+                              (last-status (and last-status
                                                 (string->number last-status)))
-                         (#:weather . ,(if weather
+                              (weather (if weather
                                            (string->number weather)
                                            (build-weather unknown)))
-                         (#:priority . ,(string->number priority))
-                         (#:max-silent . ,(string->number max-silent))
-                         (#:timeout . ,(string->number timeout))
-                         (#:job-name . ,job-name)
-                         (#:system . ,system)
-                         (#:worker . ,worker)
-                         (#:nix-name . ,nix-name)
-                         (#:eval-id . ,(string->number eval-id))
-                         (#:specification . ,specification)
-                         (#:builddependencies .
-                          ,(format-build-dependencies dependencies))
-                         (#:outputs . ,(format-outputs outputs-name
-                                                       outputs-path))
-                         (#:buildproducts .
-                          ,(format-build-products products-id
-                                                  products-type
-                                                  products-file-size
-                                                  products-checksum
-                                                  products-path)))
+                              (priority (string->number priority))
+                              (max-silent-time (string->number max-silent))
+                              (timeout (string->number timeout))
+                              (job-name job-name)
+                              (system system)
+                              (worker worker)
+                              (nix-name nix-name)
+                              (evaluation-id (string->number eval-id))
+                              (specification-name specification)
+                              ;; (dependencies
+                              ;;  (format-build-dependencies dependencies))
+                              (outputs (format-outputs outputs-name
+                                                       outputs-path
+                                                       derivation))
+                              (products
+                               (format-build-products products-id
+                                                      products-type
+                                                      products-file-size
+                                                      products-checksum
+                                                      products-path)))
                        result))))))))
 
 (define (db-get-build derivation-or-id)
@@ -1439,6 +1614,12 @@ SELECT id FROM pending_dependencies WHERE deps = 0 LIMIT 
1;"))
       ((id) (db-get-build (string->number id)))
       (else #f))))
 
+(define-record-type* <checkout> checkout make-checkout
+  checkout?
+  (commit    checkout-commit)
+  (channel   checkout-channel)
+  (directory checkout-directory))
+
 (define (db-get-checkouts eval-id)
   (with-db-worker-thread db
     (let loop ((rows (exec-query/bind
@@ -1447,12 +1628,11 @@ WHERE evaluation =" eval-id " ORDER BY channel ASC;"))
                (checkouts '()))
       (match rows
         (() (reverse checkouts))
-        (((revision channel directory)
-          . rest)
+        (((revision channel directory) . rest)
          (loop rest
-               (cons `((#:commit . ,revision)
-                       (#:channel . ,(string->symbol channel))
-                       (#:directory . ,directory))
+               (cons (checkout (commit revision)
+                               (channel (string->symbol channel))
+                               (directory directory))
                      checkouts)))))))
 
 (define (db-get-latest-checkout spec channel eval-id)
@@ -1465,20 +1645,9 @@ specification with an evaluation id inferior or equal to 
EVAL-ID."
             " AND evaluation <= " eval-id "ORDER BY evaluation DESC LIMIT 1;")
       (() #f)
       (((channel revision directory))
-       `((#:commit . ,revision)
-         (#:channel . ,(string->symbol channel))
-         (#:directory . ,directory))))))
-
-(define (parse-evaluation evaluation)
-  (match evaluation
-    ((id specification status timestamp checkouttime evaltime)
-     `((#:id . ,(string->number id))
-       (#:specification . ,specification)
-       (#:status . ,(string->number status))
-       (#:timestamp . ,(string->number timestamp))
-       (#:checkouttime . ,(string->number checkouttime))
-       (#:evaltime . ,(string->number evaltime))
-       (#:checkouts . ,(db-get-checkouts id))))))
+       (checkout (commit revision)
+                 (channel (string->symbol channel))
+                 (directory directory))))))
 
 (define (db-get-evaluation id)
   (with-db-worker-thread db
@@ -1489,6 +1658,16 @@ FROM Evaluations WHERE id = " id)
       ((evaluation)
        (parse-evaluation evaluation)))))
 
+(define (parse-evaluation lst)
+  (match lst
+    ((id specification status timestamp checkouttime evaltime)
+     (evaluation (id (string->number id))
+                 (specification-name specification)
+                 (status (string->number status))
+                 (completion-time (string->number timestamp))
+                 (checkout-time (string->number checkouttime))
+                 (start-time (string->number evaltime))))))
+
 (define* (db-get-evaluations limit
                              #:optional spec)
   (with-db-worker-thread db
@@ -1509,6 +1688,19 @@ ORDER BY id DESC LIMIT :limit;")
            (loop rest
                  (cons (parse-evaluation evaluation) evaluations))))))))
 
+(define-record-type* <build-summary> build-summary make-build-summary
+  build-summary?
+  this-build-summary
+  (evaluation-id build-summary-evaluation-id)
+  (status        build-summary-status)
+  (checkouts     build-summary-checkouts
+                 (thunked)
+                 (default (db-get-checkouts
+                           (build-summary-evaluation-id this-build-summary))))
+  (succeeded     build-summary-succeeded (default 0))
+  (failed        build-summary-failed (default 0))
+  (scheduled     build-summary-scheduled (default 0)))
+
 (define (db-get-evaluations-build-summary spec limit border-low border-high)
   (with-db-worker-thread db
     (let ((query "
@@ -1531,18 +1723,20 @@ ORDER BY E.id DESC;")
                     (#:borderlow . ,border-low)
                     (#:borderhigh . ,border-high))))
       (let loop ((rows (exec-query/bind-params db query params))
-                 (evaluations '()))
+                 (summaries '()))
         (match rows
-          (() (reverse evaluations))
+          (()
+           (reverse summaries))
           (((id status succeeded failed scheduled) . rest)
            (loop rest
-                 (cons `((#:id . ,(string->number id))
-                         (#:status . ,(string->number status))
-                         (#:checkouts . ,(db-get-checkouts id))
-                         (#:succeeded . ,(or (string->number succeeded) 0))
-                         (#:failed . ,(or (string->number failed) 0))
-                         (#:scheduled . ,(or (string->number scheduled) 0)))
-                       evaluations))))))))
+                 (cons (build-summary
+                        (evaluation-id (string->number id))
+                        (status (string->number status))
+                        (checkouts (db-get-checkouts id))
+                        (succeeded (or (string->number succeeded) 0))
+                        (failed (or (string->number failed) 0))
+                        (scheduled (or (string->number scheduled) 0)))
+                       summaries))))))))
 
 (define (db-get-previous-eval eval-id)
   "Return the successful evaluation preceeding EVAL-ID, for the same
@@ -1615,14 +1809,24 @@ GROUP BY Evaluations.specification;") ))
                (evaluations '()))
       (match rows
         (() (reverse evaluations))
-        (((specification evaluation)
-          . rest)
+        (((specification evaluation) . rest)
          (loop rest
-               (cons `((#:specification . ,specification)
-                       (#:evaluation
-                        . ,(and=> (string->number evaluation)
-                                  db-get-evaluation)))
-                     evaluations)))))))
+               (match (string->number evaluation)
+                 (#f evaluations)
+                 (id (cons (db-get-evaluation id) evaluations)))))))))
+
+(define-record-type* <evaluation-summary>
+  evaluation-summary make-evaluation-summary
+  evaluation-summary?
+  (id              evaluation-summary-id)
+  (status          evaluation-summary-status)
+  (total           evaluation-summary-total)
+  (succeeded       evaluation-summary-succeeded)
+  (failed          evaluation-summary-failed)
+  (scheduled       evaluation-summary-scheduled)
+  (start-time      evaluation-summary-start-time)
+  (checkout-time   evaluation-summary-checkout-time)
+  (completion-time evaluation-summary-completion-time))
 
 (define (db-get-evaluation-summary id)
   (with-db-worker-thread db
@@ -1642,16 +1846,17 @@ WHERE Evaluations.id = " id
 ORDER BY Evaluations.id ASC;"))
       ((id status timestamp checkouttime evaltime
            total succeeded failed scheduled)
-       `((#:id . ,(string->number id))
-         (#:status . ,(string->number status))
-         (#:total . ,(or (string->number total) 0))
-         (#:timestamp . ,(string->number timestamp))
-         (#:checkouttime . ,(string->number checkouttime))
-         (#:evaltime . ,(string->number evaltime))
-         (#:succeeded . ,(or (string->number succeeded) 0))
-         (#:failed . ,(or (string->number failed) 0))
-         (#:scheduled . ,(or (string->number scheduled) 0))))
-      (else #f))))
+       (evaluation-summary
+        (id (string->number id))
+        (status (string->number status))
+        (total (or (string->number total) 0))
+        (start-time (string->number timestamp))
+        (checkout-time (string->number checkouttime))
+        (completion-time (string->number evaltime))
+        (succeeded (or (string->number succeeded) 0))
+        (failed (or (string->number failed) 0))
+        (scheduled (or (string->number scheduled) 0))))
+      (_ #f))))
 
 (define (db-get-evaluation-absolute-summary evaluation)
   (expect-one-row
@@ -1665,7 +1870,7 @@ ORDER BY Evaluations.id ASC;"))
                     (number->string
                      (if (number? eval)
                          eval
-                         (assq-ref eval #:id))))
+                         (build-summary-evaluation-id eval))))
                   evaluations)
              ",")))
 
@@ -1686,11 +1891,17 @@ GROUP BY Jobs.evaluation;"))
         (() (reverse summary))
         (((total succeeded failed scheduled evaluation) . rest)
          (loop rest
-               (cons `((#:evaluation . ,(number evaluation))
-                       (#:total . ,(number total))
-                       (#:succeeded . ,(number succeeded))
-                       (#:failed . ,(number failed))
-                       (#:scheduled . ,(number scheduled)))
+               (cons (evaluation-summary
+                      (id (number evaluation))
+                      (total (number total))
+                      (succeeded (number succeeded))
+                      (failed (number failed))
+                      (scheduled (number scheduled))
+                      ;; FIXME: Info missing; use a different record type?
+                      (status *unspecified*)
+                      (start-time *unspecified*)
+                      (checkout-time *unspecified*)
+                      (completion-time *unspecified*))
                      summary)))))))
 
 (define (db-get-builds-query-min filters)
@@ -1819,6 +2030,12 @@ RETURNING id;"))
         ((id) id)
         (else #f)))))
 
+(define-record-type* <dashboard> dashboard make-dashboard
+  dashboard?
+  (id                 dashboard-id)
+  (specification-name dashboard-specification-name)
+  (job-ids            dashboard-job-ids))
+
 (define (db-get-dashboard id)
   "Return the dashboard specification and jobs with the given ID."
   (with-db-worker-thread db
@@ -1826,9 +2043,10 @@ RETURNING id;"))
             (exec-query/bind db "
 SELECT specification, jobs from Dashboards WHERE id = " id ";"))
       ((specification jobs)
-       `((#:specification . ,specification)
-         (#:jobs . ,jobs)))
-      (else #f))))
+       (dashboard (id id)
+                  (specification-name specification)
+                  (job-ids jobs)))
+      (_ #f))))
 
 (define (db-add-or-update-worker worker)
   "Insert WORKER into Worker table."
diff --git a/src/cuirass/http.scm b/src/cuirass/http.scm
index 2bf50d9..4ac8006 100644
--- a/src/cuirass/http.scm
+++ b/src/cuirass/http.scm
@@ -102,41 +102,52 @@
     (if bool 1 0))
 
   (define finished?
-    (>= (assq-ref build #:status) 0))
+    (>= (build-current-status build) 0))
 
-  `((#:id . ,(assq-ref build #:id))
-    (#:evaluation . ,(assq-ref build #:eval-id))
-    (#:jobset . ,(assq-ref build #:specification))
-    (#:job . ,(assq-ref build #:job-name))
+  `((#:id . ,(build-id build))
+    (#:evaluation . ,(build-evaluation-id build))
+    (#:jobset . ,(build-specification-name build))
+    (#:job . ,(build-job-name build))
 
     ;; Hydra's API uses "timestamp" as the time of the last useful event for
     ;; that build: evaluation or completion.
     (#:timestamp . ,(if finished?
-                        (assq-ref build #:stoptime)
-                        (assq-ref build #:timestamp)))
-
-    (#:starttime . ,(assq-ref build #:starttime))
-    (#:stoptime . ,(assq-ref build #:stoptime))
-    (#:derivation . ,(assq-ref build #:derivation))
-    (#:buildoutputs . ,(assq-ref build #:outputs))
-    (#:system . ,(assq-ref build #:system))
-    (#:nixname . ,(assq-ref build #:nix-name))
-    (#:buildstatus . ,(assq-ref build #:status))
-    (#:weather . ,(assq-ref build #:weather))
+                        (build-completion-time build)
+                        (build-creation-time build)))
+
+    (#:starttime . ,(build-start-time build))
+    (#:stoptime . ,(build-completion-time build))
+    (#:derivation . ,(build-derivation build))
+    (#:buildoutputs . ,(map (lambda (output)
+                              (list (output-name output)
+                                    (cons "path"
+                                          (output-item output))))
+                            (build-outputs build)))
+    (#:system . ,(build-system build))
+    (#:nixname . ,(build-nix-name build))
+    (#:buildstatus . ,(build-current-status build))
+    (#:weather . ,(build-current-weather build))
     (#:busy . ,(bool->int (eqv? (build-status started)
-                                (assq-ref build #:status))))
-    (#:priority . ,(assq-ref build #:priority))
+                                (build-current-status build))))
+    (#:priority . ,(build-priority build))
     (#:finished . ,(bool->int finished?))
-    (#:buildproducts . ,(list->vector
-                         (assq-ref build #:buildproducts)))))
+    (#:buildproducts . ,(list->vector (build-products build)))))
 
 (define (evaluation->json-object evaluation)
   "Turn EVALUATION into a representation suitable for 'json->scm'."
-  ;; XXX: Since #:checkouts is a list of alists, we must turn it into a vector
-  ;; so that 'json->scm' converts it to a JSON array.
-  `(,@(alist-delete #:checkouts evaluation eq?)
-    (#:checkouts . ,(list->vector
-                     (assq-ref evaluation #:checkouts)))))
+  `((#:id . ,(evaluation-id evaluation))
+    (#:specification . ,(evaluation-specification-name evaluation))
+    (#:status . ,(evaluation-current-status evaluation))
+    (#:timestamp . ,(evaluation-completion-time evaluation))
+    (#:checkouttime . ,(evaluation-checkout-time evaluation))
+    (#:evaltime . ,(evaluation-start-time evaluation))
+    (#:checkouts
+     . ,(list->vector
+         (map (lambda (checkout)
+                `((#:commit . ,(checkout-commit checkout))
+                  (#:channel . ,(checkout-channel checkout))
+                  (#:directory . ,(checkout-directory checkout))))
+              (evaluation-checkouts evaluation))))))
 
 (define (specification->json-object spec)
   "Turn SPEC into a representation suitable for 'json->scm'."
@@ -243,7 +254,7 @@ Hydra format."
                                border-high-time border-low-time
                                border-high-id border-low-id)
   "Return the HTML page representing EVALUATION."
-  (define id             (assq-ref evaluation #:id))
+  (define id             (evaluation-summary-id evaluation))
   (define builds-id-max  (db-get-builds-max id status))
   (define builds-id-min  (db-get-builds-min id status))
   (define specification  (db-get-evaluation-specification id))
@@ -252,8 +263,9 @@ Hydra format."
   (define checkouts      (latest-checkouts specification* id))
 
   (define builds
-    (vector->list
-     (handle-builds-request
+    (with-time-logging
+     "builds request for evaluation page"
+     (db-get-builds
       `((evaluation . ,id)
         (status . ,(and=> status string->symbol))
         ,@(if paginate?
@@ -366,7 +378,7 @@ Hydra format."
      (machine-status name workers
                      (map (lambda (worker)
                             (filter (lambda (build)
-                                      (string=? (assq-ref build #:worker)
+                                      (string=? (build-worker build)
                                                 (worker-name worker)))
                                     builds))
                           workers)
@@ -452,7 +464,7 @@ passed, only display JOBS targeting this SYSTEM."
                (car systems)))
          (dashboard (db-get-dashboard dashboard-id))
          (names (and dashboard
-                     (assq-ref dashboard #:jobs)))
+                     (dashboard-job-ids dashboard)))
          (prev (db-get-previous-eval evaluation-id))
          (next (db-get-next-eval evaluation-id)))
     (html-page
@@ -612,6 +624,12 @@ passed, only display JOBS targeting this SYSTEM."
              #:body (string-append "Resource not found: "
                                    resource_name)))
 
+  (define (job->alist job)
+    ;; Convert JOB to an alist representation suitable for JSON conversion.
+    `((build . ,(job-build-id job))
+      (status . ,(job-status job))
+      (name . ,(job-name job))))
+
   (log-info "~a ~a" (request-method request)
             (uri-path (request-uri request)))
 
@@ -681,7 +699,7 @@ passed, only display JOBS targeting this SYSTEM."
 
     (('GET "admin" "evaluation" id "cancel")
      (let* ((eval (db-get-evaluation id))
-            (specification (assq-ref eval #:specification)))
+            (specification (evaluation-specification-name eval)))
        (db-cancel-pending-builds! (string->number id))
        (respond
         (build-response
@@ -693,7 +711,7 @@ passed, only display JOBS targeting this SYSTEM."
 
     (('GET "admin" "evaluation" id "restart")
      (let* ((eval (db-get-evaluation id))
-            (specification (assq-ref eval #:specification)))
+            (specification (evaluation-specification-name eval)))
        (db-restart-evaluation! (string->number id))
        (respond
         (build-response
@@ -705,7 +723,7 @@ passed, only display JOBS targeting this SYSTEM."
 
     (('GET "admin" "evaluation" id "retry")
      (let* ((eval (db-get-evaluation id))
-            (specification (assq-ref eval #:specification)))
+            (specification (evaluation-specification-name eval)))
        (db-retry-evaluation! (string->number id))
        (respond
         (build-response
@@ -747,32 +765,33 @@ passed, only display JOBS targeting this SYSTEM."
            (respond-build-not-found id))))
     (('GET "build" (= string->number id) "details")
      (let* ((build (and id (db-get-build id)))
-            (products (and build (assoc-ref build #:buildproducts)))
+            (products (and build (build-products build)))
             (dependencies
              (and build
                   (db-get-builds
-                   `((ids . ,(assoc-ref build #:builddependencies))))))
+                   `((ids . ,(build-dependencies/id build))))))
             (history
-             (db-get-builds
-              `((jobset . ,(assq-ref build #:specification))
-                (job . ,(assq-ref build #:job-name))
-                (oldevaluation . ,(assq-ref build #:eval-id))
-                (status . done)
-                (order . evaluation)
-                (nr . 10)))))
+             (and build
+                  (db-get-builds
+                   `((jobset . ,(build-specification-name build))
+                     (job . ,(build-job-name build))
+                     (oldevaluation . ,(build-evaluation-id build))
+                     (status . done)
+                     (order . evaluation)
+                     (nr . 10))))))
        (if build
            (respond-html
             (html-page
              (string-append "Build " (number->string id))
              (build-details build dependencies products history)
-             `(((#:name . ,(assq-ref build #:specification))
+             `(((#:name . ,(build-specification-name build))
                 (#:link
                  . ,(string-append "/jobset/"
-                                   (assq-ref build #:specification)))))))
+                                   (build-specification-name build)))))))
            (respond-build-not-found id))))
     (('GET "build" (= string->number id) "log" "raw")
      (let* ((build (and id (db-get-build id)))
-            (log   (and build (assq-ref build #:log))))
+            (log   (and build (build-log build))))
        (if (and log (file-exists? log))
            (respond-compressed-file log)
            (respond-not-found (uri->string (request-uri request))))))
@@ -780,7 +799,7 @@ passed, only display JOBS targeting this SYSTEM."
      (let ((output (db-get-output
                     (string-append (%store-prefix) "/" id))))
        (if output
-           (let ((build (db-get-build (assq-ref output #:derivation))))
+           (let ((build (db-get-build (output-derivation output))))
              (respond-json
               (object->json-string
                (append output
@@ -793,11 +812,12 @@ passed, only display JOBS targeting this SYSTEM."
            (respond-json
             (object->json-string
              (list->vector
-              (db-get-jobs eval-id
-                           `((names
-                              . ,(and=> (assq-ref params 'names)
-                                        (cut string-split <> #\,)))
-                             ,@params)))))
+              (map job->alist
+                   (db-get-jobs eval-id
+                                `((names
+                                   . ,(and=> (assq-ref params 'names)
+                                             (cut string-split <> #\,)))
+                                  ,@params))))))
            (respond-json-with-error 500 "Parameter not defined!"))))
     (('GET "api" "jobs" "history")
      (let* ((params (request-parameters request))
@@ -846,7 +866,7 @@ passed, only display JOBS targeting this SYSTEM."
            (respond-json-with-error 500 "Parameter not defined!"))))
     (('GET "api" "evaluations")
      (let* ((params (request-parameters request))
-            (spec (assq-ref params 'spec)) ;optional
+            (spec (assq-ref params 'spec))        ;optional
             ;; 'nr parameter is mandatory to limit query size.
             (nr (assq-ref params 'nr)))
        (if nr
@@ -900,11 +920,7 @@ passed, only display JOBS targeting this SYSTEM."
                        (db-get-specifications)
                        evals
                        (db-get-evaluations-absolute-summary
-                        (map (lambda (e)
-                               `((#:id . ,(assq-ref
-                                           (assq-ref e #:evaluation)
-                                           #:id))))
-                             evals))
+                        (map evaluation-id evals))
                        ;; Get all the latest evaluations, regardless of their
                        ;; status.
                        (db-get-latest-evaluations #:status #f)))
@@ -912,13 +928,12 @@ passed, only display JOBS targeting this SYSTEM."
     (('GET "dashboard" id)
      (let ((dashboard (db-get-dashboard id)))
        (if dashboard
-           (let* ((spec (assq-ref dashboard #:specification))
+           (let* ((spec (dashboard-specification-name dashboard))
                   (evaluations (db-get-latest-evaluations))
                   (evaluation
                    (any (lambda (eval)
-                          (and (string=? (assq-ref eval #:specification)
-                                         spec)
-                               (assq-ref eval #:evaluation)))
+                          (string=? (evaluation-specification-name eval)
+                                    spec))
                         evaluations))
                   (uri
                    (string->uri-reference
@@ -1144,9 +1159,8 @@ passed, only display JOBS targeting this SYSTEM."
        "Workers status"
        (let* ((workers (db-get-workers))
               (builds (db-worker-current-builds))
-              (builds*
-               (db-get-build-percentages builds)))
-         (workers-status workers builds*))
+              (percentages (db-get-build-percentages builds)))
+         (workers-status workers builds percentages))
        '())))
 
     (('GET "metrics")
diff --git a/src/cuirass/notification.scm b/src/cuirass/notification.scm
index 5249cb9..1123b6b 100644
--- a/src/cuirass/notification.scm
+++ b/src/cuirass/notification.scm
@@ -95,7 +95,7 @@
 
 (define (build-weather-text build)
   "Return the build weather string."
-  (let ((weather (assq-ref build #:weather)))
+  (let ((weather (build-current-weather build)))
     (cond
      ((= weather weather-success)
       "fixed")
@@ -104,14 +104,14 @@
 
 (define (build-details-url build)
   "Return the build details URL for BUILD."
-  (let ((id (assq-ref build #:id))
+  (let ((id (build-id build))
         (url (or (%cuirass-url) "")))
     (string-append url "/build/" (number->string id) "/details")))
 
 (define (notification-subject build)
   "Return the subject for the given NOTIFICATION."
-  (let* ((job-name (assq-ref build #:job-name))
-         (specification (assq-ref build #:specification))
+  (let* ((job-name (build-job-name build))
+         (specification (build-specification-name build))
          (weather-text (build-weather-text build)))
     (format #f "Build ~a on ~a is ~a."
             job-name specification weather-text)))
@@ -119,8 +119,8 @@
 (define (notification-text build)
   "Return the text for the given NOTIFICATION."
   (let* ((url (build-details-url build))
-         (job-name (assq-ref build #:job-name))
-         (specification (assq-ref build #:specification))
+         (job-name (build-job-name build))
+         (specification (build-specification-name build))
          (weather-text (build-weather-text build)))
     (format #f "The build ~a for specification ~a is ~a. You can find \
 the detailed information about this build here: ~a."
diff --git a/src/cuirass/rss.scm b/src/cuirass/rss.scm
index 6a64d06..6cd4d35 100644
--- a/src/cuirass/rss.scm
+++ b/src/cuirass/rss.scm
@@ -130,23 +130,23 @@ list ATTRS and the child nodes in BODY."
 
 (define (build-details-url build)
   "Return the build details URL for BUILD."
-  (let ((id (assq-ref build #:id))
+  (let ((id (build-id build))
         (url (or (%cuirass-url) "")))
     (string-append url "/build/" (number->string id) "/details")))
 
 (define* (build->rss-item build)
   "Convert BUILD into an RSS <item> node."
   (let* ((url (build-details-url build))
-         (id (assq-ref build #:id))
-         (job-name (assq-ref build #:job-name))
-         (specification (assq-ref build #:specification))
-         (weather  (assq-ref build #:weather))
+         (id (build-id build))
+         (job-name (build-job-name build))
+         (specification (build-specification-name build))
+         (weather (build-current-weather build))
          (weather-text (cond
                         ((= weather (build-weather new-success))
                          "fixed")
                         ((= weather (build-weather new-failure))
                          "broken")))
-         (stoptime (assq-ref build #:stoptime)))
+         (stoptime (build-completion-time build)))
     `(item
       (guid ,url)
       (title
diff --git a/src/cuirass/scripts/evaluate.scm b/src/cuirass/scripts/evaluate.scm
index fa341fa..2ce0294 100644
--- a/src/cuirass/scripts/evaluate.scm
+++ b/src/cuirass/scripts/evaluate.scm
@@ -1,5 +1,5 @@
 ;;;; evaluate -- convert a specification to a job list
-;;; Copyright © 2016, 2018, 2022 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2016, 2018, 2022, 2023 Ludovic Courtès <ludo@gnu.org>
 ;;; Copyright © 2016, 2017 Mathieu Lirzin <mthl@gnu.org>
 ;;; Copyright © 2017, 2018, 2021 Mathieu Othacehe <othacehe@gnu.org>
 ;;; Copyright © 2018 Clément Lassieur <clement@lassieur.org>
@@ -27,7 +27,7 @@
   #:use-module (guix derivations)
   #:use-module (guix inferior)
   #:use-module (guix monads)
-  #:use-module (guix store)
+  #:use-module ((guix store) #:hide (build))
   #:autoload   (guix ui) (show-what-to-build*)
   #:use-module (srfi srfi-1)
   #:use-module (ice-9 match)
@@ -38,14 +38,36 @@
   "Return the list of CHANNEL-INSTANCE records describing the given
 CHECKOUTS."
   (map (lambda (checkout)
-         (let ((channel (assq-ref checkout #:channel))
-               (directory (assq-ref checkout #:directory))
-               (commit (assq-ref checkout #:commit)))
+         (let ((channel (checkout-channel checkout))
+               (directory (checkout-directory checkout))
+               (commit (checkout-commit checkout)))
            (checkout->channel-instance directory
                                        #:name channel
                                        #:commit commit)))
        checkouts))
 
+(define (user-alists->builds jobs specification-name evaluation-id)
+  "Convert JOBS, the user-supplied list of job alists for SPECIFICATION-NAME 
and
+EVALUATION-ID, into a list of <build> records."
+  (map (lambda (alist)
+         (build (evaluation-id evaluation-id)
+                (specification-name specification-name)
+                (job-name (assq-ref alist #:job-name))
+                (nix-name (assq-ref alist #:nix-name))
+                (system (assq-ref alist #:system))
+                (timeout (assq-ref alist #:timeout))
+                (max-silent-time (assq-ref alist #:max-silent-time))
+                (derivation (assq-ref alist #:derivation))
+                (dependencies (assq-ref alist #:inputs))
+                (outputs
+                 (map (match-lambda
+                        ((name . item)
+                         (output (name name)
+                                 (item item)
+                                 (derivation derivation))))
+                      (or (assq-ref alist #:outputs) '())))))
+       jobs))
+
 (define* (inferior-evaluation store profile
                               #:key
                               eval-id instances
@@ -75,7 +97,14 @@ Pass the BUILD, CHANNELS and SYSTEMS arguments to the 
EVAL-PROC procedure."
             `(lambda (store)
                (,eval-proc store ',args)))))
       (close-inferior inferior)
-      (db-register-builds jobs eval-id spec))))
+
+      ;; EVAL-PROC returns a list of job alists: this has the advantage of
+      ;; being serializable and immune to ABI and API changes.  Here, convert
+      ;; it to <build> records for internal consumption.
+      (db-register-builds (user-alists->builds jobs
+                                               (specification-name spec)
+                                               eval-id)
+                          spec))))
 
 (define (channel-instances->profile instances)
   "Return a directory containing a guix filetree defined by INSTANCES, a list
diff --git a/src/cuirass/scripts/remote-server.scm 
b/src/cuirass/scripts/remote-server.scm
index 580d112..4bc5832 100644
--- a/src/cuirass/scripts/remote-server.scm
+++ b/src/cuirass/scripts/remote-server.scm
@@ -255,10 +255,10 @@ be used to reply to the worker."
                     (worker-name worker)))
        (let ((build (pop-build name)))
          (if build
-             (let ((derivation (assq-ref build #:derivation))
-                   (priority (assq-ref build #:priority))
-                   (timeout (assq-ref build #:timeout))
-                   (max-silent (assq-ref build #:max-silent)))
+             (let ((derivation (build-derivation build))
+                   (priority (build-priority build))
+                   (timeout (build-timeout build))
+                   (max-silent (build-max-silent-time build)))
                (when (and (%debug) worker)
                  (log-debug "~a (~a): build ~a submitted."
                             (worker-address worker)
diff --git a/src/cuirass/templates.scm b/src/cuirass/templates.scm
index efbb78e..ee49c67 100644
--- a/src/cuirass/templates.scm
+++ b/src/cuirass/templates.scm
@@ -32,13 +32,11 @@
   #:use-module (guix derivations)
   #:use-module (guix packages)
   #:use-module (guix progress)
-  #:use-module (guix store)
+  #:use-module ((guix store) #:hide (build))
   #:use-module ((guix utils) #:select (string-replace-substring
                                        version>?))
   #:use-module (cuirass config)
-  #:use-module ((cuirass database) #:select (build-status
-                                             build-weather
-                                             evaluation-status))
+  #:use-module (cuirass database)
   #:use-module (cuirass remote)
   #:use-module (cuirass specification)
   #:export (html-page
@@ -269,14 +267,13 @@ system whose names start with " (code "guile-") ":" (br)
 (define (specifications-table specs evaluations summaries latest-evaluations)
   "Return HTML for the SPECS table."
   (define (spec->latest-eval-ok name)
-    (find (lambda (s)
-            (string=? (assq-ref s #:specification) name))
+    (find (lambda (e)
+            (string=? (evaluation-specification-name e) name))
           evaluations))
 
   (define (spec->latest-eval name)
-    (any (lambda (s)
-           (and (string=? (assq-ref s #:specification) name)
-                (assq-ref s #:evaluation)))
+    (find (lambda (e)
+            (string=? (evaluation-specification-name e) name))
           latest-evaluations))
 
   (define (eval-summary eval)
@@ -288,8 +285,8 @@ system whose names start with " (code "guile-") ":" (br)
           summaries))
 
   (define (summary->percentage summary)
-    (let ((total (assq-ref summary #:total))
-          (succeeded (assq-ref summary #:succeeded)))
+    (let ((total (evaluation-summary-total summary))
+          (succeeded (evaluation-summary-succeeded summary)))
       (nearest-exact-integer (* 100 (/ succeeded total)))))
 
   `((div (@ (class "d-flex flex-row mb-3"))
@@ -381,7 +378,7 @@ system whose names start with " (code "guile-") ":" (br)
                                  (specification-name spec)))
                                (last-eval-status-ok?
                                 (and last-eval
-                                     (<= (assq-ref last-eval #:status)
+                                     (<= (evaluation-current-status last-eval)
                                          (evaluation-status succeeded))))
                                (percentage
                                 (and summary (summary->percentage summary)))
@@ -406,11 +403,11 @@ system whose names start with " (code "guile-") ":" (br)
                               (div
                                (@ (class "job-rel d-none"))
                                ,(successful-build-badge
-                                 (assq-ref summary #:succeeded))
+                                 (evaluation-summary-succeeded summary))
                                ,(failed-build-badge
-                                 (assq-ref summary #:failed))
+                                 (evaluation-summary-failed summary))
                                ,(scheduled-build-badge
-                                 (assq-ref summary #:scheduled)))))
+                                 (evaluation-summary-scheduled summary)))))
                            ((and last-eval (not last-eval-status-ok?))
                             `((center
                                ,@(evaluation-badges last-eval #f))))
@@ -668,20 +665,20 @@ the existing SPEC otherwise."
 
 (define (build-details build dependencies products history)
   "Return HTML showing details for the BUILD."
-  (define status (assq-ref build #:status))
-  (define weather (assq-ref build #:weather))
+  (define status (build-current-status build))
+  (define weather (build-current-weather build))
 
   (define evaluation
-    (assq-ref build #:eval-id))
+    (build-evaluation-id build))
 
   (define (find-dependency id)
     (find (lambda (build)
-            (eq? (assoc-ref build #:id) id))
+            (= (build-id build) id))
           dependencies))
 
   (define (history-table-row build)
     (define status
-      (assq-ref build #:status))
+      (build-current-status build))
 
     `(tr
       (td (span (@ (class ,(status-class status))
@@ -689,11 +686,11 @@ the existing SPEC otherwise."
                    (aria-hidden "true"))
                 ""))
       (th (@ (scope "row"))
-          (a (@ (href "/build/" ,(assq-ref build #:id) "/details"))
-             ,(assq-ref build #:id)))
-      (td ,(assq-ref build #:nix-name))
+          (a (@ (href "/build/" ,(build-id build) "/details"))
+             ,(build-id build)))
+      (td ,(build-nix-name build))
       (td ,(if (completed? status)
-               (time->string (assq-ref build #:stoptime))
+               (time->string (build-completion-time build))
                "—"))))
 
   `((div (@ (class "d-flex flex-row mb-3"))
@@ -712,13 +709,13 @@ the existing SPEC otherwise."
                   (li (@ (role "menuitem"))
                       (a (@ (class "dropdown-item")
                             (href "/admin/build/"
-                                  ,(assq-ref build #:id) "/restart"))
+                                  ,(build-id build) "/restart"))
                          " Restart")))))
     (table
      (@ (class "table table-sm table-hover"))
      (tbody
       (tr (th "Build ID")
-          (td ,(assq-ref build #:id)))
+          (td ,(build-id build)))
       (tr (th "Evaluation")
           (td (a (@ (href ,(string-append "/eval/"
                                           (number->string evaluation))))
@@ -728,13 +725,13 @@ the existing SPEC otherwise."
                        (title ,(status-title status)))
                     ,(string-append " " (status-title status)))))
       (tr (th "System")
-          (td ,(assq-ref build #:system)))
+          (td ,(build-system build)))
       (tr (th "Name")
-          (td ,(assq-ref build #:nix-name)))
+          (td ,(build-nix-name build)))
       (tr (th "Duration")
           (td ,(let ((timestamp (time-second (current-time time-utc)))
-                     (start (assq-ref build #:starttime))
-                     (stop  (assq-ref build #:stoptime)))
+                     (start (build-start-time build))
+                     (stop  (build-completion-time build)))
                  (cond
                   ((and (> start 0) (> stop 0))
                    (string-append (number->string (- stop start))
@@ -745,7 +742,7 @@ the existing SPEC otherwise."
                   (else "—")))))
       (tr (th "Finished")
           (td ,(if (completed? status)
-                   (time->string (assq-ref build #:stoptime))
+                   (time->string (build-completion-time build))
                    "—")))
       (tr (th "Weather")
           (td (span (@ (class ,(weather-class weather))
@@ -758,16 +755,16 @@ the existing SPEC otherwise."
                        (= (build-status succeeded) status)
                        (= (build-status failed) status)
                        (= (build-status canceled) status))
-                   `(a (@ (href "/build/" ,(assq-ref build #:id) "/log/raw"))
+                   `(a (@ (href "/build/" ,(build-id build) "/log/raw"))
                        "raw")
                    "—")))
       (tr (th "Derivation")
-          (td (pre ,(assq-ref build #:derivation))))
+          (td (pre ,(build-derivation build))))
       (tr (th "Dependencies")
           (td
            (@ (class "dependencies"))
            ,@(let ((dependencies
-                    (assq-ref build #:builddependencies))
+                    (build-dependencies/id build))
                    (max-items 10))
                (if (> (length dependencies) 0)
                    `(,(map (lambda (id index)
@@ -800,18 +797,18 @@ the existing SPEC otherwise."
                            '()))
                    '("—")))))
       (tr (th "Outputs")
-          (td ,(map (match-lambda ((out (#:path . path))
-                                   `(pre ,path)))
-                    (assq-ref build #:outputs))))
+          (td ,(map (lambda (output)
+                      `(pre ,(output-item output)))
+                    (build-outputs build))))
       ,@(if (null? products)
             '()
             (let ((product-items
                    (map
                     (lambda (product)
-                      (let* ((id (assq-ref product #:id))
-                             (size (assq-ref product #:file-size))
-                             (type (assq-ref product #:type))
-                             (path (assq-ref product #:path))
+                      (let* ((id (build-product-id product))
+                             (size (build-product-file-size product))
+                             (type (build-product-type product))
+                             (path (build-product-file product))
                              (href (format #f "/download/~a" id)))
                         `(a (@ (href ,href))
                             (li (@ (class "list-group-item"))
@@ -880,26 +877,28 @@ the existing SPEC otherwise."
   (let ((changes
          (string-join
           (map (lambda (checkout)
-                 (let ((input (assq-ref checkout #:channel))
-                       (commit (assq-ref checkout #:commit)))
+                 (let ((input (checkout-channel checkout))
+                       (commit (checkout-commit checkout)))
                    (format #f "~a → ~a" input (substring commit 0 7))))
                checkouts)
           ", ")))
     (if (string=? changes "") '(em "None") changes)))
 
 (define (evaluation-badges evaluation absolute)
-  (let ((status (assq-ref evaluation #:status)))
+  (let ((status (build-summary-status evaluation)))
     (if (= status (evaluation-status started))
         '((em "In progress…"))
         (cond
          ((= status (evaluation-status failed))
-          `((a (@ (href "/eval/" ,(assq-ref evaluation #:id) "/log/raw")
+          `((a (@ (href "/eval/" ,(build-summary-evaluation-id evaluation)
+                        "/log/raw")
                   (class "oi oi-x text-danger")
                   (title "Failed")
                   (aria-hidden "true"))
                "")))
          ((= status (evaluation-status aborted))
-          `((a (@ (href "/eval/" ,(assq-ref evaluation #:id) "/log/raw")
+          `((a (@ (href "/eval/" ,(build-summary-evaluation-id evaluation)
+                        "/log/raw")
                   (class "oi oi-x text-warning")
                   (title "Aborted")
                   (aria-hidden "true"))
@@ -907,28 +906,28 @@ the existing SPEC otherwise."
          ((= status (evaluation-status succeeded))
           `((div
              (@ (class "job-abs d-none"))
-             ,(successful-build-badge (assq-ref absolute #:succeeded))
-             ,(failed-build-badge (assq-ref absolute #:failed))
-             ,(scheduled-build-badge (assq-ref absolute #:scheduled)))
+             ,(successful-build-badge (evaluation-summary-succeeded absolute))
+             ,(failed-build-badge (evaluation-summary-failed absolute))
+             ,(scheduled-build-badge (evaluation-summary-scheduled absolute)))
             (div
              (@ (class "job-rel"))
-             ,(successful-build-badge (assq-ref evaluation #:succeeded)
+             ,(successful-build-badge (evaluation-summary-succeeded evaluation)
                                       (string-append
                                        "/eval/"
                                        (number->string
-                                        (assq-ref evaluation #:id))
+                                        (evaluation-summary-id evaluation))
                                        "?status=succeeded"))
-             ,(failed-build-badge (assq-ref evaluation #:failed)
+             ,(failed-build-badge (evaluation-summary-failed evaluation)
                                   (string-append
                                    "/eval/"
                                    (number->string
-                                    (assq-ref evaluation #:id))
+                                    (evaluation-summary-id evaluation))
                                    "?status=failed"))
-             ,(scheduled-build-badge (assq-ref evaluation #:scheduled)
+             ,(scheduled-build-badge (evaluation-summary-scheduled evaluation)
                                      (string-append
                                       "/eval/"
                                       (number->string
-                                       (assq-ref evaluation #:id))
+                                       (evaluation-summary-id evaluation))
                                       "?status=pending")))))))))
 
 (define* (evaluation-info-table name evaluations id-min id-max
@@ -936,9 +935,11 @@ the existing SPEC otherwise."
   "Return HTML for the EVALUATION table NAME. ID-MIN and ID-MAX are
   global minimal and maximal id."
   (define (eval-absolute-summary eval)
-    (find (lambda (e)
-            (= (assq-ref e #:evaluation) (assq-ref eval #:id)))
-          absolute-summary))
+    (pk 'abs-sum eval absolute-summary '====>
+     (find (lambda (e)
+             (= (evaluation-summary-id e)
+                (build-summary-evaluation-id eval)))
+           absolute-summary)))
 
   `((div (@ (class "d-flex flex-row mb-3"))
          (div (@ (class "lead mr-auto"))
@@ -976,15 +977,17 @@ the existing SPEC otherwise."
                (th (@ (scope "col")) "Action")))
              (tbody
               ,@(map
-                 (lambda (row)
+                 (lambda (summary)
                    `(tr (th (@ (scope "row"))
-                            (a (@ (href "/eval/" ,(assq-ref row #:id)))
-                               ,(assq-ref row #:id)))
-                        (td ,(input-changes (assq-ref row #:checkouts)))
+                            (a (@ (href
+                                   "/eval/"
+                                   ,(build-summary-evaluation-id summary)))
+                               ,(build-summary-evaluation-id summary)))
+                        (td ,(input-changes (build-summary-checkouts summary)))
                         (td
-                         ,@(evaluation-badges row
-                                              (eval-absolute-summary row)))
-                        ,(let* ((id (assq-ref row #:id))
+                         ,@(evaluation-badges summary
+                                              (eval-absolute-summary summary)))
+                        ,(let* ((id (build-summary-evaluation-id summary))
                                 (title
                                  (string-append "Dashboard evaluation "
                                                 (number->string id))))
@@ -994,7 +997,7 @@ the existing SPEC otherwise."
                                  (@ (class
                                       ,(string-append
                                         "oi oi-monitor d-inline-block "
-                                        (if (eq? (assq-ref row #:status)
+                                        (if (eq? (build-summary-status summary)
                                                  (evaluation-status succeeded))
                                             "visible"
                                             "invisible")))
@@ -1026,25 +1029,25 @@ the existing SPEC otherwise."
                                       (li (@ (role "menuitem"))
                                           (a (@ (class "dropdown-item")
                                                 (href "/admin/evaluation/"
-                                                      ,(assq-ref row #:id)
+                                                      
,(build-summary-evaluation-id summary)
                                                       "/cancel"))
                                              " Cancel pending builds"))
                                       (li (@ (role "menuitem"))
                                           (a (@ (class "dropdown-item")
                                                 (href "/admin/evaluation/"
-                                                      ,(assq-ref row #:id)
+                                                      
,(build-summary-evaluation-id summary)
                                                       "/restart"))
                                              " Restart all builds"))
                                       (li (@ (role "menuitem"))
                                           (a (@ (class "dropdown-item")
                                                 (href "/admin/evaluation/"
-                                                      ,(assq-ref row #:id)
+                                                      
,(build-summary-evaluation-id summary)
                                                       "/retry"))
                                              " Retry the evaluation")))))))))
                  evaluations)))))
     ,(if (null? evaluations)
          (pagination "" "" "" "")
-         (let* ((eval-ids (map (cut assq-ref <> #:id) evaluations))
+         (let* ((eval-ids (map build-summary-evaluation-id evaluations))
                 (page-id-min (last eval-ids))
                 (page-id-max (first eval-ids)))
            (pagination
@@ -1127,10 +1130,10 @@ and BUILD-MAX are global minimal and maximal (stoptime, 
rowid) pairs."
 
   (define (table-row build)
     (define status
-      (assq-ref build #:buildstatus))
+      (build-current-status build))
 
     (define weather
-      (assq-ref build #:weather))
+      (build-current-weather build))
 
     `(tr
       (td (span (@ (class ,(status-class status))
@@ -1142,25 +1145,25 @@ and BUILD-MAX are global minimal and maximal (stoptime, 
rowid) pairs."
                    (aria-hidden "true"))
                 ""))
       (th (@ (scope "row"))
-          (a (@ (href "/build/" ,(assq-ref build #:id) "/details"))
-             ,(assq-ref build #:id)))
-      (td ,(assq-ref build #:jobset))
+          (a (@ (href "/build/" ,(build-id build) "/details"))
+             ,(build-id build)))
+      (td ,(build-specification-name build))
       (td ,(if (completed? status)
-               (time->string (assq-ref build #:stoptime))
+               (time->string (build-completion-time build))
                "—"))
-      (td ,(assq-ref build #:job))
-      (td ,(assq-ref build #:nixname))
-      (td ,(assq-ref build #:system))
+      (td ,(build-job-name build))
+      (td ,(build-nix-name build))
+      (td ,(build-system build))
       (td ,(if (completed-with-logs? status)
-               `(a (@ (href "/build/" ,(assq-ref build #:id) "/log/raw"))
+               `(a (@ (href "/build/" ,(build-id build) "/log/raw"))
                    "raw")
                "—"))))
 
-  (define (build-id build)
+  (define (page-boundary-build-id build)
     (match build
       ((stoptime id) id)))
 
-  (define (build-stoptime build)
+  (define (page-boundary-build-stoptime build)
     (match build
       ((stoptime id) stoptime)))
 
@@ -1173,36 +1176,36 @@ and BUILD-MAX are global minimal and maximal (stoptime, 
rowid) pairs."
              (tbody ,@(map table-row builds)))))
     ,(if (null? builds)
          (pagination "" "" "" "")
-         (let* ((build-time-ids (map (lambda (row)
-                                       (list (assq-ref row #:stoptime)
-                                             (assq-ref row #:id)))
+         (let* ((build-time-ids (map (lambda (build)
+                                       (list (build-completion-time build)
+                                             (build-id build)))
                                      builds))
                 (page-build-min (last build-time-ids))
                 (page-build-max (first build-time-ids)))
            (pagination
             (format
              #f "?border-high-time=~d&border-high-id=~d~@[&status=~a~]"
-             (build-stoptime build-max)
-             (1+ (build-id build-max))
+             (page-boundary-build-stoptime build-max)
+             (1+ (page-boundary-build-id build-max))
              status)
             (if (equal? page-build-max build-max)
                 ""
                 (format
                  #f "?border-low-time=~d&border-low-id=~d~@[&status=~a~]"
-                 (build-stoptime page-build-max)
-                 (build-id page-build-max)
+                 (page-boundary-build-stoptime page-build-max)
+                 (page-boundary-build-id page-build-max)
                  status))
             (if (equal? page-build-min build-min)
                 ""
                 (format
                  #f "?border-high-time=~d&border-high-id=~d~@[&status=~a~]"
-                 (build-stoptime page-build-min)
-                 (build-id page-build-min)
+                 (page-boundary-build-stoptime page-build-min)
+                 (page-boundary-build-id page-build-min)
                  status))
             (format
              #f "?border-low-time=~d&border-low-id=~d~@[&status=~a~]"
-             (build-stoptime build-min)
-             (1- (build-id build-min))
+             (page-boundary-build-stoptime build-min)
+             (1- (page-boundary-build-id build-min))
              status))))))
 
 ;; FIXME: Copied from (guix scripts describe).
@@ -1255,7 +1258,7 @@ the nearest exact even integer."
                (th (@ (class "border-0") (scope "col")) "Commit")))
           (tbody
            ,@(map (lambda (checkout)
-                    (let* ((name  (assq-ref checkout #:channel))
+                    (let* ((name  (checkout-channel checkout))
                            (channel (find (lambda (channel)
                                             (eq? (channel-name channel)
                                                  name))
@@ -1264,7 +1267,7 @@ the nearest exact even integer."
                       ;; inputs.
                       (if channel
                           (let ((url (channel-url channel))
-                                (commit (assq-ref checkout #:commit)))
+                                (commit (checkout-commit checkout)))
                             `(tr (td ,url)
                                  (td (code ,(commit-hyperlink url commit)))))
                           '())))
@@ -1298,14 +1301,13 @@ the nearest exact even integer."
                                  builds-id-min builds-id-max)
   "Return HTML for an evaluation page, containing a table of builds for that
 evaluation."
-  (define id        (assq-ref evaluation #:id))
-  (define total     (assq-ref evaluation #:total))
-  (define succeeded (assq-ref evaluation #:succeeded))
-  (define timestamp (assq-ref evaluation #:timestamp))
-  (define evaltime  (assq-ref evaluation #:evaltime))
-  (define failed    (assq-ref evaluation #:failed))
-  (define scheduled (assq-ref evaluation #:scheduled))
-  (define spec      (assq-ref evaluation #:spec))
+  (define id        (evaluation-summary-id evaluation))
+  (define total     (evaluation-summary-total evaluation))
+  (define succeeded (evaluation-summary-succeeded evaluation))
+  (define timestamp (evaluation-summary-start-time evaluation))
+  (define evaltime  (evaluation-summary-completion-time evaluation))
+  (define failed    (evaluation-summary-failed evaluation))
+  (define scheduled (evaluation-summary-scheduled evaluation))
 
   (define duration  (- evaltime timestamp))
 
@@ -1411,7 +1413,7 @@ and BUILD-MAX are global minimal and maximal row 
identifiers."
 
   (define (table-row build)
     (define status
-      (assq-ref build #:buildstatus))
+      (build-current-status build))
 
     `(tr
       (td (span (@ (class ,(status-class status))
@@ -1419,17 +1421,17 @@ and BUILD-MAX are global minimal and maximal row 
identifiers."
                    (aria-hidden "true"))
                 ""))
       (th (@ (scope "row"))
-          (a (@ (href "/build/" ,(assq-ref build #:id) "/details"))
-             ,(assq-ref build #:id)))
-      (td ,(assq-ref build #:jobset))
+          (a (@ (href "/build/" ,(build-id build) "/details"))
+             ,(build-id build)))
+      (td ,(build-job-name build))
       (td ,(if (completed? status)
-               (time->string (assq-ref build #:stoptime))
+               (time->string (build-completion-time build))
                "—"))
-      (td ,(assq-ref build #:job))
-      (td ,(assq-ref build #:nixname))
-      (td ,(assq-ref build #:system))
+      (td ,(build-job-name build))
+      (td ,(build-nix-name build))
+      (td ,(build-system build))
       (td ,(if (completed-with-logs? status)
-               `(a (@ (href "/build/" ,(assq-ref build #:id) "/log/raw"))
+               `(a (@ (href "/build/" ,(build-id build) "/log/raw"))
                    "raw")
                "—"))))
 
@@ -1474,13 +1476,13 @@ and BUILD-MAX are global minimal and maximal row 
identifiers."
   (define (build-row build)
     `(tr
       (th (@ (scope "row"))
-          (a (@ (href "/build/" ,(assq-ref build #:id) "/details"))
-             ,(assq-ref build #:id)))
-      (td ,(assq-ref build #:job-name))
+          (a (@ (href "/build/" ,(build-id build) "/details"))
+             ,(build-id build)))
+      (td ,(build-job-name build))
       (td ,(time->string
-            (assq-ref build #:starttime)))
-      (td ,(assq-ref build #:system))
-      (td (a (@ (href "/build/" ,(assq-ref build #:id) "/log/raw"))
+            (build-start-time build)))
+      (td ,(build-system build))
+      (td (a (@ (href "/build/" ,(build-id build) "/log/raw"))
              "raw"))))
 
   `((p (@ (class "lead")) "Running builds")
@@ -1705,7 +1707,7 @@ completed builds divided by the time required to build 
them.")
                          #:labels '("Pending builds")
                          #:colors (list "#3e95cd")))))
 
-(define (workers-status workers builds)
+(define (workers-status workers builds percentages)
   (define (machine-row machine)
     (let* ((workers (sort (filter-map
                            (lambda (worker)
@@ -1719,7 +1721,7 @@ completed builds divided by the time required to build 
them.")
                    (match (filter
                            (lambda (build)
                              (let ((build-worker
-                                    (assq-ref build #:worker)))
+                                    (build-worker build)))
                                (and build-worker
                                     (string=? build-worker worker))))
                            builds)
@@ -1729,11 +1731,11 @@ completed builds divided by the time required to build 
them.")
       `(div (@ (class "col-sm-4 mt-3"))
             (a (@(href "/machine/" ,machine))
                (h6 ,machine))
-            ,(map (lambda (build)
+            ,(map (lambda (build percentage)
                     (let ((style (format #f
                                          "width: ~a%"
                                          (if build
-                                             (assq-ref build #:percentage)
+                                             percentage
                                              0))))
                       `(div (@ (class "progress mt-1")
                                (style "height: 20px"))
@@ -1749,14 +1751,14 @@ d-flex position-absolute w-100"))
                                         (a (@ (class "text-dark text-truncate")
                                               (style "max-width: 150px")
                                               (href "/build/"
-                                                    ,(assq-ref build #:id)
+                                                    ,(build-id build)
                                                     "/details"))
-                                           ,(assq-ref build #:job-name)))
+                                           ,(build-job-name build)))
                                       '(em
                                         (@ (class "justify-content-center
 text-dark d-flex position-absolute w-100"))
                                         "idle"))))))
-                  builds))))
+                  builds percentages))))
 
   (let ((machines (reverse
                    (sort (delete-duplicates
@@ -1825,9 +1827,9 @@ text-dark d-flex position-absolute w-100"))
                                 `(a (@ (class "text-truncate")
                                        (style "max-width: 150px")
                                        (href "/build/"
-                                             ,(assq-ref build #:id)
+                                             ,(build-id build)
                                              "/details"))
-                                    ,(assq-ref build #:job-name)))))
+                                    ,(build-job-name build)))))
                         (td ,(time->string
                               (worker-last-seen worker)))))
                  workers builds)))))
@@ -1886,24 +1888,24 @@ text-dark d-flex position-absolute w-100"))
 
 (define* (evaluation-dashboard evaluation systems
                                #:key
-                               (checkouts (assq-ref evaluation #:checkouts))
+                               (checkouts (evaluation-checkouts evaluation))
                                channels
                                current-system
                                dashboard-id
                                names
                                prev-eval
                                next-eval)
-  (define evaluation-id
-    (assq-ref evaluation #:id))
+  (define id
+    (evaluation-id evaluation))
   (define time
-    (assq-ref evaluation #:evaltime))
+    (evaluation-completion-time evaluation))
 
   (let ((jobs
          (if names
              (format #f "/api/jobs?evaluation=~a&names=~a"
-                     evaluation-id names)
+                     id names)
              (format #f "/api/jobs?evaluation=~a&system=~a"
-                     evaluation-id current-system))))
+                     id current-system))))
     `((nav
        (@ (aria-label "Evaluation navigation")
           (class "eval-nav"))
@@ -1912,8 +1914,8 @@ text-dark d-flex position-absolute w-100"))
             (p (@ (class "lead mb-0 mr-3"))
                "Dashboard for "
                (a (@ (href ,(string-append "/eval/"
-                                           (number->string evaluation-id))))
-                  "evaluation #" ,(number->string evaluation-id))))
+                                           (number->string id))))
+                  "evaluation #" ,(number->string id))))
            (li (@ (class
                     ,(string-append "page-item "
                                     (if prev-eval
@@ -1965,7 +1967,7 @@ text-dark d-flex position-absolute w-100"))
                                  (if names
                                      "d-none"
                                      "")))
-               (action "/eval/" ,evaluation-id "/dashboard")
+               (action "/eval/" ,id "/dashboard")
                (method "GET"))
             (div (@ (class "col-auto"))
                  (select (@ (id "system")
@@ -2022,9 +2024,9 @@ content as a string."
 
   (if summary
       (let* ((succeeded
-              (assq-ref summary #:succeeded))
+              (evaluation-summary-succeeded summary))
              (total
-              (assq-ref summary #:total))
+              (evaluation-summary-total summary))
              (percentage
               (nearest-exact-integer
                (* 100 (/ succeeded total))))
diff --git a/tests/database.scm b/tests/database.scm
index 69c6053..209cd29 100644
--- a/tests/database.scm
+++ b/tests/database.scm
@@ -86,15 +86,18 @@
                            #:key
                            (job-name "job")
                            (outputs
-                            `(("foo" . ,(format #f "~a.output" drv)))))
-  `((#:derivation . ,drv)
-    (#:eval-id . ,eval-id)
-    (#:job-name . ,job-name)
-    (#:timestamp . ,(time-second (current-time time-utc)))
-    (#:system . "x86_64-linux")
-    (#:nix-name . "foo")
-    (#:log . "log")
-    (#:outputs . ,outputs)))
+                            (list
+                             (output (name "foo")
+                                     (derivation drv)
+                                     (item (format #f "~a.output" drv))))))
+  (build (derivation drv)
+         (evaluation-id eval-id)
+         (specification-name "whatever")
+         (job-name job-name)
+         (system "x86_64-linux")
+         (nix-name "foo")
+         (log "log")
+         (outputs outputs)))
 
 (define %dummy-worker
   (worker
@@ -179,7 +182,7 @@ timestamp, checkouttime, evaltime) VALUES ('guix', 0, 0, 0, 
0);")
   (test-equal "db-get-latest-checkout"
     '("fakesha3" "fakesha4")
     (with-fibers
-      (map (cut assq-ref <> #:commit)
+      (map checkout-commit
            (list (db-get-latest-checkout "guix" 'guix 3)
                  (db-get-latest-checkout "guix" 'my-channel 3)))))
 
@@ -198,30 +201,28 @@ timestamp, checkouttime, evaltime) VALUES ('guix', 0, 0, 
0, 0);")
   (test-equal "db-add-build"
     "/foo.drv"
     (with-fibers
-      (let ((build (make-dummy-build "/foo.drv")))
-        (db-add-build build))))
-
-  (test-equal "db-add-build duplicate"
-    "/foo.drv"
-    (with-fibers
-      (let ((build (make-dummy-build "/foo.drv")))
-        (db-add-build build))))
+      (let* ((build (make-dummy-build "/foo.drv"))
+             (id    (db-add-build build)))
+        (and (not (db-add-build build))           ;duplicate
+             (build-derivation (db-get-build id))))))
 
   (test-assert "db-add-build-product"
     (with-fibers
-      (db-add-build-product `((#:build . 1)
-                              (#:type . "1")
-                              (#:file-size . 1)
-                              (#:checksum . "sum")
-                              (#:path . "path")))))
+      (db-add-build-product (build-product (build-id 1)
+                                           (type "1")
+                                           (file-size 1)
+                                           (checksum "sum")
+                                           (file "path")))))
 
   (test-equal "db-get-output"
-    '((#:derivation . "/foo.drv") (#:name . "foo"))
+    (output (name "foo") (derivation "/foo.drv")
+            (item "/foo.drv.output"))
     (with-fibers
-      (db-get-output "/foo.drv.output")))
+     (db-get-output "/foo.drv.output")))
 
   (test-equal "db-get-outputs"
-    '(("foo" (#:path . "/foo.drv.output")))
+    (list (output (name "foo") (derivation "/foo.drv")
+                  (item "/foo.drv.output")))
     (with-fibers
       (db-get-outputs "/foo.drv")))
 
@@ -232,44 +233,62 @@ timestamp, checkouttime, evaltime) VALUES ('guix', 0, 0, 
0, 0);")
   (test-assert "db-register-builds"
     (with-fibers
       (let ((drv "/test.drv"))
-        (db-register-builds `(((#:job-name . "test")
-                               (#:derivation . ,drv)
-                               (#:system . "x86_64-linux")
-                               (#:nix-name . "test")
-                               (#:log . "log")
-                               (#:outputs .
-                                (("foo" . ,(format #f "~a.output" drv))
-                                 ("foo2" . ,(format #f "~a.output.2" drv))))))
-                            2 (db-get-specification "guix")))))
+        (db-register-builds
+         (list (build (job-name "test")
+                      (evaluation-id 2)
+                      (specification-name "whatever")
+                      (derivation drv)
+                      (system "x86_64-linux")
+                      (nix-name "test")
+                      (log "log")
+                      (outputs
+                       (list (output
+                              (name "foo")
+                              (derivation drv)
+                              (item (string-append drv ".output")))
+                             (output
+                              (name "foo2")
+                              (derivation drv)
+                              (item (string-append drv ".output.2")))))))
+         (db-get-specification "guix")))))
 
   (test-assert "db-get-jobs"
     (with-fibers
       (match (db-get-jobs 2
                           '((#:system . "x86_64-linux")))
         ((job)
-         (string=? (assq-ref job #:name) "test")))))
+         (string=? (job-name job) "test")))))
 
   (test-assert "db-get-jobs names"
     (with-fibers
       (match (db-get-jobs 2
                           '((names "test")))
         ((job)
-         (string=? (assq-ref job #:name) "test")))))
+         (string=? (job-name job) "test")))))
 
   (test-assert "db-register-builds same-outputs"
     (with-fibers
       (let ((drv "/test2.drv"))
         (db-add-evaluation "guix"
                            (make-dummy-instances "fakesha5" "fakesha6"))
-        (db-register-builds `(((#:job-name . "test")
-                               (#:derivation . ,drv)
-                               (#:system . "x86_64-linux")
-                               (#:nix-name . "test")
-                               (#:log . "log")
-                               (#:outputs .
-                                (("foo" . "/test.drv.output")
-                                 ("foo2" . "/test.drv.output.2")))))
-                            4 (db-get-specification "guix")))))
+        (db-register-builds
+         (list (build (job-name "test")
+                      (evaluation-id 4)
+                      (specification-name "whatever")
+                      (derivation drv)
+                      (system "x86_64-linux")
+                      (nix-name "test")
+                      (log "log")
+                      (outputs
+                       (list (output
+                              (name "foo")
+                              (derivation drv)
+                              (item "/test.drv.output"))
+                             (output
+                              (name "foo2")
+                              (derivation drv)
+                              (item "/test.drv.output.2"))))))
+         (db-get-specification "guix")))))
 
   (test-equal "db-get-previous-eval"
     1
@@ -280,14 +299,12 @@ timestamp, checkouttime, evaltime) VALUES ('guix', 0, 0, 
0, 0);")
     (with-fibers
       (not (db-get-next-eval 3))))
 
-  (test-assert "db-get-jobs same-outputs"
+  (test-equal "db-get-jobs same-outputs"
+    "/test.drv"
     (with-fibers
       (match (db-get-jobs 4 '())
         ((job)
-         (string=? (assq-ref (db-get-build
-                              (assq-ref job #:build))
-                             #:derivation)
-                   "/test.drv")))))
+         (build-derivation (db-get-build (job-build-id job)))))))
 
   (test-assert "db-get-jobs-history"
     (with-fibers
@@ -316,40 +333,38 @@ timestamp, checkouttime, evaltime) VALUES ('guix', 0, 0, 
0, 0);")
                      '((nr . 1)
                        (query . "status:failed test")))
                ((build) build))))
-        (list
-         (assoc-ref build #:id)
-         (assoc-ref build #:status)
-         (assoc-ref build #:job-name)))))
+        (list (build-id build)
+              (build-current-status build)
+              (build-job-name build)))))
 
-  (test-assert "db-get-builds"
+  (test-equal "db-get-builds + build-outputs"
+    (list (output (name "foo") (derivation "/test.drv")
+                  (item "/test.drv.output"))
+          (output (name "foo2") (derivation "/test.drv")
+                  (item "/test.drv.output.2")))
     (with-fibers
-      (let* ((build (match (db-get-builds `((order . build-id)
-                                            (status . failed)))
-                      ((build) build)))
-             (outputs (assq-ref build #:outputs)))
-        (equal? outputs
-                '(("foo" (#:path . "/test.drv.output"))
-                  ("foo2" (#:path . "/test.drv.output.2")))))))
+     (match (db-get-builds `((order . build-id)
+                             (status . failed)))
+       ((build)
+        (build-outputs build)))))
 
   (test-equal "db-get-builds job-name"
     "/foo.drv"
     (with-fibers
-      (let ((build (match (db-get-builds `((order . build-id)
-                                           (job . "job")))
-                     ((build) build))))
-        (assoc-ref build #:derivation))))
+     (match (db-get-builds `((order . build-id)
+                             (job . "job")))
+       ((build)
+        (build-derivation build)))))
 
   (test-equal "db-get-build"
     "/foo.drv"
     (with-fibers
-      (let ((build (db-get-build 1)))
-        (assoc-ref build #:derivation))))
+     (build-derivation (db-get-build 1))))
 
   (test-equal "db-get-build derivation"
     1
     (with-fibers
-      (let ((build (db-get-build "/foo.drv")))
-        (assoc-ref build #:id))))
+     (build-id (db-get-build "/foo.drv"))))
 
   (test-equal "db-get-pending-derivations"
     '("/foo.drv")
@@ -360,19 +375,19 @@ timestamp, checkouttime, evaltime) VALUES ('guix', 0, 0, 
0, 0);")
     '("fakesha1" "fakesha2")
     (with-fibers
       (make-dummy-instances "fakesha1" "fakesha2")
-      (map (cut assq-ref <> #:commit) (db-get-checkouts 2))))
+      (map checkout-commit (db-get-checkouts 2))))
 
   (test-equal "db-get-evaluation"
     "guix"
     (with-fibers
       (let ((evaluation (db-get-evaluation 2)))
-        (assq-ref evaluation #:specification))))
+        (evaluation-specification-name evaluation))))
 
   (test-equal "db-get-evaluations"
     '("guix" "guix")
     (with-fibers
       (map (lambda (eval)
-             (assq-ref eval #:specification))
+             (evaluation-specification-name eval))
            (db-get-evaluations 2))))
 
   (test-equal "db-get-evaluations-build-summary"
@@ -381,10 +396,9 @@ timestamp, checkouttime, evaltime) VALUES ('guix', 0, 0, 
0, 0);")
       (let ((summaries
              (db-get-evaluations-build-summary "guix" 3 #f #f)))
         (map (lambda (summary)
-               (list
-                (assq-ref summary #:succeeded)
-                (assq-ref summary #:failed)
-                (assq-ref summary #:scheduled)))
+               (list (build-summary-succeeded summary)
+                     (build-summary-failed summary)
+                     (build-summary-scheduled summary)))
              summaries))))
 
   (test-equal "db-get-evaluation-absolute-summary"
@@ -393,10 +407,9 @@ timestamp, checkouttime, evaltime) VALUES ('guix', 0, 0, 
0, 0);")
       (let ((summary
              (db-get-evaluation-absolute-summary
               (db-get-latest-evaluation "guix"))))
-        (list
-         (assq-ref summary #:succeeded)
-         (assq-ref summary #:failed)
-         (assq-ref summary #:scheduled)))))
+        (list (evaluation-summary-succeeded summary)
+              (evaluation-summary-failed summary)
+              (evaluation-summary-scheduled summary)))))
 
   (test-equal "db-get-evaluations-absolute-summary"
     '((0 1 0) (0 1 0))
@@ -406,10 +419,9 @@ timestamp, checkouttime, evaltime) VALUES ('guix', 0, 0, 
0, 0);")
              (summaries
               (db-get-evaluations-absolute-summary evaluations)))
         (map (lambda (summary)
-               (list
-                (assq-ref summary #:succeeded)
-                (assq-ref summary #:failed)
-                (assq-ref summary #:scheduled)))
+               (list (evaluation-summary-succeeded summary)
+                     (evaluation-summary-failed summary)
+                     (evaluation-summary-scheduled summary)))
              summaries))))
 
   (test-equal "db-get-evaluations-id-min"
@@ -442,33 +454,33 @@ timestamp, checkouttime, evaltime) VALUES ('guix', 0, 0, 
0, 0);")
     (with-fibers
       (match (db-get-latest-evaluations)
         ((eval)
-         (assq-ref (assq-ref eval #:evaluation) #:id)))))
+         (evaluation-id eval)))))
 
   (test-equal "db-get-latest-evaluations 2"
     4
     (with-fibers
       (match (db-get-latest-evaluations #:status #f)
         ((eval)
-         (assq-ref (assq-ref eval #:evaluation) #:id)))))
+         (evaluation-id eval)))))
 
   (test-equal "db-get-evaluation-summary"
     '(2 0 1 1)
     (with-fibers
       (let* ((summary (db-get-evaluation-summary 2))
-             (total (assq-ref summary #:total))
-             (succeeded (assq-ref summary #:succeeded))
-             (failed (assq-ref summary #:failed))
-             (scheduled (assq-ref summary #:scheduled)))
+             (total (evaluation-summary-total summary))
+             (succeeded (evaluation-summary-succeeded summary))
+             (failed (evaluation-summary-failed summary))
+             (scheduled (evaluation-summary-scheduled summary)))
         (list total succeeded failed scheduled))))
 
   (test-equal "db-get-evaluation-summary empty"
     '(0 0 0 0)
     (with-fibers
       (let* ((summary (db-get-evaluation-summary 3))
-             (total (assq-ref summary #:total))
-             (succeeded (assq-ref summary #:succeeded))
-             (failed (assq-ref summary #:failed))
-             (scheduled (assq-ref summary #:scheduled)))
+             (total (evaluation-summary-total summary))
+             (succeeded (evaluation-summary-succeeded summary))
+             (failed (evaluation-summary-failed summary))
+             (scheduled (evaluation-summary-scheduled summary)))
         (list total succeeded failed scheduled))))
 
   (test-equal "db-get-builds-query-min"
@@ -501,13 +513,14 @@ timestamp, checkouttime, evaltime) VALUES ('guix', 0, 0, 
0, 0);")
       (db-get-evaluation-specification 2)))
 
   (test-equal "db-get-build-products"
-    `(((#:id . 1)
-       (#:type . "1")
-       (#:file-size . 1)
-       (#:checksum . "sum")
-       (#:path . "path")))
+    (list (build-product (id 1)
+                         (build-id 1)
+                         (type "1")
+                         (file-size 1)
+                         (checksum "sum")
+                         (file "path")))
     (with-fibers
-      (db-get-build-products 1)))
+     (db-get-build-products 1)))
 
   (test-equal "db-get-build-product-path"
     "path"
@@ -538,8 +551,8 @@ timestamp, checkouttime, evaltime) VALUES ('guix', 0, 0, 0, 
0);")
         (db-remove-unresponsive-workers 50)
         (and (eq? (db-get-workers) '())
              (let* ((build (db-get-build drv))
-                    (worker (assq-ref build #:worker))
-                    (status (assq-ref build #:status)))
+                    (worker (build-worker build))
+                    (status (build-current-status build)))
                (and (not worker)
                     (eq? status (build-status scheduled))))))))
 
@@ -555,11 +568,15 @@ timestamp, checkouttime, evaltime) VALUES ('guix', 0, 0, 
0, 0);")
           (build-status succeeded)
           "/foo2.log")
     (with-fibers
-      (let* ((derivation (db-add-build
-                          (make-dummy-build "/foo2.drv" 2
-                                            #:outputs '(("out" . "/foo")))))
-             (get-status (lambda* (#:optional (key #:status))
-                           (assq-ref (db-get-build derivation) key))))
+      (let* ((derivation "/foo2.drv")
+             (get-status (lambda* (#:optional (field build-current-status))
+                           (field (db-get-build derivation)))))
+        (db-add-build
+         (make-dummy-build derivation 2
+                           #:outputs
+                           (list (output
+                                  (derivation derivation)
+                                  (item "/foo")))))
         (let ((status0 (get-status)))
           (db-update-build-status! "/foo2.drv" (build-status started)
                                    #:log-file "/foo2.log")
@@ -570,9 +587,9 @@ timestamp, checkouttime, evaltime) VALUES ('guix', 0, 0, 0, 
0);")
             (db-update-build-status! "/foo2.drv" (build-status succeeded))
 
             (let ((status2 (get-status))
-                  (start   (get-status #:starttime))
-                  (end     (get-status #:stoptime))
-                  (log     (get-status #:log)))
+                  (start   (get-status build-start-time))
+                  (end     (get-status build-completion-time))
+                  (log     (get-status build-log)))
               (and (> start 0) (>= end start)
                    (list status0 status1 status2 log))))))))
 
@@ -585,61 +602,82 @@ timestamp, checkouttime, evaltime) VALUES ('guix', 0, 0, 
0, 0);")
     (with-fibers
       (exec-query (%db) "DELETE FROM Builds;")
       (db-add-build (make-dummy-build "/baa.drv" 2
-                                      #:outputs `(("out" . "/baa"))))
+                                      #:outputs
+                                      (list (output
+                                             (item "/baa")
+                                             (derivation "/baa.drv")))))
       (db-add-build (make-dummy-build "/bar.drv" 2
-                                      #:outputs `(("out" . "/bar"))))
+                                      #:outputs
+                                      (list (output
+                                             (item "/bar")
+                                             (derivation "/bar.drv")))))
       (db-add-build (make-dummy-build "/baz.drv" 2
-                                      #:outputs `(("out" . "/baz"))))
+                                      #:outputs
+                                      (list (output
+                                             (item "/baz")
+                                             (derivation "/baz.drv")))))
       (db-update-build-status! "/bar.drv" (build-status started)
                                #:log-file "/bar.drv.log")
-      (let ((summarize (lambda (alist)
-                         (assq-ref alist #:derivation))))
-        (list (map summarize (db-get-builds '((nr . 3) (order . build-id))))
-              (map summarize (db-get-builds '()))
-              (map summarize (db-get-builds '((jobset . "guix"))))
-              (map summarize (db-get-builds '((nr . 1))))
-              (map summarize
-                   (db-get-builds '((order . status+submission-time))))))))
+      (list (map build-derivation (db-get-builds '((nr . 3) (order . 
build-id))))
+            (map build-derivation (db-get-builds '()))
+            (map build-derivation (db-get-builds '((jobset . "guix"))))
+            (map build-derivation (db-get-builds '((nr . 1))))
+            (map build-derivation
+                 (db-get-builds '((order . status+submission-time)))))))
 
   (test-equal "db-get-pending-derivations"
     '("/bar.drv" "/foo.drv")
     (with-fibers
       (exec-query (%db) "DELETE FROM Builds;")
       (db-add-build (make-dummy-build "/foo.drv" 1
-                                      #:outputs `(("out" . "/foo"))))
+                                      #:outputs
+                                      (list (output
+                                             (item "/foo")
+                                             (derivation "/foo.drv")))))
       (db-add-build (make-dummy-build "/bar.drv" 2
-                                      #:outputs `(("out" . "/bar"))))
+                                      #:outputs
+                                      (list (output
+                                             (item "/bar")
+                                             (derivation "/bar.drv")))))
       (sort (db-get-pending-derivations) string<?)))
 
   (test-assert "db-get-build-percentages"
     (with-fibers
       (let* ((ts (time-second (current-time time-utc)))
-             (old `((#:derivation . "/last.drv")
-                    (#:eval-id . 2)
-                    (#:job-name . "job")
-                    (#:timestamp . ,(- ts 10))
-                    (#:status . 0)
-                    (#:starttime . 10)
-                    (#:stoptime . 20)
-                    (#:system . "x86_64-linux")
-                    (#:nix-name . "foo")
-                    (#:log . "log")
-                    (#:outputs . (("out" . "/old-percentage")))))
-             (new `((#:derivation . "/cur.drv")
-                    (#:eval-id . 2)
-                    (#:job-name . "job")
-                    (#:timestamp . ,(- ts 5))
-                    (#:starttime . ,(- ts 5))
-                    (#:system . "x86_64-linux")
-                    (#:nix-name . "foo")
-                    (#:log . "log")
-                    (#:outputs . (("out" . "/new-percentage"))))))
+             (old (build (derivation "/last.drv")
+                         (evaluation-id 2)
+                         (job-name "job")
+                         (specification-name "whatever")
+                         (creation-time (- ts 10))
+                         (status 0)
+                         (start-time 10)
+                         (completion-time 20)
+                         (system "x86_64-linux")
+                         (nix-name "foo")
+                         (log "log")
+                         (outputs
+                          (list (output
+                                 (item "/old-percentage")
+                                 (derivation "/last.drv"))))))
+             (new (build (derivation "/cur.drv")
+                         (evaluation-id 2)
+                         (job-name "job")
+                         (specification-name "whatever")
+                         (creation-time (- ts 5))
+                         (start-time (- ts 5))
+                         (system "x86_64-linux")
+                         (nix-name "foo")
+                         (log "log")
+                         (outputs
+                          (list (output
+                                 (item "/new-percentage")
+                                 (derivation "/cur.drv")))))))
         (db-add-build old)
         (db-add-build new)
         (match (db-get-build-percentages
-                (list (db-get-build (assq-ref new #:derivation))))
-          ((build)
-           (>= (assq-ref build #:percentage) 50))))))
+                (list (db-get-build (build-derivation new))))
+          ((percentage)
+           (>= percentage 50))))))
 
   (test-equal "db-update-build-status!"
     (list #f 1)
@@ -648,20 +686,27 @@ timestamp, checkouttime, evaltime) VALUES ('guix', 0, 0, 
0, 0);")
                          (make-dummy-instances "fakesha5" "fakesha6"))
       (db-add-build (make-dummy-build "/old-build.drv" 3
                                       #:job-name "job-1"
-                                      #:outputs `(("out" . "/old"))))
+                                      #:outputs
+                                      (list (output
+                                             (item "/old")
+                                             (derivation "/old-build.drv")))))
       (db-add-build (make-dummy-build "/new-build.drv" 4
                                       #:job-name "job-1"
-                                      #:outputs `(("out" . "/new"))))
+                                      #:outputs
+                                      (list (output
+                                             (item "/new")
+                                             (derivation "/new-build.drv")))))
+
       (db-update-build-status! "/old-build.drv" 1)
       (db-update-build-status! "/new-build.drv" 0)
-      (map (cut assq-ref <> #:last-status)
+      (map build-last-status
            (list (db-get-build "/old-build.drv")
                  (db-get-build "/new-build.drv")))))
 
   (test-equal "db-get-builds weather"
     (build-weather new-success)
     (with-fibers
-      (assq-ref (db-get-build "/new-build.drv") #:weather)))
+      (build-current-weather (db-get-build "/new-build.drv"))))
 
   (test-assert "mail notification"
     (with-fibers
@@ -680,7 +725,7 @@ timestamp, checkouttime, evaltime) VALUES ('guix', 0, 0, 0, 
0);")
     (with-fibers
       (db-update-build-status! "/old-build.drv" 0)
       (db-update-build-status! "/new-build.drv" 1)
-      (assq-ref (db-get-build "/new-build.drv") #:weather)))
+      (build-current-weather (db-get-build "/new-build.drv"))))
 
   (test-assert "mail notification, broken job"
     (with-fibers
@@ -699,28 +744,28 @@ timestamp, checkouttime, evaltime) VALUES ('guix', 0, 0, 
0, 0);")
     (with-fibers
       (db-update-build-status! "/old-build.drv" 0)
       (db-update-build-status! "/new-build.drv" 0)
-      (assq-ref (db-get-build "/new-build.drv") #:weather)))
+      (build-current-weather (db-get-build "/new-build.drv"))))
 
   (test-equal "db-get-builds weather"
     (build-weather still-failing)
     (with-fibers
       (db-update-build-status! "/old-build.drv" 1)
       (db-update-build-status! "/new-build.drv" 1)
-      (assq-ref (db-get-build "/new-build.drv") #:weather)))
+      (build-current-weather (db-get-build "/new-build.drv"))))
 
   (test-assert "db-restart-build!"
     (with-fibers
       (let ((build (db-get-build "/new-build.drv")))
-        (db-restart-build! (assq-ref build #:id))
-        (eq? (assq-ref (db-get-build "/new-build.drv") #:status)
-             (build-status scheduled)))))
+        (db-restart-build! (build-id build))
+        (= (build-current-status (db-get-build "/new-build.drv"))
+           (build-status scheduled)))))
 
   (test-assert "db-restart-evaluation!"
     (with-fibers
       (let ((build (db-get-build "/old-build.drv")))
-        (db-restart-evaluation! (assq-ref build #:eval-id))
-        (eq? (assq-ref (db-get-build "/old-build.drv") #:status)
-             (build-status scheduled)))))
+        (db-restart-evaluation! (build-evaluation-id build))
+        (= (build-current-status (db-get-build "/old-build.drv"))
+           (build-status scheduled)))))
 
   (test-assert "db-retry-evaluation!"
     (with-fibers
@@ -731,11 +776,11 @@ timestamp, checkouttime, evaltime) VALUES ('guix', 0, 0, 
0, 0);")
     (with-fibers
       (let* ((drv "/old-build.drv")
              (build (db-get-build drv))
-             (eval-id (assq-ref build #:eval-id)))
+             (eval-id (build-evaluation-id build)))
         (db-update-build-status! drv (build-status started))
         (db-cancel-pending-builds! eval-id)
-        (eq? (assq-ref (db-get-build drv) #:status)
-             (build-status canceled)))))
+        (= (build-current-status (db-get-build drv))
+           (build-status canceled)))))
 
   (test-assert "db-push-notification"
     (with-fibers
@@ -745,7 +790,7 @@ timestamp, checkouttime, evaltime) VALUES ('guix', 0, 0, 0, 
0);")
           (from "from")
           (to "to")
           (server (mailer)))
-         (assq-ref build #:id)))))
+         (build-id build)))))
 
   (test-assert "db-pop-notification"
     (with-fibers
@@ -753,26 +798,35 @@ timestamp, checkouttime, evaltime) VALUES ('guix', 0, 0, 
0, 0);")
         (match (db-pop-notification)
           ((notif . notif-build)
            (and (email? notif)
-                (equal? build notif-build)))))))
+                ;; <build> records cannot be compared with 'equal?' because
+                ;; they contain procedures, hence this comparison.
+                (string=? (build-derivation build)
+                          (build-derivation notif-build))
+                (eqv? (build-id build)
+                      (build-id notif-build))))))))
 
   (test-assert "set-build-successful!"
     (with-fibers
       (let* ((name "/foo5.drv")
              (build
-              (make-dummy-build name #:outputs `(("out" . ,(getcwd)))))
-             (drv (assq-ref build #:derivation)))
+              (make-dummy-build name
+                                #:outputs
+                                (list (output
+                                       (item (getcwd))
+                                       (derivation "/foo5.drv")))))
+             (drv (build-derivation build)))
         (db-add-build build)
         (set-build-successful! drv)
-        (match (assq-ref (db-get-build name) #:buildproducts)
+        (match (build-products (db-get-build name))
           ((product)
-           (equal? (assq-ref product #:path) (getcwd)))))))
+           (equal? (build-product-file product) (getcwd)))))))
 
   (test-assert "db-worker-current-builds"
     (with-fibers
-      (let ((drv-1
-             (db-add-build (make-dummy-build "/build-1.drv")))
-            (drv-2
-             (db-add-build (make-dummy-build "/build-2.drv"))))
+      (let ((drv-1 "/build-1.drv")
+            (drv-2 "/build-2.drv"))
+        (db-add-build (make-dummy-build drv-1))
+        (db-add-build (make-dummy-build drv-2))
         (db-add-or-update-worker %dummy-worker)
         (db-update-build-worker! drv-1 "worker")
         (db-update-build-worker! drv-2 "worker")
@@ -780,14 +834,14 @@ timestamp, checkouttime, evaltime) VALUES ('guix', 0, 0, 
0, 0);")
         (db-update-build-status! drv-2 (build-status started))
         (match (db-worker-current-builds)
           ((build)
-           (eq? (assq-ref (db-get-build drv-2) #:id)
-                (assq-ref build #:id)))))))
+           (= (build-id (db-get-build drv-2))
+              (build-id build)))))))
 
   (test-equal "db-register-dashboard"
     "guix"
     (with-fibers
       (let ((id (db-register-dashboard "guix" "emacs")))
-        (assq-ref (db-get-dashboard id) #:specification))))
+        (dashboard-specification-name (db-get-dashboard id)))))
 
   (test-assert "db-add-build-dependencies"
     (with-fibers
@@ -798,10 +852,15 @@ timestamp, checkouttime, evaltime) VALUES ('guix', 0, 0, 
0, 0);")
     (with-fibers
       (let* ((drv1 "/build-1.drv")
              (drv2 "/build-2.drv")
-             (id1 (assq-ref (db-get-build drv1) #:id))
-             (id2 (assq-ref (db-get-build drv2) #:id)))
+             (id1 (build-id (db-get-build drv1)))
+             (id2 (build-id (db-get-build drv2))))
         (match (db-get-build-dependencies id1)
-          ((id) (eq? id id2))))))
+          ((id) (= id id2))))))
+
+  (test-equal "build-dependencies"
+    '("/build-2.drv")
+    (with-fibers
+     (build-dependencies (db-get-build "/build-1.drv"))))
 
   (test-assert "db-get-builds no-dependencies"
     (with-fibers
@@ -809,28 +868,24 @@ timestamp, checkouttime, evaltime) VALUES ('guix', 0, 0, 
0, 0);")
                                (build-status scheduled))
       (db-update-build-status! "/build-2.drv"
                                (build-status scheduled))
-      (string=? (assq-ref (db-get-pending-build "x86_64-linux")
-                          #:derivation)
+      (string=? (build-derivation
+                 (db-get-pending-build "x86_64-linux"))
                 "/build-2.drv")))
 
   (test-assert "dependencies trigger"
     (with-fibers
-      (let ((drv-1
-             (db-add-build (make-dummy-build "/build-dep-1.drv")))
-            (drv-2
-             (db-add-build (make-dummy-build "/build-dep-2.drv")))
-            (drv-3
-             (db-add-build (make-dummy-build "/build-dep-3.drv")))
-            (drv-4
-             (db-add-build (make-dummy-build "/build-dep-4.drv")))
-            (drv-5
-             (db-add-build (make-dummy-build "/build-dep-5.drv")))
-            (drv-6
-             (db-add-build (make-dummy-build "/build-dep-6.drv")))
-            (drv-7
-             (db-add-build (make-dummy-build "/build-dep-7.drv")))
+      (let ((drv-1 "/build-dep-1.drv")
+            (drv-2 "/build-dep-2.drv")
+            (drv-3 "/build-dep-3.drv")
+            (drv-4 "/build-dep-4.drv")
+            (drv-5 "/build-dep-5.drv")
+            (drv-6 "/build-dep-6.drv")
+            (drv-7 "/build-dep-7.drv")
             (status (lambda (drv)
-                      (assq-ref (db-get-build drv) #:status))))
+                      (build-current-status (db-get-build drv)))))
+        (for-each (compose db-add-build make-dummy-build)
+                  (list drv-1 drv-2 drv-3 drv-4
+                        drv-5 drv-6 drv-7))
         (db-add-build-dependencies "/build-dep-2.drv"
                                    (list "/build-dep-1.drv"))
         (db-add-build-dependencies "/build-dep-4.drv"
diff --git a/tests/http.scm b/tests/http.scm
index 69d9143..3561f01 100644
--- a/tests/http.scm
+++ b/tests/http.scm
@@ -122,29 +122,35 @@
 
   (test-assert "fill-db"
     (let* ((build1
-            `((#:derivation . "/gnu/store/fake.drv")
-              (#:eval-id . 1)
-              (#:job-name . "fake-job")
-              (#:system . "x86_64-linux")
-              (#:nix-name . "fake-1.0")
-              (#:log . "unused so far")
-              (#:status . ,(build-status succeeded))
-              (#:outputs . (("out" . "/gnu/store/fake-1.0")))
-              (#:timestamp . 1501347493)
-              (#:starttime . 1501347493)
-              (#:stoptime . 1501347493)))
+            (build (derivation "/gnu/store/fake.drv")
+                   (evaluation-id 1)
+                   (specification-name "guix")
+                   (job-name "fake-job")
+                   (system "x86_64-linux")
+                   (nix-name "fake-1.0")
+                   (log "unused so far")
+                   (status (build-status succeeded))
+                   (outputs
+                    (list (output
+                           (item "/gnu/store/fake-1.0")
+                           (derivation derivation))))
+                   (creation-time 1501347493)
+                   (start-time 1501347493)
+                   (completion-time 1501347493)))
            (build2
-            `((#:derivation . "/gnu/store/fake2.drv")
-              (#:eval-id . 1)
-              (#:job-name . "fake-job")
-              (#:system . "x86_64-linux")
-              (#:nix-name . "fake-2.0")
-              (#:log . "unused so far")
-              (#:status . ,(build-status scheduled))
-              (#:outputs . (("out" . "/gnu/store/fake-2.0")))
-              (#:timestamp . 1501347493)
-              (#:starttime . 0)
-              (#:stoptime . 0)))
+            (build (derivation "/gnu/store/fake2.drv")
+                   (evaluation-id 1)
+                   (specification-name "guix")
+                   (job-name "fake-job")
+                   (system "x86_64-linux")
+                   (nix-name "fake-2.0")
+                   (log "unused so far")
+                   (status (build-status scheduled))
+                   (outputs
+                    (list (output
+                           (item "/gnu/store/fake-2.0")
+                           (derivation derivation))))
+                   (creation-time 1501347493)))
            (spec
             (specification
              (name "guix")
diff --git a/tests/remote.scm b/tests/remote.scm
index 118b10c..c089045 100644
--- a/tests/remote.scm
+++ b/tests/remote.scm
@@ -16,7 +16,12 @@
 ;;; You should have received a copy of the GNU General Public License
 ;;; along with Cuirass.  If not, see <http://www.gnu.org/licenses/>.
 
-(use-modules (cuirass database)
+(use-modules ((cuirass database)
+              #:renamer (lambda (symbol)
+                          ;; Avoid collision with #$output.
+                          (if (eq? symbol 'output)
+                              'make-output
+                              symbol)))
              (cuirass specification)
              (gnu packages base)
              (guix build utils)
@@ -25,7 +30,7 @@
              (guix gexp)
              (guix monads)
              (guix packages)
-             (guix store)
+             ((guix store) #:hide (build))
              (tests common)
              (avahi)
              (avahi client)
@@ -108,16 +113,18 @@
                      drv
                      output
                      (timeout 0))
-  `((#:derivation . ,drv)
-    (#:eval-id . 1)
-    (#:job-name . "fake-job")
-    (#:system . "x86_64-linux")
-    (#:nix-name . "fake-1.0")
-    (#:log . "unused so far")
-    (#:status . ,(build-status scheduled))
-    (#:outputs . (("out" . ,output)))
-    (#:timestamp . 1501347493)
-    (#:timeout . ,timeout)))
+  (build (derivation drv)
+         (evaluation-id 1)
+         (specification-name "guix")
+         (job-name "fake-job")
+         (system "x86_64-linux")
+         (nix-name "fake-1.0")
+         (log "unused so far")
+         (status (build-status scheduled))
+         (outputs (list (make-output (item output)
+                                     (derivation drv))))
+         (creation-time 501347493)
+         (timeout timeout)))
 
 (define guix-daemon-running?
   (let ((result (delay (guard (c ((store-connection-error? c) #f))
@@ -152,8 +159,7 @@
   (test-skip (if (and (guix-daemon-running?) (avahi-daemon-running?)) 0 100))
 
   (test-assert "fill-db"
-    (let ((build build)
-          (spec
+    (let ((spec
            (specification
             (name "guix")
             (build 'hello)))
@@ -182,7 +188,7 @@
   (test-assert "build done"
     (retry
      (lambda ()
-       (eq? (assq-ref (db-get-build (force drv)) #:status)
+       (eq? (build-current-status (db-get-build (force drv)))
             (build-status succeeded)))
      #:times 10
      #:delay 1))
@@ -194,7 +200,8 @@
                                 #:timeout 1))
       (retry
        (lambda ()
-         (eq? (assq-ref (db-get-build (force drv-with-timeout)) #:status)
+         (eq? (build-current-status
+               (db-get-build (force drv-with-timeout)))
               (build-status failed)))
        #:times 10
        #:delay 1)))
@@ -206,7 +213,7 @@
       (db-update-build-status! (force drv) (build-status scheduled))
       (retry
        (lambda ()
-         (eq? (assq-ref (db-get-build (force drv)) #:status)
+         (eq? (build-current-status (db-get-build (force drv)))
               (build-status succeeded)))
        #:times 10
        #:delay 1)))



reply via email to

[Prev in Thread] Current Thread [Next in Thread]