···11+# day10: Code Walkthrough
22+33+This document walks through the changes to day10 since commit `e76b3395` —
44+roughly 3,600 lines of new code across 16 commits. The work transforms day10
55+from a batch builder into something closer to a queryable build service with
66+history tracking, failure recovery, and incremental cascade rebuilds.
77+88+The base URL for source links is:
99+`https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10`
1010+1111+---
1212+1313+## Table of Contents
1414+1515+1. [New Library Modules](#1-new-library-modules)
1616+2. [Build Failure Classification & History Recording](#2-build-failure-classification--history-recording)
1717+3. [Status Generation](#3-status-generation)
1818+4. [DAG Executor](#4-dag-executor)
1919+5. [CLI Commands](#5-cli-commands)
2020+6. [Infrastructure: Logging, Races, and Robustness](#6-infrastructure-logging-races-and-robustness)
2121+7. [Utility Consolidation](#7-utility-consolidation)
2222+8. [On-Disk Structure](#8-on-disk-structure)
2323+2424+---
2525+2626+## 1. New Library Modules
2727+2828+Four new modules were added to `day10_lib`:
2929+3030+### History ([lib/history.ml](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/lib/history.ml))
3131+3232+Per-package append-only build history, stored as JSONL files at
3333+`packages/{pkg}/history.jsonl`. Each entry records a single build attempt:
3434+3535+```ocaml
3636+type entry = {
3737+ ts : string; (* ISO 8601 timestamp *)
3838+ run : string; (* Run identifier, e.g. "2026-03-09-125444" *)
3939+ build_hash : string; (* Content-addressed layer hash *)
4040+ status : string; (* "success" or "failure" *)
4141+ category : string; (* e.g. "success", "build_failure", "dependency_failure" *)
4242+ compiler : string; (* OCaml version *)
4343+ blessed : bool; (* Whether this is the canonical build *)
4444+ error : string option; (* Error description *)
4545+ failed_dep : string option; (* Package name of the failed dependency *)
4646+ failed_dep_hash : string option; (* Build hash of the failed dependency *)
4747+}
4848+```
4949+5050+Key functions:
5151+5252+- [`append`](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/lib/history.ml#L83) —
5353+ Appends an entry with `Unix.lockf F_LOCK` file locking, safe for concurrent
5454+ forked processes writing to the same package's history.
5555+5656+- [`read_latest`](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/lib/history.ml#L114) —
5757+ Returns the most recent entry per `build_hash`, deduplicating across runs.
5858+ Used by the status command and status index generation.
5959+6060+- [`compact`](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/lib/history.ml#L147) —
6161+ Compresses old history by collapsing consecutive same-status, same-hash
6262+ entries older than `max_age_days` down to first + last. Keeps the file from
6363+ growing without bound.
6464+6565+### Status Index ([lib/status_index.ml](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/lib/status_index.ml))
6666+6767+Global build status snapshot, written to `status.json` after each run:
6868+6969+```ocaml
7070+type t = {
7171+ generated : string;
7272+ run_id : string;
7373+ blessed_totals : (string * int) list; (* category -> count for blessed builds *)
7474+ non_blessed_totals : (string * int) list; (* category -> count for non-blessed *)
7575+ changes : change list; (* status transitions since last run *)
7676+ new_packages : string list;
7777+}
7878+```
7979+8080+[`generate`](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/lib/status_index.ml#L130)
8181+scans all package directories, reads their history files, tallies totals by
8282+category, and detects changes by comparing entries from the current `run_id`
8383+against entries from previous runs.
8484+8585+### GC ([lib/gc.ml](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/lib/gc.ml))
8686+8787+Garbage collection for the build cache. Three levels:
8888+8989+- [`gc_layers`](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/lib/gc.ml#L66) —
9090+ Deletes `build-*`, `doc-*`, `jtw-*` layer directories not referenced by any
9191+ current solution. Protected layers (`base/`, `packages/`, `solutions/`,
9292+ `logs/`, tool layers) are
9393+ [never deleted](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/lib/gc.ml#L53).
9494+9595+- [`gc_universes`](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/lib/gc.ml#L165) —
9696+ Deletes empty universe directories not referenced by any doc output.
9797+ Conservative: preserves universes that still contain package documentation.
9898+9999+- [`gc_logs`](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/lib/gc.ml#L230) —
100100+ Compacts per-package history files and manages old run directories (archive
101101+ or delete based on `keep_runs` threshold).
102102+103103+### Notify ([lib/notify.ml](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/lib/notify.ml))
104104+105105+Simple notification dispatch to Slack, Zulip, Telegram, Email, or stdout.
106106+Each channel reads its configuration from environment variables. Used by the
107107+`notify` CLI command.
108108+109109+---
110110+111111+## 2. Build Failure Classification & History Recording
112112+113113+When a build completes, two things happen before moving on:
114114+115115+### Classification ([bin/main.ml#L341](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/bin/main.ml#L341))
116116+117117+`classify_build_failure` scans the build log for known error patterns and
118118+assigns a category:
119119+120120+```ocaml
121121+let classify_build_failure build_log_path =
122122+ let log_content = try Os.read_from_file build_log_path with _ -> "" in
123123+ let transient_patterns = [
124124+ "No space left on device"; "Connection timed out";
125125+ "Could not resolve host"; ...
126126+ ] in
127127+ let depext_patterns = [
128128+ "Unable to locate package"; "is not available";
129129+ "unmet dependencies"; ...
130130+ ] in
131131+ if matches_any transient_patterns log_content then
132132+ ("failure", "transient_failure", Some "Transient infrastructure failure")
133133+ else if matches_any depext_patterns log_content then
134134+ ("failure", "depext_unavailable", Some "Missing system dependency")
135135+ else
136136+ ("failure", "build_failure", None)
137137+```
138138+139139+Pattern matching uses
140140+[`contains_substring_ci`](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/bin/main.ml#L308) —
141141+a pure-OCaml case-insensitive substring search, replacing the earlier
142142+`Str.regexp` approach which had thread-safety issues (the `Str` module uses
143143+global mutable state).
144144+145145+### Recording ([bin/main.ml#L286](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/bin/main.ml#L286))
146146+147147+`record_build_result` appends a history entry, deduplicating within a run via
148148+an in-memory hashtable:
149149+150150+```ocaml
151151+let recorded_this_run : (string, bool) Hashtbl.t = Hashtbl.create 1024
152152+153153+let record_build_result ~packages_dir ~run_id ~pkg_str ~build_hash
154154+ ~status ~category ~compiler ~blessed ~error ~failed_dep ~failed_dep_hash =
155155+ let key = Printf.sprintf "%s:%s" pkg_str build_hash in
156156+ if not (Hashtbl.mem recorded_this_run key) then begin
157157+ Hashtbl.replace recorded_this_run key true;
158158+ let entry = { ts = ...; run = run_id; build_hash; status;
159159+ category; compiler; blessed; error;
160160+ failed_dep; failed_dep_hash } in
161161+ History.append ~packages_dir ~pkg_str entry
162162+ end
163163+```
164164+165165+### Root-cause tracking ([bin/main.ml#L1746](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/bin/main.ml#L1746))
166166+167167+When a package fails due to a dependency failure, `find_root_failure` walks the
168168+dependency graph transitively to find the actual build that broke:
169169+170170+```ocaml
171171+let rec find_root_failure solution pkg_hashes pkg visited =
172172+ (* ... walks dep graph to find the package that actually failed to build,
173173+ rather than recording the immediate dep that was skipped *)
174174+```
175175+176176+The result is stored in the history entry's `failed_dep` and `failed_dep_hash`
177177+fields, so queries can immediately show *why* a package failed without
178178+re-walking the graph.
179179+180180+---
181181+182182+## 3. Status Generation
183183+184184+[`print_batch_summary`](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/bin/main.ml#L1568)
185185+runs after every batch, rerun, and cascade. It:
186186+187187+1. Iterates all solutions and their packages
188188+2. Records build results to per-package history
189189+3. Classifies failures by scanning build logs
190190+4. Walks dep graphs for root-cause attribution
191191+5. Generates `status.json` via `Status_index.generate`
192192+193193+This is the integration point where the build pipeline meets the
194194+history/status system.
195195+196196+---
197197+198198+## 4. DAG Executor
199199+200200+The biggest single addition. When `--fork N` is passed to `batch`, instead of
201201+building packages sequentially per-solution, day10 builds a *global* DAG of
202202+all unique build layers across all solutions and executes them in parallel.
203203+204204+### Build nodes ([bin/main.ml#L1147](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/bin/main.ml#L1147))
205205+206206+```ocaml
207207+type build_node = {
208208+ pkg : OpamPackage.t;
209209+ build_hash : string; (* "build-{hash}" *)
210210+ ordered_deps : OpamPackage.t list;
211211+ dep_build_hashes : string list; (* build hashes of deps, in order *)
212212+}
213213+```
214214+215215+### DAG construction ([bin/main.ml#L1157](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/bin/main.ml#L1157))
216216+217217+`build_global_dag` walks all solutions, computes build hashes for each
218218+package, deduplicates by hash (since the same package with the same deps
219219+produces the same hash regardless of which solution requested it), then
220220+topologically sorts the result.
221221+222222+### Execution ([bin/main.ml#L1219](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/bin/main.ml#L1219))
223223+224224+`execute_dag` manages the parallel build loop. Its internal structure was
225225+refactored into four composable helpers:
226226+227227+```ocaml
228228+let execute_dag ~np ~on_complete ?on_cascade ~cache_dir ~os_key nodes build_one =
229229+ (* ... setup ... *)
230230+231231+ (* Mark a node as cascade-failed and notify callbacks *)
232232+ let cascade_fail ~failed_hash ~failed_dep_hash = ... in
233233+234234+ (* Recursively propagate failure to all transitive dependents *)
235235+ let rec propagate_failure failed_dep_hash = ... in
236236+237237+ (* After a node completes, promote or cascade-fail its dependents *)
238238+ let promote_dependents hash = ... in
239239+240240+ (* Record a node's completion and promote its dependents *)
241241+ let complete_node hash success = ... in
242242+```
243243+244244+The main loop pops nodes from a ready queue, checks for cached layers (instant
245245+resolution without forking), forks workers for uncached layers, and reaps
246246+completed children. Cache hits and failures are handled identically via
247247+`complete_node`, which calls `promote_dependents` to either enqueue dependents
248248+or cascade-fail them.
249249+250250+**Cascade failure propagation**: When a build fails, `promote_dependents`
251251+checks each reverse dependent's deps. If any dep failed, the dependent is
252252+immediately marked as failed via `cascade_fail` (no fork, no build). Then
253253+`propagate_failure` recursively walks the reverse-dependency graph, marking
254254+all transitive dependents as failed.
255255+256256+**The `on_cascade` callback**: Passed by the caller, it fires for every
257257+cascade-failed node with the `failed_hash` and `failed_dep_hash`. The
258258+[batch caller](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/bin/main.ml#L1960)
259259+uses this to:
260260+261261+1. Log the cascade:
262262+ `dag: CASCADE lwt-ssl.1.2.0 (build-abc) — dep lwt.6.1.1 (build-def) failed`
263263+2. Write a skeleton layer via
264264+ [`Util.write_skeleton_layer`](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/bin/util.ml#L239)
265265+266266+**Forked children use `Unix._exit`**
267267+([line 1317](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/bin/main.ml#L1317))
268268+instead of `exit` to avoid running OCaml's `at_exit` handlers, which could
269269+flush the parent's partially-written stdio buffers.
270270+271271+---
272272+273273+## 5. CLI Commands
274274+275275+All commands are wired up via cmdliner at the bottom of `main.ml`. All support
276276+`--format json` for machine consumption.
277277+278278+### status ([bin/main.ml#L2409](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/bin/main.ml#L2409))
279279+280280+Displays the build status overview from `status.json`. With `--details`,
281281+breaks down failures by category with package lists.
282282+283283+### query ([bin/main.ml#L2569](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/bin/main.ml#L2569))
284284+285285+Shows detailed build information for a specific package: current status, build
286286+hash, compiler version, history of status changes across runs. With `--log`,
287287+displays the build log inline.
288288+289289+### failures ([bin/main.ml#L2648](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/bin/main.ml#L2648))
290290+291291+Lists all packages with failing builds. Supports `--blessed-only` to filter
292292+to canonical builds and `--category` to filter by failure type
293293+(`build_failure`, `dependency_failure`, `transient_failure`, etc.).
294294+295295+### changes ([bin/main.ml#L2696](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/bin/main.ml#L2696))
296296+297297+Shows status transitions since the last run (e.g. `success → failure`). Reads
298298+the `changes_since_last` field from `status.json`.
299299+300300+### disk ([bin/main.ml#L2743](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/bin/main.ml#L2743))
301301+302302+Reports disk usage breakdown: base image, build layers, doc layers, packages
303303+metadata, logs, solutions.
304304+305305+### rerun ([bin/main.ml#L2910](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/bin/main.ml#L2910))
306306+307307+Retries a failed build. Accepts a build hash or package name. The key design
308308+choice: reruns use the opam files stored in the layer's own
309309+`opam-repository/` directory, so **no external opam-repository path is
310310+needed**. The rebuild uses the exact same opam files as the original build.
311311+312312+The [`rerun_build_layer`](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/bin/main.ml#L2842)
313313+function reads the skeleton layer's `layer.json` for the dep list, points the
314314+solver at the layer's embedded `opam-repository/`, and rebuilds.
315315+316316+With `--cascade`, after the rerun succeeds, it finds all packages that
317317+recorded a `dependency_failure` pointing at this build hash and reruns
318318+them too.
319319+320320+### cascade ([bin/main.ml#L3107](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/bin/main.ml#L3107))
321321+322322+Bulk cascade: scans all packages for `dependency_failure` entries, checks if
323323+the failing dependency now succeeds, and reruns everything that's unblocked.
324324+Supports `--blessed-first`, `--dry-run`, and `--fork N`.
325325+326326+[`find_cascade_targets`](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/bin/main.ml#L2811)
327327+does the scan: for each package with a `dependency_failure` history entry, it
328328+checks whether the `failed_dep_hash` layer now has `exit_status = 0`.
329329+330330+### rdeps ([bin/main.ml#L3007](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/bin/main.ml#L3007))
331331+332332+Finds reverse dependencies of a package by scanning cached solutions. With
333333+`--failing`, filters to rdeps that are currently failing.
334334+335335+### gc ([bin/main.ml#L3226](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/bin/main.ml#L3226))
336336+337337+Garbage collects logs, old run directories, and compacts history files.
338338+Supports `--archive`, `--keep-runs`, `--stable-threshold`, `--dry-run`.
339339+340340+### universe ([bin/main.ml#L3276](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/bin/main.ml#L3276))
341341+342342+Looks up packages in a universe by hash (or prefix). Without arguments, lists
343343+all universes with package counts.
344344+345345+### log ([bin/main.ml#L3374](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/bin/main.ml#L3374))
346346+347347+Displays the build or doc log for a specific layer hash, with metadata.
348348+349349+### notify ([bin/main.ml#L3084](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/bin/main.ml#L3084))
350350+351351+Sends a message to an external channel (Slack, Zulip, Telegram, Email,
352352+stdout).
353353+354354+---
355355+356356+## 6. Infrastructure: Logging, Races, and Robustness
357357+358358+### Per-process logging ([bin/os.ml](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/bin/os.ml))
359359+360360+`Os.log` writes timestamped messages to `logs/{pid}.log`. This is critical for
361361+`--fork` mode where multiple processes are running concurrently — each gets
362362+its own log file keyed by PID. The `sudo` and `exec` functions log their
363363+commands and exit codes.
364364+365365+### Container command logging ([bin/linux.ml](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/bin/linux.ml))
366366+367367+The `build` function logs the full container command (runc argv) before
368368+execution, so build failures can be reproduced.
369369+370370+### mkdir race fix ([bin/os.ml](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/bin/os.ml#L74))
371371+372372+```ocaml
373373+let rec mkdir ?(parents = false) dir =
374374+ if not (Sys.file_exists dir) then (
375375+ (if parents then ...);
376376+ try Sys.mkdir dir 0o755
377377+ with Sys_error _ when Sys.file_exists dir && Sys.is_directory dir -> ())
378378+```
379379+380380+The original `Sys.mkdir` would raise `Sys_error` if another forked worker
381381+created the directory between the `file_exists` check and the `mkdir` call.
382382+This race caused crashes when multiple workers built different variants of the
383383+same package (e.g. 8 workers building `lwt.6.1.1` concurrently), which then
384384+cascaded to ~370 dependent packages. The fix catches the error and verifies
385385+the directory exists.
386386+387387+### EINTR handling in fork functions
388388+389389+The `fork`, `fork_with_progress`, and `fork_map` functions now catch
390390+`Unix.Unix_error(EINTR, _, _)` from `waitpid`, which occurs when system calls
391391+are interrupted by signals. Previously, this could crash the parent process.
392392+393393+### Base image hash invalidation ([bin/linux.ml](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/bin/linux.ml))
394394+395395+`base_hash` computes a hash from the OS distribution, version, architecture,
396396+and opam-build source hash. `layer_hash` now includes the base hash, so when
397397+the base container image changes (new OS version, updated opam-build), all
398398+cached layers are automatically invalidated.
399399+400400+---
401401+402402+## 7. Utility Consolidation
403403+404404+Three helpers were extracted into
405405+[`bin/util.ml`](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/bin/util.ml)
406406+to eliminate code that was duplicated 2-3 times across the build, skeleton,
407407+and DAG paths:
408408+409409+### populate_opam_repository ([util.ml#L217](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/bin/util.ml#L217))
410410+411411+Copies opam files for a list of packages into an opam-repository directory,
412412+searching the configured repositories in order. Copies both the `opam` file
413413+and the optional `files/` subdirectory. Previously inlined in three places.
414414+415415+### write_skeleton_layer ([util.ml#L239](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/bin/util.ml#L239))
416416+417417+Creates a skeleton layer for a cascade-failed package:
418418+419419+```ocaml
420420+let write_skeleton_layer ~cache_dir ~os_key ~opam_repositories
421421+ ~layer_name ~pkg ~ordered_deps ~dep_build_hashes =
422422+ let layer_dir = Path.(cache_dir / os_key / layer_name) in
423423+ if not (Sys.file_exists layer_dir) then begin
424424+ Os.mkdir ~parents:true layer_dir;
425425+ save_layer_info Path.(layer_dir / "layer.json")
426426+ pkg ordered_deps dep_build_hashes (-1);
427427+ let opam_repo = create_opam_repository layer_dir in
428428+ populate_opam_repository ~opam_repo ~opam_repositories
429429+ (pkg :: ordered_deps);
430430+ ensure_package_layer_symlink ~cache_dir ~os_key
431431+ ~pkg_str:(OpamPackage.to_string pkg) ~layer_name
432432+ end
433433+```
434434+435435+The `exit_status = -1` sentinel marks the layer as "never built". The embedded
436436+`opam-repository/` means `rerun` and `cascade` can rebuild without needing an
437437+external opam-repository path. Previously inlined in two places (sequential
438438+build path and DAG `on_cascade` callback).
439439+440440+### wait_for_layer_json ([util.ml#L251](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/bin/util.ml#L251))
441441+442442+Polls for a `layer.json` file to appear (created by a parallel worker), with
443443+0.5s intervals for up to 5 minutes. Also updates the file's timestamp.
444444+Previously inlined in three places (build, doc, and jtw layer functions).
445445+446446+---
447447+448448+## 8. On-Disk Structure
449449+450450+The [on-disk structure doc](https://tangled.org/jon.recoil.org/monopam-myspace/blob/main/day10/docs/ON_DISK_STRUCTURE.md)
451451+describes the full layout. The key design properties:
452452+453453+**Content-addressable layers.** Build hashes are computed from the base image
454454+hash plus the opam file contents of every dependency. Identical dependency sets
455455+always produce the same hash, enabling cross-solution cache reuse. When the
456456+base image changes, all hashes change and stale layers are naturally
457457+invalidated.
458458+459459+**Self-contained layers.** Each layer directory contains everything needed to
460460+rebuild it: `layer.json` (metadata, dep list, dep hashes), `opam-repository/`
461461+(exact opam files), `build.log`, and `fs/` (filesystem delta). The `rerun` and
462462+`cascade` commands exploit this: they read the skeleton layer's embedded opam
463463+files rather than requiring an external opam-repository path.
464464+465465+**Append-only history.** Per-package `history.jsonl` files provide a full
466466+audit trail of build results across runs. The `compact` operation prevents
467467+unbounded growth by collapsing old consecutive same-status entries.
468468+469469+**Atomic writes.** `status.json` and compacted history files are written via
470470+temp file + rename. History appends use file locking. Layer directories are
471471+created with exclusive locking via `create_directory_exclusively`.
472472+473473+```
474474+<cache-dir>/
475475+├── solutions/<opam-repo-sha>/<pkg>.json
476476+├── logs/{<pid>.log, runs/<run-id>/{summary.json, build/, docs/}}
477477+└── <os-key>/
478478+ ├── build-config.json
479479+ ├── status.json
480480+ ├── base/{Dockerfile, fs/, build.log, base.hash}
481481+ ├── build-<hash>/{layer.json, build.log, fs/, opam-repository/}
482482+ ├── doc-<hash>/{layer.json, odoc-voodoo-all.log}
483483+ ├── jtw-<hash>/{layer.json, jtw.log}
484484+ ├── universes/<hash>.json
485485+ └── packages/<pkg>/{history.jsonl, build-<hash>→, blessed-build→}
486486+```