From fb49d8eae28f17513475f6ffea44a9db0ec7fe9c Mon Sep 17 00:00:00 2001 From: Calascibetta Romain Date: Wed, 20 Dec 2023 16:38:02 +0100 Subject: [PATCH 1/6] Add two SQL requests to collect older builds according to a parameter --- db/builder_db.ml | 58 +++++++++++++++++++++++++++++++++++++++++ db/builder_db.mli | 6 +++++ test/dune | 2 +- test/test_builder_db.ml | 37 ++++++++++++++++++++++++++ 4 files changed, 102 insertions(+), 1 deletion(-) diff --git a/db/builder_db.ml b/db/builder_db.ml index 42010af..65d777c 100644 --- a/db/builder_db.ml +++ b/db/builder_db.ml @@ -203,6 +203,31 @@ module Build = struct job_id : [`job] id; } + let pp ppf t = + Fmt.pf ppf "@[{ uuid=@ %a;@ \ + start=@ %a;@ \ + finish=@ %a;@ \ + result=@ @[%a@];@ \ + console=@ %a;@ \ + script=@ %a;@ \ + platform=@ %S;@ \ + main_binary=@ @[%a@];@ \ + input_id=@ @[%a@];@ \ + user_id=@ %Lx;@ \ + job_id=@ %Lx;@ }@]" + Uuidm.pp t.uuid + Ptime.pp t.start + Ptime.pp t.finish + Builder.pp_execution_result t.result + Fpath.pp t.console + Fpath.pp t.script + t.platform + Fmt.(Dump.option int64) t.main_binary + Fmt.(Dump.option (using Cstruct.to_string string)) t.input_id + t.user_id + t.job_id + + let t = let rep = Caqti_type.(tup3 @@ -326,6 +351,39 @@ module Build = struct LIMIT 1 |} + let get_builds_older_than = + Caqti_type.(tup3 (id `job) (option string) ptime) ->* Caqti_type.(tup2 t file) @@ + {| SELECT b.uuid, b.start_d, b.start_ps, b.finish_d, b.finish_ps, + b.result_code, b.result_msg, b.console, b.script, + b.platform, b.main_binary, b.input_id, b.user, b.job, + a.filepath, a.localpath, a.sha256, a.size + FROM build_artifact a + INNER JOIN build b ON b.id = a.build + WHERE b.main_binary = a.id AND b.job = $1 + AND ($2 IS NULL OR platform = $2) + AND (CAST((b.finish_d * 3600) AS REAL) + CAST(b.finish_ps AS REAL) / 1000000000000) <= CAST(strftime('%s', $3) AS REAL) + ORDER BY b.start_d DESC, b.start_ps DESC + |} + (* NOTE(dinosaure): [sqlite3] does not have the [date] type. [finish_d] is + the number of days and [finish_ps] is the number of picoseconds. We try + to cast these two fields into a [REAL] which corresponds to the seconds + since 1970-01-01. Actually, our precision is bad because [strftime] and + [$3] don't have the pico-second precision... *) + + let get_builds_and_exclude_the_first = + Caqti_type.(tup3 (id `job) (option string) int) ->* Caqti_type.tup2 t file @@ + {| SELECT b.uuid, b.start_d, b.start_ps, b.finish_d, b.finish_ps, + b.result_code, b.result_msg, b.console, b.script, + b.platform, b.main_binary, b.input_id, b.user, b.job, + a.filepath, a.localpath, a.sha256, a.size + FROM build_artifact a + INNER JOIN build b ON b.id = a.build + WHERE b.main_binary = a.id AND b.job = $1 + AND ($2 IS NULL OR platform = $2) + ORDER BY b.start_d DESC, b.start_ps DESC + LIMIT -1 OFFSET $3 + |} + let get_latest_successful = Caqti_type.(tup2 (id `job) (option string)) ->? t @@ {| SELECT diff --git a/db/builder_db.mli b/db/builder_db.mli index 9d1f052..d400da0 100644 --- a/db/builder_db.mli +++ b/db/builder_db.mli @@ -110,6 +110,8 @@ sig job_id : [`job] id; } + val pp : t Fmt.t + val get_by_uuid : (Uuidm.t, [`build] id * t, [ `One | `Zero ]) Caqti_request.t @@ -127,6 +129,10 @@ sig val get_latest_successful : ([`job] id * string option, t, [ `One | `Zero ]) Caqti_request.t + val get_builds_older_than : + ([`job] id * string option * Ptime.t, t * file, [ `Many | `One | `Zero ]) Caqti_request.t + val get_builds_and_exclude_the_first : + ([`job] id * string option * int, t * file, [ `Many | `One | `Zero ]) Caqti_request.t val get_previous_successful_different_output : ([`build] id, t, [ `One | `Zero ]) Caqti_request.t diff --git a/test/dune b/test/dune index 859c0ca..9bc8ae4 100644 --- a/test/dune +++ b/test/dune @@ -1,7 +1,7 @@ (test (name test_builder_db) (modules test_builder_db) - (libraries builder_db caqti.blocking alcotest mirage-crypto-rng.unix)) + (libraries ptime.clock.os builder_db caqti.blocking alcotest mirage-crypto-rng.unix)) (test (name markdown_to_html) diff --git a/test/test_builder_db.ml b/test/test_builder_db.ml index db56022..afd0985 100644 --- a/test/test_builder_db.ml +++ b/test/test_builder_db.ml @@ -276,6 +276,39 @@ let test_artifact_remove_by_build (module Db : CONN) = get_opt "no build" >>= fun (id, _build) -> Db.exec Builder_db.Build_artifact.remove_by_build id +let test_get_builds_older_than (module Db : CONN) = + add_second_build (module Db) >>= fun () -> + let date = Option.get (Ptime.of_float_s (3600. /. 2.)) in + Db.find_opt Builder_db.Job.get_id_by_name job_name >>= fail_if_none >>= fun job_id -> + Db.collect_list Builder_db.Build.get_builds_older_than (job_id, None, date) >>= fun builds -> + let builds = List.map (fun ({ Builder_db.Build.uuid; _ }, _) -> uuid) builds in + Alcotest.(check (list Testable.uuid)) "last build" builds [ uuid ]; + Db.collect_list Builder_db.Build.get_builds_older_than (job_id, None, Ptime_clock.now ()) >>= fun builds -> + let builds = List.map (fun ({ Builder_db.Build.uuid; _ }, _) -> uuid) builds in + (* NOTE(dinosaure): from the most recent to the older. *) + Alcotest.(check (list Testable.uuid)) "last builds" builds [ uuid'; uuid ]; + Ok () + +let test_builds_and_exclude_the_first (module Db : CONN) = + add_second_build (module Db) >>= fun () -> + Db.find_opt Builder_db.Job.get_id_by_name job_name >>= fail_if_none >>= fun job_id -> + Db.collect_list Builder_db.Build.get_builds_and_exclude_the_first (job_id, None, 1) >>= fun builds -> + let builds = List.map (fun ({ Builder_db.Build.uuid; _ }, _) -> uuid) builds in + Alcotest.(check (list Testable.uuid)) "keep recent build" builds [ uuid ]; + Db.collect_list Builder_db.Build.get_builds_and_exclude_the_first (job_id, None, 2) >>= fun builds -> + let builds = List.map (fun ({ Builder_db.Build.uuid; _ }, _) -> uuid) builds in + Alcotest.(check (list Testable.uuid)) "keep 2 builds" builds []; + Db.collect_list Builder_db.Build.get_builds_and_exclude_the_first (job_id, None, 3) >>= fun builds -> + let builds = List.map (fun ({ Builder_db.Build.uuid; _ }, _) -> uuid) builds in + Alcotest.(check (list Testable.uuid)) "last more builds than we have" builds []; + Db.collect_list Builder_db.Build.get_builds_and_exclude_the_first (job_id, None, 0) >>= fun builds -> + let builds = List.map (fun ({ Builder_db.Build.uuid; _ }, _) -> uuid) builds in + Alcotest.(check (list Testable.uuid)) "delete all builds" builds [ uuid'; uuid ]; + Db.collect_list Builder_db.Build.get_builds_and_exclude_the_first (job_id, None, -1) >>= fun builds -> + let builds = List.map (fun ({ Builder_db.Build.uuid; _ }, _) -> uuid) builds in + Alcotest.(check (list Testable.uuid)) "test an incomprehensible argument (-1)" builds [ uuid'; uuid ]; + Ok () + let () = let open Alcotest in Alcotest.run "Builder_db" [ @@ -310,4 +343,8 @@ let () = test_case "Other artifact doesn't exists" `Quick (with_build_db test_artifact_exists_false); test_case "Remove by build" `Quick (with_build_db test_artifact_remove_by_build); ]; + "vacuum", [ + test_case "Get builds older than now" `Quick (with_build_db test_get_builds_older_than); + test_case "Get older builds and keep a fixed number of then" `Quick (with_build_db test_builds_and_exclude_the_first); + ] ] From 53f1c4958c4a01859f4b5245d50ab910976225aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reynir=20Bj=C3=B6rnsson?= Date: Tue, 9 Jan 2024 12:41:25 +0100 Subject: [PATCH 2/6] Use our own ptime serializer --- db/builder_db.ml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/db/builder_db.ml b/db/builder_db.ml index 65d777c..e02519a 100644 --- a/db/builder_db.ml +++ b/db/builder_db.ml @@ -352,7 +352,7 @@ module Build = struct |} let get_builds_older_than = - Caqti_type.(tup3 (id `job) (option string) ptime) ->* Caqti_type.(tup2 t file) @@ + Caqti_type.(tup3 (id `job) (option string) Rep.ptime) ->* Caqti_type.(tup2 t file) @@ {| SELECT b.uuid, b.start_d, b.start_ps, b.finish_d, b.finish_ps, b.result_code, b.result_msg, b.console, b.script, b.platform, b.main_binary, b.input_id, b.user, b.job, @@ -361,14 +361,9 @@ module Build = struct INNER JOIN build b ON b.id = a.build WHERE b.main_binary = a.id AND b.job = $1 AND ($2 IS NULL OR platform = $2) - AND (CAST((b.finish_d * 3600) AS REAL) + CAST(b.finish_ps AS REAL) / 1000000000000) <= CAST(strftime('%s', $3) AS REAL) + AND b.finish_d = $3 AND b.finish_ps = $4 ORDER BY b.start_d DESC, b.start_ps DESC |} - (* NOTE(dinosaure): [sqlite3] does not have the [date] type. [finish_d] is - the number of days and [finish_ps] is the number of picoseconds. We try - to cast these two fields into a [REAL] which corresponds to the seconds - since 1970-01-01. Actually, our precision is bad because [strftime] and - [$3] don't have the pico-second precision... *) let get_builds_and_exclude_the_first = Caqti_type.(tup3 (id `job) (option string) int) ->* Caqti_type.tup2 t file @@ From 73e1bf81cea44809cb733591e5813aaecd23d85d Mon Sep 17 00:00:00 2001 From: Robur Date: Tue, 9 Jan 2024 11:58:05 +0000 Subject: [PATCH 3/6] Fix comparison: less than or equal, not strict equal --- db/builder_db.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/builder_db.ml b/db/builder_db.ml index e02519a..c11b295 100644 --- a/db/builder_db.ml +++ b/db/builder_db.ml @@ -361,7 +361,7 @@ module Build = struct INNER JOIN build b ON b.id = a.build WHERE b.main_binary = a.id AND b.job = $1 AND ($2 IS NULL OR platform = $2) - AND b.finish_d = $3 AND b.finish_ps = $4 + AND (b.finish_d < $3 OR (b.finish_d = $3 AND b.finish_ps <= $4)) ORDER BY b.start_d DESC, b.start_ps DESC |} From 6594c6b9127806e1b2f84b039d78f6db99c500df Mon Sep 17 00:00:00 2001 From: Robur Date: Tue, 9 Jan 2024 12:05:24 +0000 Subject: [PATCH 4/6] Rename get_builds_and_exclude_the_first --- db/builder_db.ml | 2 +- db/builder_db.mli | 2 +- test/test_builder_db.ml | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/db/builder_db.ml b/db/builder_db.ml index c11b295..c950253 100644 --- a/db/builder_db.ml +++ b/db/builder_db.ml @@ -365,7 +365,7 @@ module Build = struct ORDER BY b.start_d DESC, b.start_ps DESC |} - let get_builds_and_exclude_the_first = + let get_builds_excluding_latest_n = Caqti_type.(tup3 (id `job) (option string) int) ->* Caqti_type.tup2 t file @@ {| SELECT b.uuid, b.start_d, b.start_ps, b.finish_d, b.finish_ps, b.result_code, b.result_msg, b.console, b.script, diff --git a/db/builder_db.mli b/db/builder_db.mli index d400da0..7501860 100644 --- a/db/builder_db.mli +++ b/db/builder_db.mli @@ -131,7 +131,7 @@ sig Caqti_request.t val get_builds_older_than : ([`job] id * string option * Ptime.t, t * file, [ `Many | `One | `Zero ]) Caqti_request.t - val get_builds_and_exclude_the_first : + val get_builds_excluding_latest_n : ([`job] id * string option * int, t * file, [ `Many | `One | `Zero ]) Caqti_request.t val get_previous_successful_different_output : ([`build] id, t, [ `One | `Zero ]) diff --git a/test/test_builder_db.ml b/test/test_builder_db.ml index afd0985..bc3c222 100644 --- a/test/test_builder_db.ml +++ b/test/test_builder_db.ml @@ -289,22 +289,22 @@ let test_get_builds_older_than (module Db : CONN) = Alcotest.(check (list Testable.uuid)) "last builds" builds [ uuid'; uuid ]; Ok () -let test_builds_and_exclude_the_first (module Db : CONN) = +let test_builds_excluding_latest_n (module Db : CONN) = add_second_build (module Db) >>= fun () -> Db.find_opt Builder_db.Job.get_id_by_name job_name >>= fail_if_none >>= fun job_id -> - Db.collect_list Builder_db.Build.get_builds_and_exclude_the_first (job_id, None, 1) >>= fun builds -> + Db.collect_list Builder_db.Build.get_builds_excluding_latest_n (job_id, None, 1) >>= fun builds -> let builds = List.map (fun ({ Builder_db.Build.uuid; _ }, _) -> uuid) builds in Alcotest.(check (list Testable.uuid)) "keep recent build" builds [ uuid ]; - Db.collect_list Builder_db.Build.get_builds_and_exclude_the_first (job_id, None, 2) >>= fun builds -> + Db.collect_list Builder_db.Build.get_builds_excluding_latest_n (job_id, None, 2) >>= fun builds -> let builds = List.map (fun ({ Builder_db.Build.uuid; _ }, _) -> uuid) builds in Alcotest.(check (list Testable.uuid)) "keep 2 builds" builds []; - Db.collect_list Builder_db.Build.get_builds_and_exclude_the_first (job_id, None, 3) >>= fun builds -> + Db.collect_list Builder_db.Build.get_builds_excluding_latest_n (job_id, None, 3) >>= fun builds -> let builds = List.map (fun ({ Builder_db.Build.uuid; _ }, _) -> uuid) builds in Alcotest.(check (list Testable.uuid)) "last more builds than we have" builds []; - Db.collect_list Builder_db.Build.get_builds_and_exclude_the_first (job_id, None, 0) >>= fun builds -> + Db.collect_list Builder_db.Build.get_builds_excluding_latest_n (job_id, None, 0) >>= fun builds -> let builds = List.map (fun ({ Builder_db.Build.uuid; _ }, _) -> uuid) builds in Alcotest.(check (list Testable.uuid)) "delete all builds" builds [ uuid'; uuid ]; - Db.collect_list Builder_db.Build.get_builds_and_exclude_the_first (job_id, None, -1) >>= fun builds -> + Db.collect_list Builder_db.Build.get_builds_excluding_latest_n (job_id, None, -1) >>= fun builds -> let builds = List.map (fun ({ Builder_db.Build.uuid; _ }, _) -> uuid) builds in Alcotest.(check (list Testable.uuid)) "test an incomprehensible argument (-1)" builds [ uuid'; uuid ]; Ok () @@ -345,6 +345,6 @@ let () = ]; "vacuum", [ test_case "Get builds older than now" `Quick (with_build_db test_get_builds_older_than); - test_case "Get older builds and keep a fixed number of then" `Quick (with_build_db test_builds_and_exclude_the_first); + test_case "Get older builds and keep a fixed number of then" `Quick (with_build_db test_builds_excluding_latest_n); ] ] From 082f2582dd3333930b1fc499959cac5138394988 Mon Sep 17 00:00:00 2001 From: Robur Date: Tue, 9 Jan 2024 12:12:18 +0000 Subject: [PATCH 5/6] Add a comment on LIMIT -1 OFFSET n --- db/builder_db.ml | 1 + 1 file changed, 1 insertion(+) diff --git a/db/builder_db.ml b/db/builder_db.ml index c950253..df958f5 100644 --- a/db/builder_db.ml +++ b/db/builder_db.ml @@ -378,6 +378,7 @@ module Build = struct ORDER BY b.start_d DESC, b.start_ps DESC LIMIT -1 OFFSET $3 |} + (* "LIMIT -1 OFFSET n" is all rows except the first n *) let get_latest_successful = Caqti_type.(tup2 (id `job) (option string)) ->? t @@ From 46df7f92a7f2e71a8affaf50fbac164048b92e04 Mon Sep 17 00:00:00 2001 From: Calascibetta Romain Date: Mon, 5 Feb 2024 17:33:18 +0100 Subject: [PATCH 6/6] Aggregate all builds (even failed builds) --- db/builder_db.ml | 34 +++++++++++++++------------------- db/builder_db.mli | 4 ++-- test/test_builder_db.ml | 14 +++++++------- 3 files changed, 24 insertions(+), 28 deletions(-) diff --git a/db/builder_db.ml b/db/builder_db.ml index df958f5..ca3d7a9 100644 --- a/db/builder_db.ml +++ b/db/builder_db.ml @@ -352,30 +352,26 @@ module Build = struct |} let get_builds_older_than = - Caqti_type.(tup3 (id `job) (option string) Rep.ptime) ->* Caqti_type.(tup2 t file) @@ - {| SELECT b.uuid, b.start_d, b.start_ps, b.finish_d, b.finish_ps, - b.result_code, b.result_msg, b.console, b.script, - b.platform, b.main_binary, b.input_id, b.user, b.job, - a.filepath, a.localpath, a.sha256, a.size - FROM build_artifact a - INNER JOIN build b ON b.id = a.build - WHERE b.main_binary = a.id AND b.job = $1 + Caqti_type.(tup3 (id `job) (option string) Rep.ptime) ->* t @@ + {| SELECT uuid, start_d, start_ps, finish_d, finish_ps, + result_code, result_msg, console, script, + platform, main_binary, input_id, user, job + FROM build + WHERE job = $1 AND ($2 IS NULL OR platform = $2) - AND (b.finish_d < $3 OR (b.finish_d = $3 AND b.finish_ps <= $4)) - ORDER BY b.start_d DESC, b.start_ps DESC + AND (finish_d < $3 OR (finish_d = $3 AND finish_ps <= $4)) + ORDER BY start_d DESC, start_ps DESC |} let get_builds_excluding_latest_n = - Caqti_type.(tup3 (id `job) (option string) int) ->* Caqti_type.tup2 t file @@ - {| SELECT b.uuid, b.start_d, b.start_ps, b.finish_d, b.finish_ps, - b.result_code, b.result_msg, b.console, b.script, - b.platform, b.main_binary, b.input_id, b.user, b.job, - a.filepath, a.localpath, a.sha256, a.size - FROM build_artifact a - INNER JOIN build b ON b.id = a.build - WHERE b.main_binary = a.id AND b.job = $1 + Caqti_type.(tup3 (id `job) (option string) int) ->* t @@ + {| SELECT uuid, start_d, start_ps, finish_d, finish_ps, + result_code, result_msg, console, script, + platform, main_binary, input_id, user, job + FROM build + WHERE job = $1 AND ($2 IS NULL OR platform = $2) - ORDER BY b.start_d DESC, b.start_ps DESC + ORDER BY start_d DESC, start_ps DESC LIMIT -1 OFFSET $3 |} (* "LIMIT -1 OFFSET n" is all rows except the first n *) diff --git a/db/builder_db.mli b/db/builder_db.mli index 7501860..c6a1821 100644 --- a/db/builder_db.mli +++ b/db/builder_db.mli @@ -130,9 +130,9 @@ sig ([`job] id * string option, t, [ `One | `Zero ]) Caqti_request.t val get_builds_older_than : - ([`job] id * string option * Ptime.t, t * file, [ `Many | `One | `Zero ]) Caqti_request.t + ([`job] id * string option * Ptime.t, t, [ `Many | `One | `Zero ]) Caqti_request.t val get_builds_excluding_latest_n : - ([`job] id * string option * int, t * file, [ `Many | `One | `Zero ]) Caqti_request.t + ([`job] id * string option * int, t, [ `Many | `One | `Zero ]) Caqti_request.t val get_previous_successful_different_output : ([`build] id, t, [ `One | `Zero ]) Caqti_request.t diff --git a/test/test_builder_db.ml b/test/test_builder_db.ml index bc3c222..8a41ab8 100644 --- a/test/test_builder_db.ml +++ b/test/test_builder_db.ml @@ -281,10 +281,10 @@ let test_get_builds_older_than (module Db : CONN) = let date = Option.get (Ptime.of_float_s (3600. /. 2.)) in Db.find_opt Builder_db.Job.get_id_by_name job_name >>= fail_if_none >>= fun job_id -> Db.collect_list Builder_db.Build.get_builds_older_than (job_id, None, date) >>= fun builds -> - let builds = List.map (fun ({ Builder_db.Build.uuid; _ }, _) -> uuid) builds in + let builds = List.map (fun { Builder_db.Build.uuid; _ } -> uuid) builds in Alcotest.(check (list Testable.uuid)) "last build" builds [ uuid ]; Db.collect_list Builder_db.Build.get_builds_older_than (job_id, None, Ptime_clock.now ()) >>= fun builds -> - let builds = List.map (fun ({ Builder_db.Build.uuid; _ }, _) -> uuid) builds in + let builds = List.map (fun { Builder_db.Build.uuid; _ } -> uuid) builds in (* NOTE(dinosaure): from the most recent to the older. *) Alcotest.(check (list Testable.uuid)) "last builds" builds [ uuid'; uuid ]; Ok () @@ -293,19 +293,19 @@ let test_builds_excluding_latest_n (module Db : CONN) = add_second_build (module Db) >>= fun () -> Db.find_opt Builder_db.Job.get_id_by_name job_name >>= fail_if_none >>= fun job_id -> Db.collect_list Builder_db.Build.get_builds_excluding_latest_n (job_id, None, 1) >>= fun builds -> - let builds = List.map (fun ({ Builder_db.Build.uuid; _ }, _) -> uuid) builds in + let builds = List.map (fun { Builder_db.Build.uuid; _ } -> uuid) builds in Alcotest.(check (list Testable.uuid)) "keep recent build" builds [ uuid ]; Db.collect_list Builder_db.Build.get_builds_excluding_latest_n (job_id, None, 2) >>= fun builds -> - let builds = List.map (fun ({ Builder_db.Build.uuid; _ }, _) -> uuid) builds in + let builds = List.map (fun { Builder_db.Build.uuid; _ } -> uuid) builds in Alcotest.(check (list Testable.uuid)) "keep 2 builds" builds []; Db.collect_list Builder_db.Build.get_builds_excluding_latest_n (job_id, None, 3) >>= fun builds -> - let builds = List.map (fun ({ Builder_db.Build.uuid; _ }, _) -> uuid) builds in + let builds = List.map (fun { Builder_db.Build.uuid; _ } -> uuid) builds in Alcotest.(check (list Testable.uuid)) "last more builds than we have" builds []; Db.collect_list Builder_db.Build.get_builds_excluding_latest_n (job_id, None, 0) >>= fun builds -> - let builds = List.map (fun ({ Builder_db.Build.uuid; _ }, _) -> uuid) builds in + let builds = List.map (fun { Builder_db.Build.uuid; _ } -> uuid) builds in Alcotest.(check (list Testable.uuid)) "delete all builds" builds [ uuid'; uuid ]; Db.collect_list Builder_db.Build.get_builds_excluding_latest_n (job_id, None, -1) >>= fun builds -> - let builds = List.map (fun ({ Builder_db.Build.uuid; _ }, _) -> uuid) builds in + let builds = List.map (fun { Builder_db.Build.uuid; _ } -> uuid) builds in Alcotest.(check (list Testable.uuid)) "test an incomprehensible argument (-1)" builds [ uuid'; uuid ]; Ok ()