::  ford: build system vane
!:
::    Ford is a functional reactive build system.
::
::    A Ford build is a function of the Urbit namespace and a date that
::    produces marked, typed data or an error.
::
::    The function in the definition of a build is called a "schematic,"
::    and it's represented by a Hoon data structure with twenty-five sub-types.
::    A schematic is a (possibly trivial) DAG of sub-builds to be performed.
::    The different schematic sub-types transform the results of their
::    sub-builds in different ways.
::
::    We call the date in the definition of a build the "formal date" to
::    distinguish it from the time at which the build was performed.
::
::    Each build is referentially transparent with respect to its formal date:
::    ask to run that function on the namespace and a particular formal date,
::    and Ford will always produce the same result.
::
::    We can now say Ford is a functional build system, since each build is a
::    function. We have not yet explained how it's a functional reactive build
::    system. With Ford, you can subscribe to results of a build. Ford tracks
::    the result of a "live" build consisting of a static schematic and the
::    ever-changing current date. Whenever this live build's result changes,
::    Ford sends you the new result and the formal date of the build (the date
::    which would cause the same result if you asked Ford to build that
::    schematic again). This is a push-based FRP paradigm.
::
::    The implementation is event-driven, like the rest of Urbit. While
::    performing a build, Ford registers each namespace access as a dependency
::    and also notes whether the dependency is "live," meaning the path within
::    the namespace updates with time. For example a live Clay dependency would
::    update the +case within the +beam over time.
::
::    A request to perform a build without subscribing to its future changes is
::    called a "once build."
::
::    After finishing a build, Ford subscribes to updates on the build's
::    dependencies. For now, this just means it subscribes to Clay for file
::    changes. Whenever any of the files in the subscription have new contents,
::    Clay will notify Ford, which will then rerun any live builds that depend
::    on any of the changed files and send its subscribers the new results.
::
::    This matches the semantics of live builds defined above. If someone had
::    asked for a build of the schematic with a formal date d2 just before the
::    changed Clay files, Ford would respond with the result of the previous
::    build with formal date d1, which would still be an accurate
::    representation of the schematic's result at d2, since Ford knows none of
::    its dependencies changed between d1 and d2.
::
::    Note that Ford can only calculate dependencies after running a build,
::    not before. This is because Ford can be thought of as an interpreter for
::    schematics, rather than a compiler, in the sense that it can't have a
::    dependency-gathering step followed by a build step. The dependencies of
::    some schematics must be calculated based on results, e.g. the %alts
::    schematic, which tries a sequence of sub-builds until one succeeds. If
::    the first sub-build succeeds, the build depends only on that first
::    sub-build, but if the first fails and the second succeeds, the build
::    depends on both.
::
::    This dynamicity implies we don't know what we depend on until we depend
::    on it. Most build systems have this property, but this part of Ford's
::    job is easier than for most Unix-based build systems: Ford draws all
::    resources from an immutable namespace, and it can track every access of
::    that namespace.
::
::    Ford might produce a build's result asynchronously, in a subsequent Arvo
::    event. This happens when accessing the namespace doesn't complete
::    synchronously, such as when grabbing a file from another ship. Ford
::    guarantees it will respond with build results in chronological order
::    using the formal date, not the order in which the builds completed.
::
::    Ford does not guarantee it will notify a subscriber of a changed build
::    only once per change. In common usage it will not send duplicate
::    notifications, but it might if its cache was recently wiped.
::
::    Ford uses dependency tracking, caching, and results of previous builds
::    to eliminate excess work. When rerunning a live build, Ford "promotes"
::    previous results to the new time if the build's dependencies hvaen't
::    changed since the previous build's formal date. Ford does this check
::    for each build in a tree of sub-builds under the "root build," which
::    is the build that was requested directly.
::
::    In addition to the main %build +task sub-type, Ford also supports
::    four other commands:
::
::    %kill: cancel a build
::
::      A once build in progress will be canceled, including all of its
::      sub-builds that aren't part of any other builds.
::
::      A live build's subscriptions will be canceled, its completed results
::      will be deleted, and its dependency tracking information will be
::      deleted. If a rebuild is in progress, it will be canceled.
::
::    %keep: resize caches
::
::      Ford maintains two caches: a :compiler-cache that stores
::      content-addressed compiler operations, such as parsing, compiling,
::      and type inference; and a :build-cache that stores previously
::      completed build trees along with their results and dependency tracking.
::
::      The %keep command resets the maximum sizes of these caches, deleting
::      entries if necessary.
::
::    %wipe: decimate storage
::
::      The %wipe command removes build results from storage to free memory.
::      It deletes the specified percentage of build results, in LRU
::      (Least Recently Used) order. It also removes entries from the compiler
::      cache. It does not remove dependency tracking information.
::
::    %wegh: report memory usage
::
::      Like all vanes, Ford can also be asked to produce a human-readable
::      report of its memory usage. Nock cannot calculate its own memory use
::      directly, so instead we produce the nouns themselves, which the runtime
::      "weighs" based on its memory model.
::
::    For details on Ford's implementation, consult Ford's vane interface core
::    near the bottom of the file.
::
::  pit: a +vase of the hoon+zuse kernel, which is a deeply nested core
::
|=  pit=vase
::
=,  contain
=,  ford
::  ford internal data structures
::
=>  =~
|%
::  +move: arvo moves that ford can emit
::
+=  move
  ::
  $:  ::  duct: request identifier
      ::
      =duct
      ::  card: move contents; either a +note or a +gift:able
      ::
      card=(wind note gift:able)
  ==
::  +note: private request from ford to another vane
::
+$  note
  $~  [%c %warp *@p *riff:clay]
  $%  ::  %c: to clay
      ::
      $:  %c
          ::  %warp: internal (intra-ship) file request
          ::
          $>(%warp task:able:clay)
  ==  ==
::  +sign: private response from another vane to ford
::
+$  sign
  $~  [%c %writ *riot:clay]
  $?  ::  %c: from clay
      ::
      ::    XX also from behn due to %slip asynchronicity
      ::
      $:  ?(%b %c)
          $>  $?  ::  %writ: internal (intra-ship) file response
                  ::
                  %writ
                  ::  %wris: response to %mult; many changed files
                  ::
                  %wris
              ==
          gift:able:clay
  ==  ==
--
|%
::  +axle: overall ford state
::
+=  axle
  $:  ::  date: date at which ford's state was updated to this data structure
      ::
      date=%~2018.12.13
      ::  state: all persistent state
      ::
      state=ford-state
  ==
::  +ford-state: all state that ford maintains
::
+=  ford-state
  $:  ::  builds: per-build state machine for all builds
      ::
      ::    Ford holds onto all in-progress builds that were either directly
      ::    requested by a duct (root builds) or that are dependencies
      ::    (sub-builds) of a directly requested build.
      ::
      ::    It also stores the last completed version of each live build tree
      ::    (root build and sub-builds), and any cached builds.
      ::
      builds=(map build build-status)
      ::  ducts: per-duct state machine for all incoming ducts (build requests)
      ::
      ::    Ford tracks every duct that has requested a build until it has
      ::    finished dealing with that request.
      ::
      ::    For live ducts, we store the duct while we repeatedly run new
      ::    versions of the live build it requested until it is explicitly
      ::    canceled by the requester.
      ::
      ::    A once (non-live) duct, on the other hand, will be removed
      ::    as soon as the requested build has been completed.
      ::
      ducts=(map duct duct-status)
      ::  builds-by-schematic: all attempted builds, sorted by time
      ::
      ::    For each schematic we've attempted to build at any time,
      ::    list the formal dates of all build attempts, sorted newest first.
      ::
      builds-by-schematic=(map schematic (list @da))
      ::  pending-scrys: outgoing requests for static resources
      ::
      pending-scrys=(request-tracker scry-request)
      ::  pending-subscriptions: outgoing subscriptions on live resources
      ::
      pending-subscriptions=(request-tracker subscription)
      ::  build-cache: fifo queue of completed root builds
      ::
      $=  build-cache
      $:  ::  next-anchor-id: incrementing identifier for cache anchors
          ::
          next-anchor-id=@ud
          ::  queue: fifo queue of root builds identified by anchor id
          ::
          queue=(capped-queue build-cache-key)
      ==
      ::  compiler-cache: clock based cache of build results
      ::
      compiler-cache=(clock compiler-cache-key build-result)
  ==
::  +anchor: something which holds on to builds
::
::    An anchor is a reference which keeps builds. This is either a %duct, in
::    which case the build is live because a duct is waiting for a response, or
::    a %cache, in which case the anchor is a cached build.
::
::    When a duct would be removed from a build, the %duct anchor is replaced
::    with a %cache anchor. This %cache anchor refers to a FIFO queue of cached
::    builds.
::
+=  anchor
  $%  ::  %duct: this is anchored on a duct
      ::
      [%duct =duct]
      ::  %cache: this is anchored to a cache entry
      ::
      [%cache id=@ud]
  ==
::  +build-status: current data for a build, including construction status
::
::    +build-status stores the construction status of a build as a finite state
::    machine (:state). It stores links to dependent sub-builds in :subs, and
::    per-duct client builds in :clients.
::
+=  build-status
  $:  ::  requesters: ducts for whom this build is the root build
      ::
      requesters=(set anchor)
      ::  clients: per duct information for this build
      ::
      clients=(jug anchor build)
      ::  subs: sub-builds of this build, for whom this build is a client
      ::
      subs=(map build build-relation)
      ::  state: a state machine for tracking the build's progress
      ::
      $=  state
      $%  $:  ::  %untried: build has not been started yet
              ::
              %untried  ~
          ==
          $:  ::  %blocked: build blocked on either sub-builds or resource
              ::
              ::    If we're in this state and there are no blocks in :subs,
              ::    then we're blocked on a resource.
              ::
              %blocked  ~
          ==
          $:  ::  %unblocked: we were blocked but now we aren't
              ::
              %unblocked  ~
          ==
          $:  ::  %complete: build has finished running and has a result
              ::
              %complete
              ::  build-record: the product of the build, possibly tombstoned
              ::
              =build-record
  ==  ==  ==
::  +duct-status: information relating a build to a duct
::
+=  duct-status
  $:  ::  live: whether this duct is being run live
      ::
      $=  live
      $%  [%once in-progress=@da]
          $:  %live
              ::
              ::
              in-progress=(unit @da)
              ::  the last subscription we made
              ::
              ::    This can possibly have an empty set of resources, in which
              ::    we never sent a move.
              ::
              ::    NOTE: This implies that a single live build can only depend
              ::    on live resources from a single disc. We don't have a
              ::    working plan for fixing this and will need to think very
              ::    hard about the future.
              ::
              last-sent=(unit [date=@da subscription=(unit subscription)])
      ==  ==
      ::  root-schematic: the requested build for this duct
      ::
      root-schematic=schematic
  ==
::  +build-relation: how do two builds relate to each other?
::
::    A +build-relation can be either :verified or not, and :blocked or not.
::    It is a symmetric relation between two builds, in the sense that both
::    the client and the sub will store the same relation, just pointing to
::    the other build.
::
::    If it's not :verified, then the relation is a guess based on previous
::    builds. These guesses are used to ensure that we hold onto builds we
::    expect to be used in future builds. Each time we run +make on a build,
::    it might produce new :verified sub-builds, which may have been unverified
::    until then. Once a build completes, any unverified sub-builds must be
::    cleaned up, since it turned out they weren't used by the build after all.
::
::    :blocked is used to note that a build can't be completed until that
::    sub-build has been completed. A relation can be :blocked but not :verified
::    if we're trying to promote a build, but we haven't run all its sub-builds
::    yet. In that case, we'll try to promote or run the sub-build in order to
::    determine whether we can promote the client. Until the sub-build has been
::    completed, the client is provisionally blocked on the sub-build.
::
+=  build-relation
  $:  ::  verified: do we know this relation is real, or is it only a guess?
      ::
      verified=?
      ::  is this build blocked on this other build?
      ::
      blocked=?
  ==
::  +build-record: information associated with the result of a completed +build
::
+=  build-record
  $%  $:  ::  %tombstone: the build's result has been wiped
          ::
          %tombstone  ~
      ==
      $:  ::  %value: we have the +build-result
          ::
          %value
          ::  last-accessed: last time we looked at the result
          ::
          ::    This is used for LRU cache reclamation.
          ::
          last-accessed=@da
          ::  build-result: the stored value of the build's product
          ::
          =build-result
  ==  ==
::  +build: a referentially transparent request for a build
::
::    Each unique +build will always produce the same +build-result
::    when run (if it completes). A live build consists of a sequence of
::    instances of +build with the same :schematic and increasing :date.
::
+=  build
  $:  ::  date: the formal date of this build; unrelated to time of execution
      ::
      date=@da
      ::  schematic: the schematic that determines how to run this build
      ::
      =schematic
  ==
::  +request-tracker: generic tracker and multiplexer for pending requests
::
++  request-tracker
  |*  request-type=mold
  %+  map  request-type
  $:  ::  waiting: ducts blocked on this request
      ::
      waiting=(set duct)
      ::  originator: the duct that kicked off the request
      ::
      originator=duct
  ==
::  +subscription: a single subscription to changes on a set of resources
::
+=  subscription
  $:  ::  date: date this was made
      ::
      date=@da
      ::  disc: ship and desk for all :resources
      ::
      =disc
      ::  resources: we will be notified if any of these resources change
      ::
      resources=(set resource)
  ==
::  +scry-request: parsed arguments to a scry operation
::
+=  scry-request
  $:  ::  vane: the vane from which to make the request
      ::
      ::    If we add other vanes in the future, this will become a fork type.
      ::    For now, though, Ford only knows how to make asynchronous scry
      ::    requests to Clay.
      ::
      vane=%c
      ::  care: type of request
      ::
      care=care:clay
      ::  beam: request path
      ::
      =beam
  ==
::  +compiler-cache-key: content addressable build definitions
::
+=  compiler-cache-key
  $%  [%call gate=vase sample=vase]
      [%hood =beam txt=@t]
      [%ride formula=hoon subject=vase]
      [%slim subject-type=type formula=hoon]
      [%slit gate=type sample=type]
  ==
::  +build-cache-key: key for the fifo cache of completed build trees
::
+=  build-cache-key
  $:  ::  id: incrementing identifier for an +anchor
      ::
      id=@ud
      ::  root-build: the root build associated with this anchor
      ::
      root-build=build
  ==
::  +build-receipt: result of running +make
::
::    A +build-receipt contains all information necessary to perform the
::    effects and state mutations indicated by a call to +make. If :build
::    succeeded, :result will be %build-result; otherwise, it will be %blocks.
::
::    After +make runs on a batch of builds, the resulting +build-receipt's are
::    applied one at a time.
::
+=  build-receipt
  $:  ::  build: the build we worked on
      ::
      =build
      ::  result: the outcome of this build
      ::
      $=  result
      $%  ::  %build-result: the build produced a result
          ::
          $:  %build-result
              =build-result
          ==
          ::  %blocks: the build blocked on the following builds or resource
          ::
          $:  %blocks
              ::  builds: builds that :build blocked on
              ::
              builds=(list build)
          ==
      ==
      ::  sub-builds: subbuilds of :build
      ::
      ::    While running +make on :build, we need to keep track of any
      ::    sub-builds that we try to access so we can keep track of
      ::    component linkages and cache access times.
      ::
      sub-builds=(list build)
      ::  cache-access: if not ~, cache this result as :compiler-cache-key.
      ::
      cache-access=(unit [=compiler-cache-key new=?])
  ==
--
=,  format
|%
::  +tear: split a +term into segments delimited by `-`
::
::  Example:
::  ```
::  dojo> (tear 'foo-bar-baz')
::  ['foo' 'bar' 'baz']
::  ```
::
++  tear
  |=  a=term
  ^-  (list term)
  ::  sym-no-heps: a parser for terms with no heps and a leading letter
  ::
  =/  sym-no-heps  (cook crip ;~(plug low (star ;~(pose low nud))))
  ::
  (fall (rush a (most hep sym-no-heps)) /[a])
::  +segments: compute all paths from :path-part, replacing some `/`s with `-`s
::
::    For example, when passed a :path-part of 'foo-bar-baz',
::    the product will contain:
::    ```
::    dojo> (segments 'foo-bar-baz')
::    [/foo/bar/baz /foo/bar-baz /foo-bar/baz /foo-bar-baz]
::    ```
::
++  segments
  |=  path-part=@tas
  ^-  (list path)
  ::
  =/  join  |=([a=@tas b=@tas] (crip "{(trip a)}-{(trip b)}"))
  ::
  =/  torn=(list @tas)  (tear path-part)
  ::
  |-  ^-  (list (list @tas))
  ::
  ?<  ?=(~ torn)
  ::
  ?:  ?=([@ ~] torn)
    ~[torn]
  ::
  %-  zing
  %+  turn  $(torn t.torn)
  |=  s=(list @tas)
  ^-  (list (list @tas))
  ::
  ?>  ?=(^ s)
  ~[[i.torn s] [(join i.torn i.s) t.s]]
::  +build-to-tape: convert :build to a printable format
::
::    Builds often contain the standard library and large types, so
::    this function should always be called when trying to print a +build.
::
++  build-to-tape
  |=  =build
  ^-  tape
  ~+
  ::
  =/  enclose  |=(tape "[{+<}]")
  =/  date=@da  date.build
  =/  =schematic  schematic.build
  ::
  %-  enclose
  %+  welp  (trip (scot %da date))
  %+  welp  " "
  ::
  ?+      -.schematic
        :(welp "[" (trip -.schematic) " {<`@uvI`(mug schematic)>}]")
      ::
      %$
    "literal"
  ::
      ^
    %-  enclose
    ;:(welp $(build [date head.schematic]) " " $(build [date tail.schematic]))
  ::
      %alts
    ;:  welp
      %+  roll  choices.schematic
      |=  [choice=^schematic txt=_"[alts"]
      :(welp txt " " ^$(schematic.build choice))
    ::
      "]"
    ==
  ::
      %core
    :(welp "[core " (spud (en-beam (rail-to-beam source-path.schematic))) "]")
  ::
      %hood
    :(welp "[hood " (spud (en-beam (rail-to-beam source-path.schematic))) "]")
  ::
      %plan
    ;:  welp
      "[plan "
      (spud (en-beam (rail-to-beam path-to-render.schematic)))
      "]"
    ==
  ::
      %scry
    (spud (en-beam (extract-beam resource.schematic ~)))
  ::
    ::    %slim
    ::  "slim {<subject-type.schematic>} {<formula.schematic>}"
  ::
      %vale
    ;:  welp
      "[vale ["
      (trip (scot %p ship.disc.schematic))
      " "
      (trip desk.disc.schematic)
      "] "
      (trip mark.schematic)
      "]"
    ==
  ==
::  +rail-to-beam: convert :rail to a +beam, filling in the case with `[%ud 0]`
::
++  rail-to-beam
  |=  =rail
  ^-  beam
  [[ship.disc.rail desk.disc.rail [%ud 0]] spur.rail]
::  +rail-to-path: pretty-printable rail
::
++  rail-to-path
  |=  =rail
  ^-  path
  (en-beam (rail-to-beam rail))
::  +unify-jugs: make a new jug, unifying sets for all keys
::
::    Example:
::    ```
::    dojo> %+  unify-jugs
::            (~(gas by *(jug @tas @ud)) ~[[%a (sy 1 2 ~)] [%b (sy 4 5 ~)]])
::          (~(gas by *(jug @tas @ud)) ~[[%b (sy 5 6 ~)] [%c (sy 7 8 ~)]])
::
::    {[p=%a q={1 2 3}] [p=%b q={4 5 6}] [p=%c q={7 8}]}
::    ```
::
++  unify-jugs
  |*  [a=(jug) b=(jug)]
  ^+  a
  ::
  =/  tapped  ~(tap by b)
  ::
  |-  ^+  a
  ?~  tapped  a
  ::
  =/  key  p.i.tapped
  =/  vals  ~(tap in q.i.tapped)
  ::
  =.  a
    |-  ^+  a
    ?~  vals  a
    ::
    $(vals t.vals, a (~(put ju a) key i.vals))
  ::
  $(tapped t.tapped)
::  +path-to-resource: decode a +resource from a +wire
::
++  path-to-resource
  |=  =path
  ^-  (unit resource)
  ::
  =/  scry-request=(unit scry-request)  (path-to-scry-request path)
  ?~  scry-request
    ~
  =+  [vane care bem]=u.scry-request
  =/  =beam  bem
  =/  =rail  [disc=[p.beam q.beam] spur=s.beam]
  `[vane care rail]
::  +scry-request-to-path: encode a +scry-request in a +wire
::
::    Example:
::    ```
::    dojo> %-  scry-request-to-path
::          [%c %x [[~zod %home [%da ~2018.1.1]] /hoon/bar]])
::
::    /cx/~zod/home/~2018.1.1/bar/hoon
::    ```
::
++  scry-request-to-path
  |=  =scry-request
  ^-  path
  =/  =term  (cat 3 [vane care]:scry-request)
  [term (en-beam beam.scry-request)]
::  +path-to-scry-request: parse :path's components into :vane, :care, and :rail
::
++  path-to-scry-request
  |=  =path
  ^-  (unit scry-request)
  ::
  ?~  path
    ~
  ?~  vane=((soft ,%c) (end 3 1 i.path))
    ~
  ?~  care=((soft care:clay) (rsh 3 1 i.path))
    ~
  ?~  beam=(de-beam t.path)
    ~
  ?.  ?=(%da -.r.u.beam)
    ~
  `[u.vane u.care u.beam]
::  +scry-request-to-build: convert a +scry-request to a %scry build
::
++  scry-request-to-build
  |=  =scry-request
  ^-  build
  ::  we only operate on dates, not other kinds of +case:clay
  ::
  ?>  ?=(%da -.r.beam.scry-request)
  ::
  =,  scry-request
  [p.r.beam [%scry [vane care `rail`[[p q] s]:beam]]]
::  +extract-beam: obtain a +beam from a +resource
::
::    Fills case with [%ud 0] for live resources if :date is `~`.
::    For once resources, ignore :date.
::
++  extract-beam
  |=  [=resource date=(unit @da)]  ^-  beam
  ::
  =/  =case  ?~(date [%ud 0] [%da u.date])
  ::
  =,  rail.resource
  [[ship.disc desk.disc case] spur]
::  +extract-disc: obtain a +disc from a +resource
::
++  extract-disc
  |=  =resource  ^-  disc
  disc.rail.resource
::  +get-sub-schematics: find any schematics contained within :schematic
::
++  get-sub-schematics
  |=  =schematic
  ^-  (list ^schematic)
  ?-    -.schematic
      ^      ~[head.schematic tail.schematic]
      %$     ~
      %pin   ~[schematic.schematic]
      %alts  choices.schematic
      %bake  ~
      %bunt  ~
      %call  ~[gate.schematic sample.schematic]
      %cast  ~[input.schematic]
      %core  ~
      %diff  ~[start.schematic end.schematic]
      %dude  ~[attempt.schematic]
      %hood  ~
      %join  ~[first.schematic second.schematic]
      %list  schematics.schematic
      %mash  ~[schematic.first.schematic schematic.second.schematic]
      %mute  [subject.schematic (turn mutations.schematic tail)]
      %pact  ~[start.schematic diff.schematic]
      %path  ~
      %plan  ~
      %reef  ~
      %ride  ~[subject.schematic]
      %same  ~[schematic.schematic]
      %scry  ~
      %slim  ~
      %slit  ~
      %vale  ~
      %volt  ~
      %walk  ~
  ==
::  +by-schematic: door for manipulating :by-schematic.builds.ford-state
::
::    The :dates list for each key in :builds is sorted in reverse
::    chronological order. These operations access and mutate keys and values
::    of :builds and maintain that sort order.
::
++  by-schematic
  |_  builds=(map schematic (list @da))
  ::  +put: add a +build to :builds
  ::
  ::    If :build already exists in :builds, this is a no-op.
  ::    Otherwise, replace the value at the key :schematic.build
  ::    with a new :dates list that contains :date.build.
  ::
  ++  put
    |=  =build
    ^+  builds
    %+  ~(put by builds)  schematic.build
    ::
    =/  dates  (~(gut by builds) schematic.build ~)
    |-
    ^+  dates
    ?~  dates
      [date.build ~]
    ?:  =(i.dates date.build)
      dates
    ?:  (gth date.build i.dates)
      [date.build dates]
    [i.dates $(dates t.dates)]
  ::  +del: remove a +build from :builds
  ::
  ::    Removes :build from :builds by replacing the value at
  ::    the key :schematic.build with a new :dates list with
  ::    :date.build omitted. If the resulting :dates list is
  ::    empty, then remove the key-value pair from :builds.
  ::
  ++  del
    |=  =build
    ^+  builds
    =.  builds
      %+  ~(jab by builds)  schematic.build
      |=  dates=(list @da)
      ~|  build+build
      =/  date-index  (need (find [date.build]~ dates))
      (oust [date-index 1] dates)
    ::  if :builds has an empty entry for :build, delete it
    ::
    =?    builds
        =(~ (~(got by builds) schematic.build))
      (~(del by builds) schematic.build)
    ::
    builds
  ::  +find-previous: find the most recent older build with :schematic.build
  ::
  ++  find-previous
    |=  =build
    ^-  (unit ^build)
    ::
    =/  dates=(list @da)  (~(gut by builds) schematic.build ~)
    ::
    |-  ^-  (unit ^build)
    ?~  dates  ~
    ::
    ?:  (lth i.dates date.build)
      `[i.dates schematic.build]
    $(dates t.dates)
  ::  +find-next: find the earliest build of :schematic.build later than :build
  ::
  ++  find-next
    |=  =build
    ^-  (unit ^build)
    ::
    =/  dates=(list @da)  (flop (~(gut by builds) schematic.build ~))
    ::
    |-  ^-  (unit ^build)
    ?~  dates  ~
    ::
    ?:  (gth i.dates date.build)
      `[i.dates schematic.build]
    $(dates t.dates)
  --
::  +get-request-ducts: all ducts waiting on this request
::
++  get-request-ducts
  |*  [tracker=(request-tracker) request=*]
  ^-  (list duct)
  ::
  ?~  val=(~(get by tracker) request)
    ~
  ~(tap in waiting.u.val)
::  +put-request: associates a +duct with a request
::
++  put-request
  |*  [tracker=(request-tracker) request=* =duct]
  ::
  %+  ~(put by tracker)  request
  ?~  existing=(~(get by tracker) request)
    [(sy duct ~) duct]
  u.existing(waiting (~(put in waiting.u.existing) duct))
::  +del-request: remove a duct and produce the originating duct if empty
::
++  del-request
  |*  [tracker=(request-tracker) request=* =duct]
  ^-  [(unit ^duct) _tracker]
  ::  remove :duct from the existing :record of this :request
  ::
  =/  record  (~(got by tracker) request)
  =.  waiting.record  (~(del in waiting.record) duct)
  ::  if no more ducts wait on :request, delete it
  ::
  ?^  waiting.record
    [~ (~(put by tracker) request record)]
  [`originator.record (~(del by tracker) request)]
::  +parse-scaffold: produces a parser for a hoon file with +crane instances
::
::    Ford parses a superset of hoon which contains additional runes to
::    represent +crane s. This parses to a +scaffold.
::
::    src-beam: +beam of the source file we're parsing
::
++  parse-scaffold
  |=  src-beam=beam
  ::
  =/  hoon-parser  (vang & (en-beam src-beam))
  |^  ::
      %+  cook
        |=  a=[@ud (list ^cable) (list ^cable) (list ^crane) (list hoon)]
        ^-  scaffold
        [[[p q] s]:src-beam a]
      ::
      %+  ifix  [gay gay]
      ;~  plug
      ::  parses the zuse version, eg "/?  309"
      ::
        ;~  pose
          (ifix [;~(plug net wut gap) gap] dem)
          (easy zuse)
        ==
      ::  pareses the structures, eg "/-  types"
      ::
        ;~  pose
          (ifix [;~(plug net hep gap) gap] (most ;~(plug com gaw) cable))
          (easy ~)
        ==
      ::  parses the libraries, eg "/+  lib1, lib2"
      ::
        ;~  pose
          (ifix [;~(plug net lus gap) gap] (most ;~(plug com gaw) cable))
          (easy ~)
        ==
      ::
        (star ;~(sfix crane gap))
      ::
        (most gap tall:hoon-parser)
      ==
  ::  +beam: parses a hood path and converts it to a beam
  ::
  ++  beam
    %+  sear  de-beam
    ;~  pfix
      net
      (sear plex (stag %clsg poor)):hoon-parser
    ==
  ::  +cable: parses a +^cable, a reference to something on the filesystem
  ::
  ::    This parses:
  ::
  ::      `library`       ->  wraps `library` around the library `library`
  ::      `face=library`  ->  wraps `face` around the library `library`
  ::      `*library`      ->  exposes `library` directly to the subject
  ::
  ++  cable
    %+  cook  |=(a=^cable a)
    ;~  pose
      (stag ~ ;~(pfix tar sym))
      (cook |=([face=term tis=@ file=term] [`face file]) ;~(plug sym tis sym))
      (cook |=(a=term [`a a]) sym)
    ==
  ::  +crane: all runes that start with / which aren't /?, /-, /+ or //.
  ::
  ++  crane
    =<  apex
    ::  whether we allow tall form
    =|  allow-tall-form=?
    ::
    |%
    ++  apex
      %+  knee  *^crane  |.  ~+
      ;~  pfix  net
        ;~  pose
          ::  `/~`  hoon literal
          ::
          (stag %fssg ;~(pfix sig hoon))
          ::  `/$`  process query string
          ::
          (stag %fsbc ;~(pfix bus hoon))
          ::  `/|`  first of many options that succeeds
          ::
          (stag %fsbr ;~(pfix bar parse-alts))
          ::  `/=`  wrap a face around a crane
          ::
          (stag %fsts ;~(pfix tis parse-face))
          ::  `/.`  null terminated list
          ::
          (stag %fsdt ;~(pfix dot parse-list))
          ::  `/,`  switch by path
          ::
          (stag %fscm ;~(pfix com parse-switch))
          ::  `/&`  pass through a series of mark
          ::
          (stag %fspm ;~(pfix pad parse-pipe))
          ::  `/_`  run a crane on each file in the current directory
          ::
          (stag %fscb ;~(pfix cab subcrane))
          ::  `/;`  passes date through a gate
          ::
          (stag %fssm ;~(pfix mic parse-gate))
          ::  `/:`  evaluate at path
          ::
          (stag %fscl ;~(pfix col parse-at-path))
          ::  `/^`  cast
          ::
          (stag %fskt ;~(pfix ket parse-cast))
          ::  `/*`  run a crane on each file with current path as prefix
          ::
          (stag %fstr ;~(pfix tar subcrane))
          ::  `/!mark/ evaluate as hoon, then pass through mark
          ::
          (stag %fszp ;~(pfix zap ;~(sfix sym net)))
          ::  `/mark/` passes current path through :mark
          ::
          (stag %fszy ;~(sfix sym net))
        ==
      ==
    ::  +parse-alts: parse a set of alternatives
    ::
    ++  parse-alts
      %+  wide-or-tall
        (ifix [lit rit] (most ace subcrane))
      ;~(sfix (star subcrane) gap duz)
    ::  +parse-face: parse a face around a subcrane
    ::
    ++  parse-face
      %+  wide-or-tall
        ;~(plug sym ;~(pfix tis subcrane))
      ;~(pfix gap ;~(plug sym subcrane))
    ::  +parse-list: parse a null terminated list of cranes
    ::
    ++  parse-list
      %+  wide-or-tall
        fail
      ;~(sfix (star subcrane) gap duz)
    ::  +parse-switch: parses a list of [path crane]
    ::
    ++  parse-switch
      %+  wide-or-tall
        fail
      =-  ;~(sfix (star -) gap duz)
      ;~(pfix gap net ;~(plug static-path subcrane))
    ::  +parse-pipe: parses a pipe of mark conversions
    ::
    ++  parse-pipe
      %+  wide-or-tall
        ;~(plug (plus ;~(sfix sym pad)) subcrane)
      =+  (cook |=(a=term [a ~]) sym)
      ;~(pfix gap ;~(plug - subcrane))
    ::  +parse-gate: parses a gate applied to a crane
    ::
    ++  parse-gate
      %+  wide-or-tall
        ;~(plug ;~(sfix wide:hoon-parser mic) subcrane)
      ;~(pfix gap ;~(plug tall:hoon-parser subcrane))
    ::  +parse-at-path: parses a late bound bath
    ::
    ++  parse-at-path
      %+  wide-or-tall
        ;~(plug ;~(sfix late-bound-path col) subcrane)
      ;~(pfix gap ;~(plug late-bound-path subcrane))
    ::  +parse-cast: parses a mold and then the subcrane to apply that mold to
    ::
    ++  parse-cast
      %+  wide-or-tall
        ;~(plug ;~(sfix wyde:hoon-parser ket) subcrane)
      ;~(pfix gap ;~(plug till:hoon-parser subcrane))
    ::  +subcrane: parses a subcrane
    ::
    ++  subcrane
      %+  wide-or-tall
        apex(allow-tall-form |)
      ;~(pfix gap apex)
    ::  +wide-or-tall: parses tall form hoon if :allow-tall-form is %.y
    ::
    ++  wide-or-tall
      |*  [wide=rule tall=rule]
      ?.  allow-tall-form  wide
      ;~(pose wide tall)
    ::  +hoon: parses hoon as an argument to a crane
    ::
    ++  hoon
      %+  wide-or-tall
        (ifix [lac rac] (stag %cltr (most ace wide:hoon-parser)))
      ;~(pfix gap tall:hoon-parser)
    --
  ::  +static-path: parses a path
  ::
  ++  static-path
    (sear plex (stag %clsg (more net hasp))):hoon-parser
  ::  +late-bound-path: a path whose time varies
  ::
  ++  late-bound-path
    ;~  pfix  net
      %+  cook  |=(a=truss a)
      =>  hoon-parser
      ;~  plug
        (stag ~ gash)
        ;~(pose (stag ~ ;~(pfix cen porc)) (easy ~))
      ==
    ==
  --
::  +per-event: per-event core; main build engine
::
::    This arm produces a gate that when called with state and event
::    information produces the core of Ford's main build engine.
::
::    The main build engine core has the following entry points:
::
::      +start-build  start performing a build
::      +rebuild      rerun a live build at a new date
::      +unblock      continue a build that was waiting on a resource
::      +cancel       stop trying to run a build and delete its tracking info
::      +wipe         wipe the build storage to free memory
::      +keep         resize caches, deleting entries if necessary
::
::    The main internal arm is +execute-loop, which is called from +start-build,
::    +rebuild, and +unblock. +execute defines Ford's build loop.
::
++  per-event
  ::  moves: the moves to be sent out at the end of this event, reversed
  ::
  =|  moves=(list move)
  ::  scry-results: responses to scry's to handle in this event
  ::
  ::    If a value is `~`, the requested resource is not available.
  ::    Otherwise, the value will contain a +cage.
  ::
  =|  scry-results=(map scry-request (unit cage))
  ::  next-builds: builds to perform in the next iteration
  ::
  =|  next-builds=(set build)
  ::  candidate-builds: builds which might go into next-builds
  ::
  =|  candidate-builds=(set build)
  ::  gate that produces the +per-event core from event information
  ::
  ::    Produces a core containing Ford's main build engine.
  ::
  ~%  %f  ..is  ~
  |=  [[our=@p =duct now=@da scry=sley] state=ford-state]
  ::
  ~%  %per-event  +  ~
  |%
  ::  +finalize: extract moves and state from the +per-event core
  ::
  ::    Run once at the end of processing an event.
  ::
  ++  finalize
    ^-  [(list move) ford-state]
    [(flop moves) state]
  ::  |entry-points: externally fired arms
  ::
  ::+|  entry-points
  ::
  ::  +start-build: perform a fresh +build, either live or once
  ::
  ::    This might complete the build, or the build might block on one or more
  ::    requests for resources. Calls +execute-loop.
  ::
  ++  start-build
    ~/  %start-build
    |=  [=build live=?]
    ^-  [(list move) ford-state]
    ::
    =<  finalize
    ::  associate :duct with :build in :ducts.state
    ::
    =.  ducts.state
      %+  ~(put by ducts.state)  duct
      :_  schematic.build
      ?:  live
        [%live in-progress=`date.build last-sent=~]
      [%once in-progress=date.build]
    ::  register a state machine for :build in :builds.state
    ::
    =.  state  (add-build build)
    ::  :anchor: the reason we hold onto the root of this build tree
    ::
    =/  =anchor  [%duct duct]
    ::  register :duct as an anchor in :requesters.build-status
    ::
    ::    This establishes :build as the root build for :duct.
    ::
    =.  builds.state
      %+  ~(jab by builds.state)  build
      |=  =build-status
      build-status(requesters (~(put in requesters.build-status) anchor))
    ::  copy :anchor into any preexisting descendants
    ::
    ::    Sub-builds will reference :build in their :clients.build-status,
    ::    using `[%duct duct]` as the key. Some sub-builds might already
    ::    exist if we've already started running :build, so make sure they
    ::    know who their daddy is.
    ::
    =.  builds.state  (add-anchor-to-subs anchor build)
    ::  run +execute on :build in a loop until it completes or blocks
    ::
    (execute-loop (sy [build ~]))
  ::  +rebuild: rebuild a live build based on +resource updates
  ::
  ::    For every changed resource, run the %scry build for that
  ::    for that resource. Then rebuild upward using the main +execute-loop
  ::    until all relevant builds either complete or block on external
  ::    resources. Use dependency tracking information from the previous
  ::    run of this live build to inform the dependency tracking for this
  ::    new rebuild.
  ::
  ++  rebuild
    ~/  %rebuild
    |=  $:  =subscription
            new-date=@da
            =disc
            care-paths=(set [care=care:clay =path])
        ==
    ^-  [(list move) ford-state]
    ::
    ~|  [%rebuilding new-date disc]
    ::
    =<  finalize
    ::  mark this subscription as complete now that we've heard a response
    ::
    =.  pending-subscriptions.state
      +:(del-request pending-subscriptions.state subscription duct)
    ::  for every changed resource, create a %scry build
    ::
    =/  builds=(list build)
      %+  turn  ~(tap in care-paths)
      |=  [care=care:clay =path]
      ^-  build
      ::
      [new-date [%scry [%c care rail=[disc spur=(flop path)]]]]
    ::  sanity check; only rebuild live builds, not once builds
    ::
    =/  duct-status  (~(got by ducts.state) duct)
    ?>  ?=(%live -.live.duct-status)
    ::  sanity check; only rebuild once we've completed the previous one
    ::
    ?>  ?=(~ in-progress.live.duct-status)
    ?>  ?=(^ last-sent.live.duct-status)
    ::  set the in-progress date for this new build
    ::
    =.  ducts.state
      %+  ~(put by ducts.state)  duct
      duct-status(in-progress.live `new-date)
    ::  copy the previous build's tree as provisional sub-builds
    ::
    ::    This provides an upward rebuild path from leaves to root,
    ::    so that once the %scry builds complete, we'll know to rebuild
    ::    their clients. This process will continue up through rebuilding
    ::    the root build.
    ::
    ::    If the build at this new date ends up with a different set of
    ::    dependencies from its previous incarnation, provisional sub-builds
    ::    that weren't actually used will be removed in
    ::    +cleanup-orphaned-provisional-builds.
    ::
    =/  old-root=build
      [date.u.last-sent.live.duct-status root-schematic.duct-status]
    ::
    =.  state
      ::
      ~|  [%duct-doesnt-refer-to-real-build live.duct-status]
      ~|  [%missing-build (build-to-tape old-root)]
      ~|  [%dates (~(get by builds-by-schematic.state) root-schematic.duct-status)]
      ?>  (~(has by builds.state) old-root)
      ::
      (copy-build-tree-as-provisional old-root new-date=new-date)
    ::  gather all the :builds, forcing reruns
    ::
    ::    The normal +gather logic would promote the previous results
    ::    for these %scry builds, since we have subscriptions on them.
    ::    We pass `force=%.y` to ensure the builds get enqueued instead
    ::    of promoted.
    ::
    =.  ..execute  (gather (sy builds) force=%.y)
    ::  rebuild resource builds at the new date
    ::
    ::    This kicks off the main build loop, which will first build
    ::    :builds, then rebuild upward toward the root. If the whole
    ::    build tree completes synchronously, then this will produce
    ::    %made moves at the end of this event. Otherwise, it will
    ::    block on resources and complete during a later event.
    ::
    (execute-loop ~)
  ::  +unblock: continue builds that had blocked on :resource
  ::
  ::    A build can be stymied temporarily if it depends on a resource
  ::    that must be fetched asynchronously. +unblock is called when
  ::    we receive a response to a resource request that blocked a build.
  ::
  ::    We pick up the build from where we left off, starting with the
  ::    %scry build that blocked on this resource last time we tried it.
  ::
  ++  unblock
    ~/  %unblock
    |=  [=scry-request scry-result=(unit cage)]
    ^-  [(list move) ford-state]
    ::
    =<  finalize
    ::  place :scry-result in :scry-results.per-event
    ::
    ::    We don't want to call the actual +scry function again,
    ::    because we already tried that in a previous event and it
    ::    had no synchronous answer. This +unblock call is a result
    ::    of the response to the asynchronous request we made to
    ::    retrieve that resource from another vane.
    ::
    ::    Instead, we'll intercept any calls to +scry by looking up
    ::    the arguments in :scry-results.per-event. This is ok because
    ::    in this function we attempt to run every +build that had
    ::    blocked on the resource, so the information is guaranteed
    ::    to be used during this event before it goes out of scope.
    ::
    =.  scry-results  (~(put by scry-results) scry-request scry-result)
    ::  mark this +scry-request as complete now that we have a response
    ::
    =.  pending-scrys.state
      +:(del-request pending-scrys.state scry-request duct)
    ::  update :unblocked-build's state machine to reflect its new status
    ::
    =/  unblocked-build=build  (scry-request-to-build scry-request)
    =.  builds.state
      %+  ~(jab by builds.state)  unblocked-build
      |=  =build-status
      build-status(state [%unblocked ~])
    ::  jump into the main build loop, starting with :unblocked-build
    ::
    (execute-loop (sy unblocked-build ~))
  ::  +wipe: forcibly decimate build results from the state
  ::
  ::    +wipe decimates both the :compiler-cache and the results in
  ::    :builds.state. It removes the specified percentage of build results
  ::    from the state. For simplicity, it considers the weight of each
  ::    compiler cache line to be equal to the weight of a build result.
  ::
  ::    It deletes cache entries before dipping into :builds.state; it only
  ::    converts entries in :builds.state to %tombstone's if there aren't
  ::    enough entries in the compiler cache to sate the request's bloodlust.
  ::
  ::    When deleting results from :builds.state, it first sorts them by
  ::    their :last-accessed date so that the stalest builds are deleted first.
  ::    We do not touch the :build-cache directly, but because the results
  ::    of the builds in :build-cache live in :builds.state, the results of
  ::    both FIFO-cached builds and active builds are all sorted and trimmed.
  ::
  ++  wipe
    ~/  %wipe
    |=  percent-to-remove=@ud
    ^+  state
    ::  removing 0% is the same as doing nothing, so do nothing
    ::
    ?:  =(0 percent-to-remove)
      ~&  %wipe-no-op
      state
    ::
    ~|  [%wipe percent-to-remove=percent-to-remove]
    ?>  (lte percent-to-remove 100)
    ::  find all completed builds, sorted by :last-accessed date
    ::
    =/  completed-builds=(list build)
      =-  (turn - head)
      %+  sort
        ::  filter for builds with a stored +build-result
        ::
        %+  skim  ~(tap by builds.state)
        |=  [=build =build-status]
        ^-  ?
        ::
        ?=([%complete %value *] state.build-status)
      ::  sort by :last-accessed date
      ::
      |=  [[* a=build-status] [* b=build-status]]
      ^-  ?
      ::
      ?>  ?=([%complete %value *] state.a)
      ?>  ?=([%complete %value *] state.b)
      ::
      %+  lte
        last-accessed.build-record.state.a
      last-accessed.build-record.state.b
    ::  determine how many builds should remain after decimation
    ::
    ::    This formula has the property that repeated applications
    ::    of +wipe with anything other than 100% retention rate will
    ::    always eventually remove every build.
    ::
    =/  num-completed-builds=@ud
      (add (lent completed-builds) size.compiler-cache.state)
    =/  percent-to-keep=@ud  (sub 100 percent-to-remove)
    =/  num-to-keep=@ud  (div (mul percent-to-keep num-completed-builds) 100)
    =/  num-to-remove=@ud  (sub num-completed-builds num-to-keep)
    ::
    |^  ^+  state
        ::
        =+  cache-size=size.compiler-cache.state
        ?:  (lte num-to-remove cache-size)
          (remove-from-cache num-to-remove)
        =.  compiler-cache.state
          %~  purge
            (by-clock compiler-cache-key build-result)
          compiler-cache.state
        (tombstone-builds (sub num-to-remove cache-size))
    ::
    ++  remove-from-cache
      |=  count=@ud
      %_    state
          compiler-cache
        %-  %~  trim
                (by-clock compiler-cache-key build-result)
                compiler-cache.state
        count
      ==
    ::
    ++  tombstone-builds
      |=  num-to-remove=@ud
      ::
      ~|  [%wipe num-to-remove=num-to-remove]
      ::  the oldest :num-to-remove builds are considered stale
      ::
      =/  stale-builds  (scag num-to-remove completed-builds)
      ::  iterate over :stale-builds, replacing with %tombstone's
      ::
      |-  ^+  state
      ?~  stale-builds  state
      ::  replace the build's entry in :builds.state with a %tombstone
      ::
      =.  builds.state
        =<  builds
        %+  update-build-status  i.stale-builds
        |=  =build-status
        build-status(state [%complete %tombstone ~])
      ::
      $(stale-builds t.stale-builds)
    --
  ::  +keep: resize caches
  ::
  ::    Ford maintains two caches: a :build-cache for caching previously
  ::    completed build trees, and a :compiler-cache for caching various
  ::    compiler operations that tend to be shared among multiple builds.
  ::
  ::    To handle this command, we reset the maximum sizes of both of
  ::    these caches, removing entries from the caches if necessary.
  ::
  ++  keep
    ~/  %keep
    |=  [compiler-cache-size=@ud build-cache-size=@ud]
    ^+  state
    ::  pop old builds out of :build-cache and remove their cache anchors
    ::
    =^  pops  queue.build-cache.state
      %.  build-cache-size
      ~(resize (to-capped-queue build-cache-key) queue.build-cache.state)
    ::
    =.  state
      |-  ^+  state
      ?~  pops  state
      ::
      =.  state  (remove-anchor-from-root root-build.i.pops [%cache id.i.pops])
      ::
      $(pops t.pops)
    ::  resize the :compiler-cache
    ::
    %_    state
        compiler-cache
      %-  %~  resize
              (by-clock compiler-cache-key build-result)
              compiler-cache.state
      compiler-cache-size
    ==
  ::  +cancel: cancel a build
  ::
  ::    When called on a live build, removes all tracking related to the live
  ::    build, and no more %made moves will be sent for that build.
  ::
  ::    When called on a once build, removes all tracking related to the once
  ::    build, and that build will never be completed or have a %made sent.
  ::
  ::    When called on a build that isn't registered in :state, such as a
  ::    completed once build, or a build that has already been canceled,
  ::    prints and no-ops.
  ::
  ++  cancel  ^+  [moves state]
    ::
    =<  finalize
    ::
    ?~  duct-status=(~(get by ducts.state) duct)
      ~&  [%no-build-for-duct duct]
      ..execute
    ::  :duct is being canceled, so remove it unconditionally
    ::
    =.  ducts.state  (~(del by ducts.state) duct)
    ::  if the duct was not live, cancel any in-progress builds
    ::
    ?:  ?=(%once -.live.u.duct-status)
      ::
      =/  root-build=build  [in-progress.live root-schematic]:u.duct-status
      ::
      =.  ..execute  (cancel-scrys root-build)
      =.  state  (remove-anchor-from-root root-build [%duct duct])
      ..execute
    ::  if the duct was live and has an unfinished build, cancel it
    ::
    =?  ..execute  ?=(^ in-progress.live.u.duct-status)
      ::
      =/  root-build=build  [u.in-progress.live root-schematic]:u.duct-status
      ::
      =.  ..execute  (cancel-scrys root-build)
      =.  state  (remove-anchor-from-root root-build [%duct duct])
      ..execute
    ::  if there is no completed build for the live duct, we're done
    ::
    ?~  last-sent=last-sent.live.u.duct-status
      ..execute
    ::  there is a completed build for the live duct, so delete it
    ::
    =/  root-build=build  [date.u.last-sent root-schematic.u.duct-status]
    ::
    =.  state  (remove-anchor-from-root root-build [%duct duct])
    ::
    ?~  subscription.u.last-sent
      ..execute
    (cancel-clay-subscription u.subscription.u.last-sent)
  ::  +cancel-scrys: cancel all blocked %scry sub-builds of :root-builds
  ::
  ++  cancel-scrys
    |=  root-build=build
    ^+  ..execute
    ::
    =/  blocked-sub-scrys  ~(tap in (collect-blocked-sub-scrys root-build))
    ::
    |-  ^+  ..execute
    ?~  blocked-sub-scrys  ..execute
    ::
    =.  ..execute  (cancel-scry-request i.blocked-sub-scrys)
    ::
    $(blocked-sub-scrys t.blocked-sub-scrys)
  ::  +move-root-to-cache: replace :duct with a %cache anchor in :build's tree
  ::
  ++  move-root-to-cache
    ~/  %move-root-to-cache
    |=  =build
    ^+  state
    ::  obtain the new cache id and increment the :next-anchor-id in the state
    ::
    =^  new-id  next-anchor-id.build-cache.state
      =/  id=@ud  next-anchor-id.build-cache.state
      [id +(id)]
    ::  replace the requester in the root build
    ::
    =.  builds.state
      %+  ~(jab by builds.state)  build
      |=  =build-status
      %_    build-status
          requesters
        =-  (~(del in -) [%duct duct])
        =-  (~(put in -) [%cache new-id])
        requesters.build-status
      ==
    ::  enqueue :build into cache, possibly popping and deleting a stale build
    ::
    =^  oldest  queue.build-cache.state
      %.  [new-id build]
      ~(put (to-capped-queue build-cache-key) queue.build-cache.state)
    ::
    =?    state
        ?=(^ oldest)
      (remove-anchor-from-root root-build.u.oldest [%cache id.u.oldest])
    ::  recursively replace :clients in :build and descendants
    ::
    |-  ^+  state
    ::
    =/  client-status=build-status  (got-build build)
    =/  subs=(list ^build)  ~(tap in ~(key by subs.client-status))
    ::
    |-  ^+  state
    ?~  subs  state
    ::
    =.  builds.state
      %+  ~(jab by builds.state)  i.subs
      |=  =build-status
      %_    build-status
          clients
        ::  if we've already encountered :i.subs, don't overwrite
        ::
        ?:  (~(has by clients.build-status) [%cache new-id])
          clients.build-status
        ::
        =/  old-clients-on-duct  (~(get ju clients.build-status) [%duct duct])
        ::
        =-  (~(del by -) [%duct duct])
        =-  (~(put by -) [%cache new-id] old-clients-on-duct)
        clients.build-status
      ==
    ::
    =.  state  ^$(build i.subs)
    ::
    $(subs t.subs)
  ::  +remove-anchor-from-root: remove :anchor from :build's tree
  ::
  ++  remove-anchor-from-root
    ~/  %remove-anchor-from-root
    |=  [=build =anchor]
    ^+  state
    ::
    =.  builds.state
      %+  ~(jab by builds.state)  build
      |=  =build-status
      build-status(requesters (~(del in requesters.build-status) anchor))
    ::
    =.  builds.state  (remove-anchor-from-subs build anchor)
    ::
    (cleanup build)
  ::  +remove-anchor-from-subs: recursively remove :anchor from sub-builds
  ::
  ++  remove-anchor-from-subs
    ~/  %remove-anchor-from-subs
    |=  [=build =anchor]
    ^+  builds.state
    ::
    =/  =build-status  (got-build build)
    =/  subs=(list ^build)  ~(tap in ~(key by subs.build-status))
    =/  client=^build  build
    ::
    |-  ^+  builds.state
    ?~  subs  builds.state
    ::
    =/  sub-status=^build-status  (got-build i.subs)
    ::
    =.  clients.sub-status
      (~(del ju clients.sub-status) anchor client)
    ::
    =.  builds.state  (~(put by builds.state) i.subs sub-status)
    ::
    =?  builds.state  !(~(has by clients.sub-status) anchor)
      ::
      ^$(build i.subs)
    ::
    $(subs t.subs)
  ::  +add-anchors-to-build-subs: for each sub, add all of :build's anchors
  ::
  ++  add-anchors-to-build-subs
    ~/  %add-anchors-to-build-subs
    |=  =build
    ^+  state
    ::
    =/  =build-status  (got-build build)
    =/  new-anchors
      ~(tap in (~(put in ~(key by clients.build-status)) [%duct duct]))
    =/  subs  ~(tap in ~(key by subs.build-status))
    ::
    =.  state
      |-
      ^+  state
      ?~  subs  state
      ::
      =.  state  (add-build i.subs)
      ::
      $(subs t.subs)
    ::
    =.  builds.state
      |-  ^+  builds.state
      ?~  new-anchors  builds.state
      ::
      =.  builds.state  (add-anchor-to-subs i.new-anchors build)
      ::
      $(new-anchors t.new-anchors)
    ::
    state
  ::  +add-anchor-to-subs: attach :duct to :build's descendants
  ::
  ++  add-anchor-to-subs
    ~/  %add-anchor-to-subs
    |=  [=anchor =build]
    ^+  builds.state
    ::
    =/  =build-status  (got-build build)
    =/  subs=(list ^build)  ~(tap in ~(key by subs.build-status))
    =/  client=^build  build
    ::
    |-  ^+  builds.state
    ?~  subs  builds.state
    ::
    =/  sub-status=^build-status  (got-build i.subs)
    ::
    =/  already-had-anchor=?  (~(has by clients.sub-status) anchor)
    ::
    =.  clients.sub-status
      (~(put ju clients.sub-status) anchor client)
    ::
    =.  builds.state  (~(put by builds.state) i.subs sub-status)
    ::
    =?  builds.state  !already-had-anchor  ^$(build i.subs)
    ::
    $(subs t.subs)
  ::  +copy-build-tree-as-provisional: prepopulate new live build
  ::
  ::    Make a provisional copy of the completed old root build tree at the
  ::    :new time.
  ::
  ++  copy-build-tree-as-provisional
    ~/  %copy-build-tree-as-provisional
    |=  [old-root=build new-date=@da]
    ^+  state
    ~|  [old-root=(build-to-tape old-root) new-date=new-date]
    ::
    =/  old-client=build  old-root
    =/  new-client=build  old-client(date new-date)
    =.  state  (add-build new-client)
    ::
    =.  builds.state
      %+  ~(jab by builds.state)  new-client
      |=  =build-status
      build-status(requesters (~(put in requesters.build-status) [%duct duct]))
    ::
    =<  copy-node
    ::
    |%
    ++  copy-node
      ^+  state
      ::
      =/  old-build-status=build-status  (got-build old-client)
      ::
      =/  old-subs=(list build)  ~(tap in ~(key by subs.old-build-status))
      =/  new-subs=(list build)  (turn old-subs |=(a=build a(date new-date)))
      ::
      =.  builds.state
        (add-subs-to-client new-client new-subs [verified=%.n blocked=%.y])
      ::
      |-
      ^+  state
      ?~  old-subs
        state
      ::
      =.  state  (add-client-to-sub i.old-subs)
      =.  state
        copy-node(old-client i.old-subs, new-client i.old-subs(date new-date))
      ::
      $(old-subs t.old-subs)
    ::
    ++  add-client-to-sub
      |=  old-sub=build
      ^+  state
      ::
      =/  new-sub  old-sub(date new-date)
      =.  state  (add-build new-sub)
      ::
      =.  builds.state
        %+  ~(jab by builds.state)  new-sub
        |=  =build-status
        %_  build-status
          clients  (~(put ju clients.build-status) [%duct duct] new-client)
        ==
      ::
      state
    --
  ::  +add-subs-to-client: register :new-subs as subs of :new-client
  ::
  ++  add-subs-to-client
    ~/  %add-subs-to-client
    |=  [new-client=build new-subs=(list build) =build-relation]
    ^+  builds.state
    ::
    %+  ~(jab by builds.state)  new-client
    |=  =build-status
    %_    build-status
        subs
      %-  ~(gas by subs.build-status)
      %+  murn  new-subs
      |=  sub=build
      ^-  (unit (pair build ^build-relation))
      ::
      ?^  (~(get by subs.build-status) sub)
        ~
      `[sub build-relation]
    ==
  ::  |construction: arms for performing builds
  ::
  ::+|  construction
  ::
  ::  +execute-loop: +execute repeatedly until there's no more work to do
  ::
  ::    Keep running +execute until all relevant builds either complete or
  ::    block on external resource requests. See +execute for details of each
  ::    loop execution.
  ::
  ::    This implementation is for simplicity. In the longer term, we'd
  ::    like to just perform a single run through +execute and set a Behn timer
  ::    to wake us up immediately. This has the advantage that Ford stops hard
  ::    blocking the main Urbit event loop, letting other work be done.
  ::
  ++  execute-loop  !.
    ~/  %execute-loop
    |=  builds=(set build)
    ^+  ..execute
    ::
    =.  ..execute  (execute builds)
    ::
    ?:  ?&  ?=(~ next-builds)
            ?=(~ candidate-builds)
        ==
      ..execute
    ::
    $(builds ~)
  ::  +execute: main recursive construction algorithm
  ::
  ::    Performs the three step build process: First, figure out which builds
  ::    we're going to run this loop through the ford algorithm. Second, run
  ::    the gathered builds, possibly in parallel. Third, apply the
  ::    +build-receipt algorithms to the ford state.
  ::
  ++  execute
    ~/  %execute
    |=  builds=(set build)
    ^+  ..execute
    ::
    =.  ..execute  (gather builds force=%.n)
    ::
    =^  build-receipts  ..execute  run-builds
    ::
    (reduce build-receipts)
  ::  +gather: collect builds to be run in a batch
  ::
  ::    The +gather phase is the first of the three parts of +execute. In
  ::    +gather, we look through each item in :candidate-builds.  If we
  ::    should run the candidate build this cycle through the +execute loop, we
  ::    place it in :next-builds. +gather runs until it has no more candidates.
  ::
  ++  gather  !.
    ~/  %gather
    |=  [builds=(set build) force=?]
    ^+  ..execute
    ::  add builds that were triggered by incoming event to the candidate list
    ::
    =.  candidate-builds  (~(uni in candidate-builds) builds)
    ::
    |^  ^+  ..execute
        ::
        ?:  =(~ candidate-builds)
          ..execute
        ::
        =/  next=build
          ?<  ?=(~ candidate-builds)
          n.candidate-builds
        =.  candidate-builds  (~(del in candidate-builds) next)
        ::
        $(..execute (gather-build next))
    ::  +gather-build: looks at a single candidate build
    ::
    ::    This gate inspects a single build. It might move it to :next-builds,
    ::    or promote it using an old build. It also might add this build's
    ::    sub-builds to :candidate-builds.
    ::
    ++  gather-build
      |=  =build
      ^+  ..execute
      ~|  [%duct duct]
      =/  duct-status  (~(got by ducts.state) duct)
      ::  if we already have a result for this build, don't rerun the build
      ::
      =^  current-result  builds.state  (access-build-record build)
      ::
      ?:  ?=([~ %value *] current-result)
        (on-build-complete build)
      ::  place :build in :builds.state if it isn't already there
      ::
      =.  state  (add-build build)
      ::  ignore blocked builds
      ::
      =/  =build-status  (got-build build)
      ?:  ?=(%blocked -.state.build-status)
        =.  state  (add-anchors-to-build-subs build)
        ::
        =/  sub-scrys=(list scry-request)
          ~(tap in (collect-blocked-sub-scrys build))
        ::
        =.  pending-scrys.state
          |-  ^+  pending-scrys.state
          ?~  sub-scrys  pending-scrys.state
          ::
          =.  pending-scrys.state
            (put-request pending-scrys.state i.sub-scrys duct)
          ::
          $(sub-scrys t.sub-scrys)
        ::
        ..execute
      ::  old-build: most recent previous build with :schematic.build
      ::
      =/  old-build=(unit ^build)
        ?:  ?&  ?=(%live -.live.duct-status)
                ?=(^ last-sent.live.duct-status)
            ==
          ::  check whether :build was run as part of the last live build tree
          ::
          ::    If we had build this schematic as part of the build tree
          ::    during the last run of this live build, then we can compare
          ::    our result to that build. It might not be the most recent,
          ::    but if our sub-builds have the same results as they did then,
          ::    we can promote them. This is especially helpful for a %scry
          ::    build, because we don't have to make a new request for the
          ::    resource if the last live build subscribed to it.
          ::
          ::    Otherwise, default to looking up the most recent build of this
          ::    schematic in :builds-by-schematic.state. We'll have to rerun
          ::    any %scry sub-builds, but other than that, we should still be
          ::    able to promote its result if its sub-builds have the same
          ::    results as ours.
          ::
          =/  possible-build=^build
            [date.u.last-sent.live.duct-status schematic.build]
          ?:  (~(has by builds.state) possible-build)
            `possible-build
          (~(find-previous by-schematic builds-by-schematic.state) build)
        (~(find-previous by-schematic builds-by-schematic.state) build)
      ::  if no previous builds exist, we need to run :build
      ::
      ?~  old-build
        (add-build-to-next build)
      ::
      =/  old-build-status=^build-status  (got-build u.old-build)
      ::  selectively promote scry builds
      ::
      ::    We can only promote a scry if it's not forced and we ran the same
      ::    scry schematic as a descendant of the root build schematic at the
      ::    last sent time for this duct.
      ::
      ?:  ?&  ?=(%scry -.schematic.build)
              ?|  force
                  ?!
                  ?&  ?=(%live -.live.duct-status)
                      ?=(^ last-sent.live.duct-status)
                  ::
                      =/  subscription=(unit subscription)
                        subscription.u.last-sent.live.duct-status
                      ::
                      ?~  subscription
                        %.n
                      %-  ~(has in resources.u.subscription)
                      resource.schematic.build
          ==  ==  ==
        (add-build-to-next build)
      ::  if we don't have :u.old-build's result cached, we need to run :build
      ::
      =^  old-build-record  builds.state  (access-build-record u.old-build)
      ?.  ?=([~ %value *] old-build-record)
        (add-build-to-next build)
      ::
      =.  old-build-status  (got-build u.old-build)
      ::
      =/  old-subs=(list ^build)  ~(tap in ~(key by subs.old-build-status))
      =/  new-subs=(list ^build)
        (turn old-subs |=(^build +<(date date.build)))
      ::  link sub-builds provisionally, blocking on incomplete
      ::
      ::    We don't know that :build will end up depending on :new-subs,
      ::    so they're not :verified.
      ::
      =/  split-new-subs
        %+  skid  new-subs
        |=  sub=^build
        ^-  ?
        ::
        ?~  maybe-build-status=(~(get by builds.state) sub)
          %.n
        ::
        ?&  ?=(%complete -.state.u.maybe-build-status)
            ?=(%value -.build-record.state.u.maybe-build-status)
        ==
      ::
      =/  stored-new-subs=(list ^build)     -.split-new-subs
      =/  un-stored-new-subs=(list ^build)  +.split-new-subs
      ::
      =.  builds.state
        (add-subs-to-client build stored-new-subs [verified=%.n blocked=%.n])
      =.  builds.state
        (add-subs-to-client build un-stored-new-subs [verified=%.n blocked=%.y])
      ::
      =.  state  (add-anchors-to-build-subs build)
      ::
      ?^  un-stored-new-subs
        ::  enqueue incomplete sub-builds to be promoted or run
        ::
        ::    When not all our sub builds have results, we can't add :build to
        ::    :next-builds.state. Instead, put all the remaining uncached new
        ::    subs into :candidate-builds.
        ::
        ::    If all of our sub-builds finish immediately (i.e. promoted) when
        ::    they pass through +gather-internal, they will add :build back to
        ::    :candidate-builds and we will run again before +execute runs
        ::    +make.
        ::
        %_    ..execute
            candidate-builds
          (~(gas in candidate-builds) un-stored-new-subs)
        ==
      ::
      =^  promotable  builds.state  (are-subs-unchanged old-subs new-subs)
      ?.  promotable
        (add-build-to-next build)
      ::
      ?>  =(schematic.build schematic.u.old-build)
      ?>  (~(has by builds.state) build)
      (promote-build u.old-build date.build new-subs)
    ::  +are-subs-unchanged: checks sub-build equivalence, updating access time
    ::
    ++  are-subs-unchanged
      |=  [old-subs=(list build) new-subs=(list build)]
      ^-  [? _builds.state]
      ::
      ?~  old-subs
        [%.y builds.state]
      ?>  ?=(^ new-subs)
      ::
      =^  old-build-record  builds.state  (access-build-record i.old-subs)
      ?.  ?=([~ %value *] old-build-record)
        [%.n builds.state]
      ::
      =^  new-build-record  builds.state  (access-build-record i.new-subs)
      ?.  ?=([~ %value *] new-build-record)
        [%.n builds.state]
      ::
      ?.  =(build-result.u.old-build-record build-result.u.new-build-record)
        [%.n builds.state]
      $(new-subs t.new-subs, old-subs t.old-subs)
    ::  +add-build-to-next: run this build during the +make phase
    ::
    ++  add-build-to-next
      |=  =build
      ..execute(next-builds (~(put in next-builds) build))
    ::  +promote-build: promote result of :build to newer :date
    ::
    ::    Also performs relevant accounting, and possibly sends %made moves.
    ::
    ++  promote-build
      |=  [old-build=build new-date=@da new-subs=(list build)]
      ^+  ..execute
      ::  grab the previous result, freshening the cache
      ::
      =^  old-build-record  builds.state  (access-build-record old-build)
      ::  we can only promote a cached result, not missing or a %tombstone
      ::
      ?>  ?=([~ %value *] old-build-record)
      =/  =build-result  build-result.u.old-build-record
      ::  :new-build is :old-build at :date; promotion destination
      ::
      =/  new-build=build  old-build(date new-date)
      ::
      =.  builds.state
        %+  ~(jab by builds.state)  new-build
        |=  =build-status
        ^+  build-status
        ::
        %_    build-status
        ::  verify linkages between :new-build and subs
        ::
            subs
          ::
          ^-  (map build build-relation)
          %-  my
          ^-  (list (pair build build-relation))
          %+  turn  new-subs
          |=  sub=build
          ::
          [sub [verified=& blocked=|]]
        ::  copy the old result to :new-build
        ::
            state
          [%complete [%value last-accessed=now build-result=build-result]]
        ==
      ::
      (on-build-complete new-build)
    --
  ::  +run-builds: run the builds and produce +build-receipts
  ::
  ::    Runs the builds and cleans up the build lists afterwards.
  ::
  ::    When the vere interpreter has a parallel variant of +turn, use
  ::    that as each build might take a while and there are no data
  ::    dependencies between builds here. For now, though, run them serially.
  ::
  ++  run-builds
    =<  $
    ~%  %run-builds  +  ~
    |.
    ^-  [(list build-receipt) _..execute]
    ::
    =/  build-receipts=(list build-receipt)
      (turn ~(tap in next-builds) make)
    ::
    =.  next-builds  ~
    [build-receipts ..execute]
  ::  reduce: apply +build-receipts produce from the +make phase.
  ::
  ::    +gather produces builds to run make on. +make produces
  ::    +build-receipts. It is in +reduce where we take these +build-receipts
  ::    and apply them to ..execute.
  ::
  ++  reduce  !.
    ~/  %reduce
    |=  build-receipts=(list build-receipt)
    ^+  ..execute
    ::  sort :build-receipts so blocks are processed before completions
    ::
    ::    It's possible for a build to block on a sub-build that was run
    ::    in the same batch. If that's the case, make sure we register
    ::    that the build blocked on the sub-build before registering the
    ::    completion of the sub-build. This way, when we do register the
    ::    completion of the sub-build, we will know which builds are blocked
    ::    on the sub-build, so we can enqueue those blocked clients to be
    ::    rerun.
    ::
    =.  build-receipts
      %+  sort  build-receipts
      |=  [a=build-receipt b=build-receipt]
      ^-  ?
      ?=(%blocks -.result.a)
    ::
    |^  ^+  ..execute
        ?~  build-receipts  ..execute
        ::
        =.  ..execute  (apply-build-receipt i.build-receipts)
        $(build-receipts t.build-receipts)
    ::  +apply-build-receipt: applies a single state diff to ..execute
    ::
    ++  apply-build-receipt
      |=  made=build-receipt
      ^+  ..execute
      ::  process :sub-builds.made
      ::
      =.  state  (track-sub-builds build.made sub-builds.made)
      ::
      ?-    -.result.made
          %build-result
        (apply-build-result [build build-result.result cache-access]:made)
      ::
          %blocks
        (apply-blocks [build builds.result]:made)
      ==
    ::  +track-sub-builds:
    ::
    ::    For every sub-build discovered while running :build, we have to make
    ::    sure that we track that sub-build and that it is associated with the
    ::    right ducts.
    ::
    ++  track-sub-builds
      |=  [client=build sub-builds=(list build)]
      ^+  state
      ::  mark :sub-builds as :subs in :build's +build-status
      ::
      =^  build-status  builds.state
        %+  update-build-status  client
        |=  =build-status
        %_    build-status
            subs
          %-  ~(gas by subs.build-status)
          %+  turn  sub-builds
          |=  sub=build
          ::
          =/  blocked=?
            ?~  sub-status=(~(get by builds.state) sub)
              %.y
            !?=([%complete %value *] state.u.sub-status)
          ::
          [sub [verified=& blocked]]
        ==
      ::
      =.  state  (add-anchors-to-build-subs client)
      ::
      |-  ^+  state
      ?~  sub-builds  state
      ::
      =.  builds.state
        %+  ~(jab by builds.state)  i.sub-builds
        |=  build-status=^build-status
        %_    build-status
        ::  freshen :last-accessed date
        ::
            state
          ::
          ?.  ?=([%complete %value *] state.build-status)
            state.build-status
          state.build-status(last-accessed.build-record now)
        ==
      ::
      $(sub-builds t.sub-builds)
    ::  +apply-build-result: apply a %build-result +build-receipt to ..execute
    ::
    ::    Our build produced an actual result.
    ::
    ++  apply-build-result
      |=  [=build =build-result cache-access=(unit [=compiler-cache-key new=?])]
      ^+  ..execute
      ::
      =?  compiler-cache.state  ?=(^ cache-access)
        =+  by-clock=(by-clock compiler-cache-key ^build-result)
        ?.  new.u.cache-access
          =^  ignored  compiler-cache.state
            (~(get by-clock compiler-cache.state) compiler-cache-key.u.cache-access)
          compiler-cache.state
        ::
        %+  ~(put by-clock compiler-cache.state)
          compiler-cache-key.u.cache-access
        build-result
      ::
      =.  builds.state
        %+  ~(jab by builds.state)  build
        |=  =build-status
        build-status(state [%complete [%value last-accessed=now build-result]])
      ::
      (on-build-complete build)
    ::  +apply-blocks: apply a %blocks +build-receipt to ..execute
    ::
    ::    :build blocked. Record information about what builds it blocked on
    ::    and try those blocked builds as candidates in the next pass.
    ::
    ++  apply-blocks
      |=  [=build blocks=(list build)]
      ^+  ..execute
      ::  if a %scry blocked, register it and maybe send an async request
      ::
      =?    ..execute
          ?=(~ blocks)
        ?>  ?=(%scry -.schematic.build)
        =,  resource.schematic.build
        %-  start-scry-request
        [vane care [[ship.disc.rail desk.disc.rail [%da date.build]] spur.rail]]
      ::  we must run +apply-build-receipt on :build.made before :block
      ::
      ?<  %+  lien  blocks
          |=  block=^build
          ?~  maybe-build-status=(~(get by builds.state) block)
            %.n
          ?=(%complete -.state.u.maybe-build-status)
      ::  transition :build's state machine to the %blocked state
      ::
      =.  builds.state
        %+  ~(jab by builds.state)  build
        |=  =build-status
        build-status(state [%blocked ~])
      ::  enqueue :blocks to be run next
      ::
      =.  candidate-builds  (~(gas in candidate-builds) blocks)
      ::
      ..execute
    --
  ::  +make: attempt to perform :build, non-recursively
  ::
  ::    Registers component linkages between :build and its sub-builds.
  ::    Attempts to perform +scry if necessary. Does not directly enqueue
  ::    any moves.
  ::
  ++  make
    ~/  %make
    |=  =build
    ^-  build-receipt
    ::  out: receipt to return to caller
    ::
    =|  out=build-receipt
    ::  ~&  [%turbo-make (build-to-tape build)]
    ::  dispatch based on the kind of +schematic in :build
    ::
    |^  =,  schematic.build
        ::
        =.  build.out  build
        ::
        ?-    -.schematic.build
        ::
            ^  (make-autocons [head tail])
        ::
            %$  (make-literal literal)
        ::
            %pin   (make-pin date schematic)
            %alts  (make-alts choices ~)
            %bake  (make-bake renderer query-string path-to-render)
            %bunt  (make-bunt disc mark)
            %call  (make-call gate sample)
            %cast  (make-cast disc mark input)
            %core  (make-core source-path)
            %diff  (make-diff disc start end)
            %dude  (make-dude error attempt)
            %hood  (make-hood source-path)
            %join  (make-join disc mark first second)
            %list  (make-list schematics)
            %mash  (make-mash disc mark first second)
            %mute  (make-mute subject mutations)
            %pact  (make-pact disc start diff)
            %path  (make-path disc prefix raw-path)
            %plan  (make-plan path-to-render query-string scaffold)
            %reef  (make-reef disc)
            %ride  (make-ride formula subject)
            %same  (make-same schematic)
            %scry  (make-scry resource)
            %slim  (make-slim subject-type formula)
            %slit  (make-slit gate sample)
            %vale  (make-vale disc mark input)
            %volt  (make-volt disc mark input)
            %walk  (make-walk disc source target)
        ==
    ::  |schematic-handlers:make: implementation of the schematics
    ::
    ::    All of these produce a value of the same type as +make itself.
    ::
    ::  +|  schematic-handlers
    ::
    ++  make-autocons
      ~%  %make-autocons  ..^^$  ~
      |=  [head=schematic tail=schematic]
      ^-  build-receipt
      ::
      =/  head-build=^build  [date.build head]
      =/  tail-build=^build  [date.build tail]
      =^  head-result  out  (depend-on head-build)
      =^  tail-result  out  (depend-on tail-build)
      ::
      =|  blocks=(list ^build)
      =?  blocks  ?=(~ head-result)  [head-build blocks]
      =?  blocks  ?=(~ tail-result)  [tail-build blocks]
      ::  if either build blocked, we're not done
      ::
      ?^  blocks
        ::
        (return-blocks blocks)
      ::
      ?<  ?=(~ head-result)
      ?<  ?=(~ tail-result)
      ::
      (return-result %success u.head-result u.tail-result)
    ::
    ++  make-literal
      ~%  %make-literal  ..^^$  ~
      |=  =cage
      ^-  build-receipt
      (return-result %success %$ cage)
    ::
    ++  make-pin
      ~%  %make-pin  ..^^$  ~
      |=  [date=@da =schematic]
      ^-  build-receipt
      ::  pinned-sub: sub-build with the %pin date as formal date
      ::
      =/  pinned-sub=^build  [date schematic]
      ::
      =^  result  out  (depend-on pinned-sub)
      ::
      ?~  result
        (return-blocks ~[pinned-sub])
      ::
      (return-result u.result)
    ::
    ++  make-alts
      ~%  %make-alts  ..^^$  ~
      |=  [choices=(list schematic) errors=(list tank)]
      ^-  build-receipt
      ::
      ?~  choices
        (return-error [[%leaf "%alts: all options failed"] errors])
      ::
      =/  choice=^build  [date.build i.choices]
      ::
      =^  result  out  (depend-on choice)
      ?~  result
        (return-blocks ~[choice])
      ::
      ?:  ?=([%error *] u.result)
        ::
        =/  braces  [[' ' ' ' ~] ['{' ~] ['}' ~]]
        =/  wrapped-error=tank
          [%rose braces `(list tank)`message.u.result]
        =.  errors
          (weld errors `(list tank)`[[%leaf "option"] wrapped-error ~])
        $(choices t.choices)
      ::
      (return-result %success %alts u.result)
    ::
    ++  make-bake
      ~%  %make-bake  ..^^$  ~
      |=  [renderer=term query-string=coin path-to-render=rail]
      ^-  build-receipt
      ::  path-build: find the file path for the renderer source
      ::
      =/  path-build=^build
        [date.build [%path disc.path-to-render %ren renderer]]
      ::
      =^  path-result  out  (depend-on path-build)
      ?~  path-result
        (return-blocks [path-build]~)
      ::
      |^  ^-  build-receipt
          ::  if there's a renderer called :renderer, use it on :path-to-render
          ::
          ::    Otherwise, fall back to running the contents of :path-to-render
          ::    through a mark that has the same name as :renderer.
          ::
          ?:  ?=([~ %success %path *] path-result)
            (try-renderer-then-mark rail.u.path-result)
          (try-mark ~)
      ::  +try-renderer-then-mark: try to render a path, then fall back to mark
      ::
      ++  try-renderer-then-mark
        |=  =rail
        ^-  build-receipt
        ::  build a +scaffold from the renderer source
        ::
        =/  hood-build=^build  [date.build [%hood rail]]
        ::
        =^  hood-result  out  (depend-on hood-build)
        ?~  hood-result
          (return-blocks [hood-build]~)
        ::  if we can't find and parse the renderer, try the mark instead
        ::
        ?:  ?=([~ %error *] hood-result)
          (try-mark message.u.hood-result)
        ?>  ?=([~ %success %hood *] hood-result)
        ::  link the renderer, passing through :path-to-render and :query-string
        ::
        =/  plan-build=^build
          :-  date.build
          [%plan path-to-render query-string scaffold.u.hood-result]
        ::
        =^  plan-result  out  (depend-on plan-build)
        ?~  plan-result
          (return-blocks [plan-build]~)
        ::  if compiling the renderer errors out, try the mark instead
        ::
        ?:  ?=([~ %error *] plan-result)
          (try-mark message.u.plan-result)
        ?>  ?=([~ %success %plan *] plan-result)
        ::  renderers return their name as the mark
        ::
        ::    We should rethink whether we want this to be the case going
        ::    forward, but for now, Eyre depends on this detail to work.
        ::
        (return-result [%success %bake renderer vase.u.plan-result])
      ::   +try-mark: try to cast a file's contents through a mark
      ::
      ::     :errors contains any error messages from our previous attempt to
      ::     run a renderer, if we made one. This way if both the renderer and
      ::     mark fail, the requester will see the errors of both attempts.
      ::
      ++  try-mark
        |=  errors=(list tank)
        ^-  build-receipt
        ::  no renderer, try mark; retrieve directory listing of :path-to-render
        ::
        ::    There might be multiple files of different marks stored at
        ::    :path-to-render. Retrieve the directory listing for
        ::    :path-to-render, then check which of the path segments in
        ::    that directory are files (not just folders), then for each
        ::    file try to %cast its mark to the desired mark (:renderer).
        ::
        ::    Start by retrieving the directory listing, using :toplevel-build.
        ::
        =/  toplevel-build=^build
          [date.build [%scry %c %y path-to-render]]
        ::
        =^  toplevel-result  out  (depend-on toplevel-build)
        ?~  toplevel-result
          (return-blocks [toplevel-build]~)
        ::
        ?:  ?=([~ %error *] toplevel-result)
          ::
          =/  =path  (rail-to-path path-to-render)
          ?~  errors
            %-  return-error
            :-  [%leaf "ford: %bake {<renderer>} on {<path>} failed:"]
            message.u.toplevel-result
          ::
          =/  braces  [[' ' ' ' ~] ['{' ~] ['}' ~]]
          %-  return-error  :~
            [%leaf "ford: %bake {<renderer>} on {<path>} failed:"]
            [%leaf "as-renderer"]
            [%rose braces errors]
            [%leaf "as-mark"]
            [%rose braces message.u.toplevel-result]
          ==
        ?>  ?=([~ %success %scry *] toplevel-result)
        ::
        =/  toplevel-arch=arch  ;;(arch q.q.cage.u.toplevel-result)
        ::  find the :sub-path-segments that could be files
        ::
        ::    Filter out path segments that aren't a +term,
        ::    since those aren't valid marks and therefore can't
        ::    be the last segment of a filepath in Clay.
        ::
        =/  sub-path-segments=(list @ta)
          (skim (turn ~(tap by dir.toplevel-arch) head) (sane %tas))
        ::
        =/  sub-schematics=(list [sub-path=@ta =schematic])
          %+  turn  sub-path-segments
          |=  sub=@ta
          :-  sub
          [%scry %c %y path-to-render(spur [sub spur.path-to-render])]
        ::
        =^  maybe-schematic-results  out
          %-  perform-schematics  :*
            ;:  weld
              "ford: %bake "  (trip renderer)  " on "
              (spud (rail-to-path path-to-render))  " contained failures:"
            ==
            sub-schematics
            %fail-on-errors
            *@ta
          ==
        ?~  maybe-schematic-results
          out
        ::  marks: list of the marks of the files at :path-to-render
        ::
        =/  marks=(list @tas)
          %+  murn  u.maybe-schematic-results
          |=  [sub-path=@ta result=build-result]
          ^-  (unit @tas)
          ::
          ?>  ?=([%success %scry *] result)
          ::
          =/  =arch  ;;(arch q.q.cage.result)
          ::  if it's a directory, not a file, we can't load it
          ::
          ?~  fil.arch
            ~
          [~ `@tas`sub-path]
        ::  sort marks in alphabetical order
        ::
        =.  marks  (sort marks lte)
        ::  try to convert files to the destination mark, in order
        ::
        =/  alts-build=^build
          ::
          :+  date.build  %alts
          ^=  choices  ^-  (list schematic)
          ::
          %+  turn  marks
          |=  mark=term
          ^-  schematic
          ::
          =/  file=rail  path-to-render(spur [mark spur.path-to-render])
          ::
          [%cast disc.file renderer [%scry %c %x file]]
        ::
        =^  alts-result  out  (depend-on alts-build)
        ?~  alts-result
          (return-blocks [alts-build]~)
        ::
        ?:  ?=([~ %error *] alts-result)
          =/  =path  (rail-to-path path-to-render)
          ?~  errors
            %-  return-error
            :-  [%leaf "ford: %bake {<renderer>} on {<path>} failed:"]
            message.u.alts-result
          ::
          =/  braces  [[' ' ' ' ~] ['{' ~] ['}' ~]]
          %-  return-error  :~
            [%leaf "ford: %bake {<renderer>} on {<path>} failed:"]
            [%leaf "as-renderer"]
            [%rose braces errors]
            [%leaf "as-mark"]
            [%rose braces message.u.alts-result]
          ==
        ::
        ?>  ?=([~ %success %alts *] alts-result)
        ::
        =/  =build-result
          [%success %bake (result-to-cage u.alts-result)]
        ::
        (return-result build-result)
      --
    ::
    ++  make-bunt
      ~%  %make-bunt  ..^^$  ~
      |=  [=disc mark=term]
      ^-  build-receipt
      ::  resolve path of the mark definition file
      ::
      =/  path-build=^build  [date.build [%path disc %mar mark]]
      ::
      =^  path-result  out  (depend-on path-build)
      ?~  path-result
        (return-blocks [path-build]~)
      ::
      ?:  ?=([~ %error *] path-result)
        %-  return-error
        :_  message.u.path-result
        :-  %leaf
        "ford: %bunt resolving path for {<mark>} on {<disc>} failed:"
      ::
      ?>  ?=([~ %success %path *] path-result)
      ::  build the mark core from source
      ::
      =/  core-build=^build  [date.build [%core rail.u.path-result]]
      ::
      =^  core-result  out  (depend-on core-build)
      ?~  core-result
        (return-blocks [core-build]~)
      ::
      ?:  ?=([~ %error *] core-result)
        %-  return-error
        :_  message.u.core-result
        :-  %leaf
        "ford: %bunt compiling mark {<mark>} on {<disc>} failed:"
      ::
      ?>  ?=([~ %success %core *] core-result)
      ::  extract the sample from the mark core
      ::
      =/  mark-vase=vase    vase.u.core-result
      ~|  %mark-vase
      =+  [sample-type=p sample-value=q]:(slot 6 mark-vase)
      ::  if sample is wrapped in a face, unwrap it
      ::
      =?  sample-type  ?=(%face -.sample-type)  q.sample-type
      ::
      =/  =cage  [mark sample-type sample-value]
      (return-result %success %bunt cage)
    ::
    ++  make-call
      ~%  %make-call  ..^^$  ~
      |=  [gate=schematic sample=schematic]
      ^-  build-receipt
      ::
      =/  gate-build=^build  [date.build gate]
      =^  gate-result    out  (depend-on gate-build)
      ::
      =/  sample-build=^build  [date.build sample]
      =^  sample-result  out  (depend-on sample-build)
      ::
      =|  blocks=(list ^build)
      =?  blocks  ?=(~ gate-result)    [[date.build gate] blocks]
      =?  blocks  ?=(~ sample-result)  [[date.build sample] blocks]
      ?^  blocks
        (return-blocks blocks)
      ::
      ?<  ?=(~ gate-result)
      ?:  ?=([~ %error *] gate-result)
        %-  return-error
        :-  [%leaf "ford: %call failed to build gate:"]
        message.u.gate-result
      ::
      ?<  ?=(~ sample-result)
      ?:  ?=([~ %error *] sample-result)
        %-  return-error
        :-  [%leaf "ford: %call failed to build sample:"]
        message.u.sample-result
      ::
      =/  gate-vase=vase    q:(result-to-cage u.gate-result)
      =/  sample-vase=vase  q:(result-to-cage u.sample-result)
      ::  run %slit to get the resulting type of calculating the gate
      ::
      =/  slit-schematic=schematic  [%slit gate-vase sample-vase]
      =/  slit-build=^build  [date.build slit-schematic]
      =^  slit-result  out  (depend-on slit-build)
      ?~  slit-result
        (return-blocks [date.build slit-schematic]~)
      ::
      ?:  ?=([~ %error *] slit-result)
        %-  return-error
        :-  [%leaf "ford: %call failed type calculation"]
        message.u.slit-result
      ::
      ?>  ?=([~ %success %slit *] slit-result)
      ::
      =/  =compiler-cache-key  [%call gate-vase sample-vase]
      =^  cached-result  out  (access-cache compiler-cache-key)
      ?^  cached-result
        (return-result u.cached-result)
      ::
      ?>  &(?=(^ q.gate-vase) ?=(^ +.q.gate-vase))
      =/  val
        (mong [q.gate-vase q.sample-vase] intercepted-scry)
      ::
      ?-    -.val
          %0
        (return-result %success %call [type.u.slit-result p.val])
      ::
          %1
        =/  blocked-paths=(list path)  ;;((list path) p.val)
        (blocked-paths-to-receipt %call blocked-paths)
      ::
          %2
        (return-error [[%leaf "ford: %call execution failed:"] p.val])
      ==
    ::
    ++  make-cast
      ~%  %make-cast  ..^^$  ~
      |=  [=disc mark=term input=schematic]
      ^-  build-receipt
      ::
      =/  input-build=^build  [date.build input]
      ::
      =^  input-result  out  (depend-on input-build)
      ?~  input-result
        (return-blocks [input-build]~)
      ::
      ?:  ?=([~ %error *] input-result)
        %-  return-error
        :_  message.u.input-result
        :-  %leaf
        ;:  weld
          "ford: %cast "  (trip mark)  " on ["  (trip (scot %p ship.disc))
          " "  (trip desk.disc)  "] failed on input:"
        ==
      ::
      ?>  ?=([~ %success *] input-result)
      ::
      =/  result-cage=cage  (result-to-cage u.input-result)
      ::
      =/  translation-path-build=^build
        [date.build [%walk disc p.result-cage mark]]
      =^  translation-path-result  out
        (depend-on translation-path-build)
      ::
      ?~  translation-path-result
        (return-blocks [translation-path-build]~)
      ::
      ?:  ?=([~ %error *] translation-path-result)
        %-  return-error
        :_  message.u.translation-path-result
        :-  %leaf
        ;:  weld
          "ford: %cast "  (trip mark)  " on ["  (trip (scot %p ship.disc))
          " "  (trip desk.disc)  "] failed:"
        ==
      ::
      ?>  ?=([~ %success %walk *] translation-path-result)
      ::
      =/  translation-path=(list mark-action)
        results.u.translation-path-result
      ::
      |^  ^-  build-receipt
          ?~  translation-path
            (return-result %success %cast result-cage)
          ::
          =^  action-result  out
            =,  i.translation-path
            ?-  -.i.translation-path
              %grow  (run-grow source target result-cage)
              %grab  (run-grab source target result-cage)
            ==
          ::
          ?-    -.action-result
              %success
            %_  $
              translation-path  t.translation-path
              result-cage  cage.action-result
            ==
          ::
              %blocks
            (return-blocks blocks.action-result)
          ::
              %error
            (return-error [leaf+"ford: failed to %cast" tang.action-result])
        ==
      ::
      +=  action-result
        $%  ::  translation was successful and here's a cage for you
            [%success =cage]
            ::  it was an error. sorry.
            [%error =tang]
            ::  we block on a build
            [%blocks blocks=(list ^build)]
        ==
      ::
      ++  run-grab
        |=  [source-mark=term target-mark=term input-cage=cage]
        ^-  [action-result _out]
        ::
        =/  mark-path-build=^build
          [date.build [%path disc %mar target-mark]]
        ::
        =^  mark-path-result  out
          (depend-on mark-path-build)
        ?~  mark-path-result
          [[%blocks [mark-path-build]~] out]
        ::
        ?.  ?=([~ %success %path *] mark-path-result)
          %-  cast-wrap-error  :*
            source-mark
            target-mark
            ;:  weld
              "ford: %cast failed to find path for mark "  (trip source-mark)
              " during +grab:"
            ==
            mark-path-result
          ==
        ::
        =/  mark-core-build=^build  [date.build [%core rail.u.mark-path-result]]
        ::
        =^  mark-core-result  out  (depend-on mark-core-build)
        ?~  mark-core-result
          [[%blocks ~[mark-core-build]] out]
        ::  find +grab within the destination mark core
        ::
        =/  grab-build=^build
          :-  date.build
          [%ride [%limb %grab] [%$ (result-to-cage u.mark-core-result)]]
        ::
        =^  grab-result  out  (depend-on grab-build)
        ?~  grab-result
          [[%blocks [grab-build]~] out]
        ::
        ?.  ?=([~ %success %ride *] grab-result)
          =/  =path  (rail-to-path rail.u.mark-path-result)
          %-  cast-wrap-error  :*
            source-mark
            target-mark
            :(weld "ford: %cast failed to ride " (spud path) " during +grab:")
            grab-result
          ==
        ::  find an arm for the input's mark within the +grab core
        ::
        =/  grab-mark-build=^build
          :-  date.build
          [%ride [%limb source-mark] [%$ %noun vase.u.grab-result]]
        ::
        =^  grab-mark-result  out  (depend-on grab-mark-build)
        ?~  grab-mark-result
          [[%blocks [grab-mark-build]~] out]
        ::
        ?.  ?=([~ %success %ride *] grab-mark-result)
          =/  =path  (rail-to-path rail.u.mark-path-result)
          %-  cast-wrap-error  :*
            source-mark
            target-mark
            :(weld "ford: %cast failed to ride " (spud path) " during +grab:")
            grab-mark-result
          ==
        ::  slam the +mark-name:grab gate on the result of running :input
        ::
        =/  call-build=^build
          :-  date.build
          [%call gate=[%$ %noun vase.u.grab-mark-result] sample=[%$ input-cage]]
        ::
        =^  call-result  out  (depend-on call-build)
        ?~  call-result
          [[%blocks [call-build]~] out]
        ::
        ?.  ?=([~ %success %call *] call-result)
          =/  =path  (rail-to-path rail.u.mark-path-result)
          %-  cast-wrap-error  :*
            source-mark
            target-mark
            :(weld "ford: %cast failed to call +grab arm in " (spud path) ":")
            call-result
          ==
        ::
        [[%success [mark vase.u.call-result]] out]
      ::  +grow: grow from the input mark to the destination mark
      ::
      ++  run-grow
        |=  [source-mark=term target-mark=term input-cage=cage]
        ^-  [action-result _out]
        ::
        =/  starting-mark-path-build=^build
          [date.build [%path disc %mar source-mark]]
        ::
        =^  starting-mark-path-result  out
          (depend-on starting-mark-path-build)
        ?~  starting-mark-path-result
          [[%blocks [starting-mark-path-build]~] out]
        ::
        ?.  ?=([~ %success %path *] starting-mark-path-result)
          %-  cast-wrap-error  :*
            source-mark
            target-mark
            ;:  weld
              "ford: %cast failed to find path for mark "  (trip source-mark)
              " during +grow:"
            ==
            starting-mark-path-result
          ==
        ::  grow the value from the initial mark to the final mark
        ::
        ::  Replace the input mark's sample with the input's result,
        ::  then fire the mark-name:grow arm to produce a result.
        ::
        =/  grow-build=^build
          :-  date.build
          :+  %ride
            formula=`hoon`[%tsld [%wing ~[target-mark]] [%wing ~[%grow]]]
          ^=  subject
          ^-  schematic
          :*  %mute
              ^-  schematic
              [%core rail.u.starting-mark-path-result]
              ^=  mutations
              ^-  (list [wing schematic])
              [[%& 6]~ [%$ input-cage]]~
          ==
        ::
        =^  grow-result  out  (depend-on grow-build)
        ?~  grow-result
          [[%blocks [grow-build]~] out]
        ::
        ?.  ?=([~ %success %ride *] grow-result)
          =/  =path  (rail-to-path rail.u.starting-mark-path-result)
          %-  cast-wrap-error  :*
            source-mark
            target-mark
            :(weld "ford: %cast failed to ride " (spud path) " during +grow:")
            grow-result
          ==
        ::  make sure the product nests in the sample of the destination mark
        ::
        =/  bunt-build=^build  [date.build [%bunt disc target-mark]]
        ::
        =^  bunt-result  out  (depend-on bunt-build)
        ?~  bunt-result
          [[%blocks [bunt-build]~] out]
        ::
        ?.  ?=([~ %success %bunt *] bunt-result)
          %-  cast-wrap-error  :*
            source-mark
            target-mark
            :(weld "ford: %cast failed to bunt " (trip target-mark) ":")
            bunt-result
          ==
        ::
        ?.  (~(nest ut p.q.cage.u.bunt-result) | p.vase.u.grow-result)
          =*  src  source-mark
          =*  dst  target-mark
          :_  out
          :-  %error
          :_  ~
          :-  %leaf
          ;:  weld
            "ford: %cast from "  (trip src)  " to "  (trip dst)
            " failed: nest fail"
          ==
        ::
        [[%success mark vase.u.grow-result] out]
      ::
      ++  cast-wrap-error
        |=  $:  source-mark=term
                target-mark=term
                description=tape
                result=(unit build-result)
            ==
        ^-  [action-result _out]
        ::
        ?>  ?=([~ %error *] result)
        ::
        :_  out
        :-  %error
        :*  :-  %leaf
            ;:  weld
              "ford: %cast failed while trying to cast from "
              (trip source-mark)  " to "  (trip target-mark)  ":"
            ==
            [%leaf description]
            message.u.result
        ==
      --
    ::
    ++  make-core
      ~%  %make-core  ..^^$  ~
      |=  source-path=rail
      ^-  build-receipt
      ::  convert file at :source-path to a +scaffold
      ::
      =/  hood-build=^build  [date.build [%hood source-path]]
      ::
      =^  hood-result  out  (depend-on hood-build)
      ?~  hood-result
        (return-blocks [hood-build]~)
      ::
      ?:  ?=(%error -.u.hood-result)
        %-  return-error
        :-  [%leaf "ford: %core on {<(rail-to-path source-path)>} failed:"]
        message.u.hood-result
      ::  build the +scaffold into a program
      ::
      ?>  ?=([%success %hood *] u.hood-result)
      ::
      =/  plan-build=^build
        [date.build [%plan source-path `coin`[%many ~] scaffold.u.hood-result]]
      ::
      =^  plan-result  out  (depend-on plan-build)
      ?~  plan-result
        (return-blocks [plan-build]~)
      ::
      ?:  ?=(%error -.u.plan-result)
        %-  return-error
        :-  [%leaf "ford: %core on {<(rail-to-path source-path)>} failed:"]
        message.u.plan-result
      ::
      ?>  ?=([%success %plan *] u.plan-result)
      (return-result %success %core vase.u.plan-result)
    ::
    ++  make-diff
      ~%  %make-diff  ..^^$  ~
      |=  [=disc start=schematic end=schematic]
      ^-  build-receipt
      ::  run both input schematics as an autocons build
      ::
      =/  sub-build=^build  [date.build [start end]]
      ::
      =^  sub-result  out  (depend-on sub-build)
      ?~  sub-result
        (return-blocks [sub-build]~)
      ::
      ?.  ?=([~ %success ^ ^] sub-result)
        (wrap-error sub-result)
      ?.  ?=([%success *] head.u.sub-result)
        (wrap-error `head.u.sub-result)
      ?.  ?=([%success *] tail.u.sub-result)
        (wrap-error `tail.u.sub-result)
      ::
      =/  start-cage=cage  (result-to-cage head.u.sub-result)
      =/  end-cage=cage    (result-to-cage tail.u.sub-result)
      ::  if the marks aren't the same, we can't diff them
      ::
      ?.  =(p.start-cage p.end-cage)
        %-  return-error  :_  ~  :-  %leaf
        "ford: %diff failed: mark mismatch: %{<p.start-cage>} / %{<p.end-cage>}"
      ::  if the values are the same, the diff is null
      ::
      ?:  =(q.q.start-cage q.q.end-cage)
        =/  =build-result
          [%success %diff [%null [%atom %n ~] ~]]
        ::
        (return-result build-result)
      ::
      =/  mark-path-build=^build  [date.build [%path disc %mar p.start-cage]]
      ::
      =^  mark-path-result  out  (depend-on mark-path-build)
      ?~  mark-path-result
        (return-blocks [mark-path-build]~)
      ::
      ?:  ?=([~ %error *] mark-path-result)
        %-  return-error
        :-  [%leaf "ford: %diff failed on {<disc>}:"]
        message.u.mark-path-result
      ::
      ?>  ?=([~ %success %path *] mark-path-result)
      ::
      =/  mark-build=^build  [date.build [%core rail.u.mark-path-result]]
      ::
      =^  mark-result  out  (depend-on mark-build)
      ?~  mark-result
        (return-blocks [mark-build]~)
      ::
      ?:  ?=([~ %error *] mark-result)
        %-  return-error
        :-  [%leaf "ford: %diff failed on {<disc>}:"]
        message.u.mark-result
      ::
      ?>  ?=([~ %success %core *] mark-result)
      ::
      ?.  (slab %grad p.vase.u.mark-result)
        %-  return-error  :_  ~  :-  %leaf
        "ford: %diff failed: %{<p.start-cage>} mark has no +grad arm"
      ::
      =/  grad-build=^build
        [date.build [%ride [%limb %grad] [%$ %noun vase.u.mark-result]]]
      ::
      =^  grad-result  out  (depend-on grad-build)
      ?~  grad-result
        (return-blocks [grad-build]~)
      ::
      ?:  ?=([~ %error *] grad-result)
        %-  return-error
        :-  [%leaf "ford: %diff failed on {<disc>}:"]
        message.u.grad-result
      ::
      ?>  ?=([~ %success %ride *] grad-result)
      ::  if +grad produced a @tas, convert to that mark and diff those
      ::
      ?@  q.vase.u.grad-result
        =/  mark=(unit @tas)  ((sand %tas) q.vase.u.grad-result)
        ?~  mark
          %-  return-error  :_  ~  :-  %leaf
          "ford: %diff failed: %{<p.start-cage>} mark has invalid +grad arm"
        ::
        =/  diff-build=^build
          :-  date.build
          :^    %diff
              disc
            [%cast disc u.mark [%$ start-cage]]
          [%cast disc u.mark [%$ end-cage]]
        ::
        =^  diff-result  out  (depend-on diff-build)
        ?~  diff-result
          (return-blocks [diff-build]~)
        ::
        ?.  ?=([~ %success %diff *] diff-result)
          (wrap-error diff-result)
        ::
        =/  =build-result
          [%success %diff cage.u.diff-result]
        ::
        (return-result build-result)
      ::  +grad produced a cell, which should be a core with a +form arm
      ::
      ?.  (slab %form p.vase.u.grad-result)
        %-  return-error  :_  ~  :-  %leaf
        "ford: %diff failed: %{<p.start-cage>} mark has no +form:grab arm"
      ::  the +grab core should also contain a +diff arm
      ::
      ?.  (slab %diff p.vase.u.grad-result)
        %-  return-error  :_  ~  :-  %leaf
        "ford: %diff failed: %{<p.start-cage>} mark has no +diff:grab arm"
      ::
      =/  diff-build=^build
        :-  date.build
        :+  %call
          ::
          ^=  gate
          :+  %ride
            ::
            formula=`hoon`[%tsld [%wing ~[%diff]] [%wing ~[%grad]]]
          ::
          ^=  subject
          :+  %mute
            ::
            subject=`schematic`[%$ %noun vase.u.mark-result]
          ::
          ^=  mutations
          ^-  (list [wing schematic])
          [[%& 6]~ [%$ start-cage]]~
        ::
        sample=`schematic`[%$ end-cage]
      ::
      =^  diff-result  out  (depend-on diff-build)
      ?~  diff-result
        (return-blocks [diff-build]~)
      ::
      ?.  ?=([~ %success %call *] diff-result)
        (wrap-error diff-result)
      ::
      =/  form-build=^build
        [date.build [%ride [%limb %form] [%$ %noun vase.u.grad-result]]]
      ::
      =^  form-result  out  (depend-on form-build)
      ?~  form-result
        (return-blocks [form-build]~)
      ::
      ?.  ?=([~ %success %ride *] form-result)
        (wrap-error form-result)
      ::
      =/  mark=(unit @tas)  ((soft @tas) q.vase.u.form-result)
      ?~  mark
        %-  return-error  :_  ~  :-  %leaf
        "ford: %diff failed: invalid +form result: {(text vase.u.form-result)}"
      ::
      =/  =build-result
        [%success %diff [u.mark vase.u.diff-result]]
      ::
      (return-result build-result)
    ::
    ++  make-dude
      ~%  %make-dude  ..^^$  ~
      |=  [error=tank attempt=schematic]
      ^-  build-receipt
      ::
      =/  attempt-build=^build  [date.build attempt]
      =^  attempt-result  out  (depend-on attempt-build)
      ?~  attempt-result
        ::
        (return-blocks ~[[date.build attempt]])
      ::
      ?.  ?=([%error *] u.attempt-result)
        (return-result u.attempt-result)
      ::
      (return-error [error message.u.attempt-result])
    ::
    ++  make-hood
      ~%  %make-hood  ..^^$  ~
      |=  source-rail=rail
      ^-  build-receipt
      ::
      =/  scry-build=^build  [date.build [%scry [%c %x source-rail]]]
      =^  scry-result  out  (depend-on scry-build)
      ?~  scry-result
        ::
        (return-blocks ~[scry-build])
      ::
      ?:  ?=([~ %error *] scry-result)
        =/  =path  (rail-to-path source-rail)
        %-  return-error
        :-  [%leaf "ford: %hood failed for {<path>}:"]
        message.u.scry-result
      =+  as-cage=(result-to-cage u.scry-result)
      ::  hoon files must be atoms to parse
      ::
      ?.  ?=(@ q.q.as-cage)
        =/  =path  (rail-to-path source-rail)
        %-  return-error
        :_  ~
        :-  %leaf
        "ford: %hood: path {<path>} not an atom"
      ::
      =/  src-beam=beam  [[ship.disc desk.disc [%ud 0]] spur]:source-rail
      ::
      =/  =compiler-cache-key  [%hood src-beam q.q.as-cage]
      =^  cached-result  out  (access-cache compiler-cache-key)
      ?^  cached-result
        (return-result u.cached-result)
      ::
      =/  parsed
        ((full (parse-scaffold src-beam)) [1 1] (trip q.q.as-cage))
      ::
      ?~  q.parsed
        =/  =path  (rail-to-path source-rail)
        %-  return-error
        :-  :-  %leaf
            %+  weld  "ford: %hood: syntax error at "
            "[{<p.p.parsed>} {<q.p.parsed>}] in {<path>}"
        ~
      ::
      (return-result %success %hood p.u.q.parsed)
    ::
    ++  make-join
      ~%  %make-join  ..^^$  ~
      |=  [disc=disc mark=term first=schematic second=schematic]
      ^-  build-receipt
      ::
      =/  initial-build=^build
        [date.build [first second] [%path disc %mar mark]]
      ::
      =^  initial-result  out  (depend-on initial-build)
      ?~  initial-result
        (return-blocks [initial-build]~)
      ::
      ?.  ?=([~ %success [%success ^ ^] %success %path *] initial-result)
        (wrap-error initial-result)
      ?.  ?=([%success *] head.head.u.initial-result)
        (wrap-error `head.head.u.initial-result)
      ?.  ?=([%success *] tail.head.u.initial-result)
        (wrap-error `tail.head.u.initial-result)
      ::
      =/  first-cage=cage   (result-to-cage head.head.u.initial-result)
      =/  second-cage=cage  (result-to-cage tail.head.u.initial-result)
      =/  mark-path=rail    rail.tail.u.initial-result
      ::  TODO: duplicate logic with +make-pact and others
      ::
      =/  mark-build=^build  [date.build [%core mark-path]]
      ::
      =^  mark-result  out  (depend-on mark-build)
      ?~  mark-result
        (return-blocks [mark-build]~)
      ::
      ?:  ?=([~ %error *] mark-result)
        %-  return-error
        :-  [%leaf "ford: %join to {<mark>} on {<disc>} failed:"]
        message.u.mark-result
      ::
      ?>  ?=([~ %success %core *] mark-result)
      ::
      =/  mark-vase=vase  vase.u.mark-result
      ::
      ?.  (slab %grad p.mark-vase)
        %-  return-error  :_  ~  :-  %leaf
        "ford: %join failed: %{<mark>} mark has no +grad arm"
      ::
      =/  grad-build=^build
        [date.build [%ride [%limb %grad] [%$ %noun mark-vase]]]
      ::
      =^  grad-result  out  (depend-on grad-build)
      ?~  grad-result
        (return-blocks [grad-build]~)
      ::
      ?:  ?=([~ %error *] grad-result)
        %-  return-error
        :-  [%leaf "ford: %join to {<mark>} on {<disc>} failed:"]
        message.u.grad-result
      ::
      ?>  ?=([~ %success %ride *] grad-result)
      ::
      =/  grad-vase=vase  vase.u.grad-result
      ::  if +grad produced a mark, delegate %join behavior to that mark
      ::
      ?@  q.grad-vase
        ::  if +grad produced a term, make sure it's a valid mark
        ::
        =/  grad-mark=(unit term)  ((sand %tas) q.grad-vase)
        ?~  grad-mark
          %-  return-error  :_  ~  :-  %leaf
          "ford: %join failed: %{<mark>} mark invalid +grad"
        ::  todo: doesn't catch full cycles of +grad arms, only simple cases
        ::
        ?:  =(u.grad-mark mark)
          %-  return-error  :_  ~  :-  %leaf
          "ford: %join failed: %{<mark>} mark +grad arm refers to self"
        ::
        =/  join-build=^build
          [date.build [%join disc u.grad-mark [%$ first-cage] [%$ second-cage]]]
        ::
        =^  join-result  out  (depend-on join-build)
        ?~  join-result
          (return-blocks [join-build]~)
        ::
        ?:  ?=([~ %error *] join-result)
          %-  return-error
          :-  [%leaf "ford: %join to {<mark>} on {<disc>} failed:"]
          message.u.join-result
        ::
        ?>  ?=([~ %success %join *] join-result)
        ::
        (return-result u.join-result)
      ::  make sure the +grad core has a +form arm
      ::
      ?.  (slab %form p.grad-vase)
        %-  return-error  :_  ~  :-  %leaf
        "ford: %join failed: no +form:grad in %{<mark>} mark"
      ::  make sure the +grad core has a +join arm
      ::
      ?.  (slab %join p.grad-vase)
        %-  return-error  :_  ~  :-  %leaf
        "ford: %join failed: no +join:grad in %{<mark>} mark"
      ::  fire the +form:grad arm, which should produce a mark
      ::
      =/  form-build=^build
        [date.build [%ride [%limb %form] [%$ %noun grad-vase]]]
      ::
      =^  form-result  out  (depend-on form-build)
      ?~  form-result
        (return-blocks [form-build]~)
      ::
      ?.  ?=([~ %success %ride *] form-result)
        (wrap-error form-result)
      ::
      =/  form-mark=(unit term)  ((soft @tas) q.vase.u.form-result)
      ?~  form-mark
        %-  return-error  :_  ~  :-  %leaf
        "ford: %join failed: %{<mark>} mark invalid +form:grad"
      ::  the mark produced by +form:grad should match both diffs
      ::
      ?.  &(=(u.form-mark p.first-cage) =(u.form-mark p.second-cage))
        %-  return-error  :_  ~  :-  %leaf
        "ford: %join failed: mark mismatch"
      ::  if the diffs are identical, just produce the first
      ::
      ?:  =(q.q.first-cage q.q.second-cage)
        (return-result %success %join first-cage)
      ::  call the +join:grad gate on the two diffs
      ::
      =/  diff-build=^build
        :-  date.build
        :+  %call
          :+  %ride
            [%limb %join]
          [%$ %noun grad-vase]
        [%$ %noun (slop q.first-cage q.second-cage)]
      ::
      =^  diff-result  out  (depend-on diff-build)
      ?~  diff-result
        (return-blocks [diff-build]~)
      ::
      ?:  ?=([~ %error *] diff-result)
        %-  return-error
        :-  [%leaf "ford: %join to {<mark>} on {<disc>} failed:"]
        message.u.diff-result
      ::
      ?>  ?=([~ %success %call *] diff-result)
      ::  the result was a unit; if `~`, use %null mark; otherwise grab tail
      ::
      =/  =build-result
        :+  %success  %join
        ?@  q.vase.u.diff-result
          [%null vase.u.diff-result]
        [u.form-mark (slot 3 vase.u.diff-result)]
      ::
      (return-result build-result)
    ::
    ++  make-list
      ~%  %make-list  ..^^$  ~
      |=  schematics=(list schematic)
      ^-  build-receipt
      ::
      =/  key-and-schematics
        (turn schematics |=(=schematic [~ schematic]))
      ::  depend on builds of each schematic
      ::
      =^  maybe-schematic-results  out
        (perform-schematics "" key-and-schematics %ignore-errors *~)
      ?~  maybe-schematic-results
        out
      ::  return all builds
      ::
      =/  =build-result
        :+  %success  %list
        ::  the roll above implicitly flopped the results
        ::
        (flop (turn u.maybe-schematic-results tail))
      (return-result build-result)
    ::
    ++  make-mash
      ~%  %make-mash  ..^^$  ~
      |=  $:  disc=disc
              mark=term
              first=[disc=disc mark=term =schematic]
              second=[disc=disc mark=term =schematic]
          ==
      ^-  build-receipt
      ::
      =/  initial-build=^build
        [date.build [schematic.first schematic.second] [%path disc %mar mark]]
      ::
      =^  initial-result  out  (depend-on initial-build)
      ?~  initial-result
        (return-blocks [initial-build]~)
      ::  TODO: duplicate logic with +make-join
      ::
      ?.  ?=([~ %success [%success ^ ^] %success %path *] initial-result)
        (wrap-error initial-result)
      ?.  ?=([%success *] head.head.u.initial-result)
        (wrap-error `head.head.u.initial-result)
      ?.  ?=([%success *] tail.head.u.initial-result)
        (wrap-error `tail.head.u.initial-result)
      ::
      =/  first-cage=cage   (result-to-cage head.head.u.initial-result)
      =/  second-cage=cage  (result-to-cage tail.head.u.initial-result)
      =/  mark-path=rail    rail.tail.u.initial-result
      ::  TODO: duplicate logic with +make-pact and others
      ::
      =/  mark-build=^build  [date.build [%core mark-path]]
      ::
      =^  mark-result  out  (depend-on mark-build)
      ?~  mark-result
        (return-blocks [mark-build]~)
      ::
      ?.  ?=([~ %success %core *] mark-result)
        (wrap-error mark-result)
      ::
      =/  mark-vase=vase  vase.u.mark-result
      ::
      ?.  (slab %grad p.mark-vase)
        %-  return-error  :_  ~  :-  %leaf
        "ford: %mash failed: %{<mark>} mark has no +grad arm"
      ::
      =/  grad-build=^build
        [date.build [%ride [%limb %grad] [%$ %noun mark-vase]]]
      ::
      =^  grad-result  out  (depend-on grad-build)
      ?~  grad-result
        (return-blocks [grad-build]~)
      ::
      ?.  ?=([~ %success %ride *] grad-result)
        (wrap-error grad-result)
      ::
      =/  grad-vase=vase  vase.u.grad-result
      ::  if +grad produced a mark, delegate %mash behavior to that mark
      ::
      ?@  q.grad-vase
        ::  if +grad produced a term, make sure it's a valid mark
        ::
        =/  grad-mark=(unit term)  ((sand %tas) q.grad-vase)
        ?~  grad-mark
          %-  return-error  :_  ~  :-  %leaf
          "ford: %mash failed: %{<mark>} mark invalid +grad"
        ::
        =/  mash-build=^build
          :-  date.build
          :-  %mash
          :^  disc  u.grad-mark
            [disc.first mark.first [%$ first-cage]]
          [disc.second mark.second [%$ second-cage]]
        ::
        =^  mash-result  out  (depend-on mash-build)
        ?~  mash-result
          (return-blocks [mash-build]~)
        ::
        ?.  ?=([~ %success %mash *] mash-result)
          (wrap-error mash-result)
        ::
        =/  =build-result
          [%success %mash cage.u.mash-result]
        ::
        (return-result build-result)
      ::
      ?.  (slab %form p.grad-vase)
        %-  return-error  :_  ~  :-  %leaf
        "ford: %mash failed: %{<mark>} mark has no +form:grad"
      ::
      ?.  (slab %mash p.grad-vase)
        %-  return-error  :_  ~  :-  %leaf
        "ford: %mash failed: %{<mark>} mark has no +mash:grad"
      ::
      =/  form-build=^build
        [date.build [%ride [%limb %form] [%$ %noun grad-vase]]]
      ::
      =^  form-result  out  (depend-on form-build)
      ?~  form-result
        (return-blocks [form-build]~)
      ::
      ?.  ?=([~ %success %ride *] form-result)
        (wrap-error form-result)
      ::
      =/  form-mark=(unit term)  ((soft @tas) q.vase.u.form-result)
      ?~  form-mark
        %-  return-error  :_  ~  :-  %leaf
        "ford: %mash failed: %{<mark>} mark invalid +form:grad"
      ::
      ?.  &(=(u.form-mark p.first-cage) =(u.form-mark p.second-cage))
        %-  return-error  :_  ~  :-  %leaf
        "ford: %mash failed: mark mismatch"
      ::
      ?:  =(q.q.first-cage q.q.second-cage)
        =/  =build-result
          [%success %mash [%null [%atom %n ~] ~]]
        ::
        (return-result build-result)
      ::  call the +mash:grad gate on two [ship desk diff] triples
      ::
      =/  mash-build=^build
        :-  date.build
        :+  %call
          :+  %ride
            [%limb %mash]
          [%$ %noun grad-vase]
        :+  %$  %noun
        %+  slop
          ;:  slop
            [[%atom %p ~] ship.disc.first]
            [[%atom %tas ~] desk.disc.first]
            q.first-cage
          ==
        ;:  slop
          [[%atom %p ~] ship.disc.second]
          [[%atom %tas ~] desk.disc.second]
          q.second-cage
        ==
      ::
      =^  mash-result  out  (depend-on mash-build)
      ?~  mash-result
        (return-blocks [mash-build]~)
      ::
      ?.  ?=([~ %success %call *] mash-result)
        (wrap-error mash-result)
      ::
      =/  =build-result
        [%success %mash [u.form-mark vase.u.mash-result]]
      ::
      (return-result build-result)
    ::
    ++  make-mute
      ~%  %make-mute  ..^^$  ~
      |=  [subject=schematic mutations=(list [=wing =schematic])]
      ^-  build-receipt
      ::  run the subject build to produce the noun to be mutated
      ::
      =/  subject-build=^build  [date.build subject]
      =^  subject-result  out  (depend-on subject-build)
      ?~  subject-result
        (return-blocks [subject-build]~)
      ::
      ?.  ?=([~ %success *] subject-result)
        (wrap-error subject-result)
      ::
      =/  subject-cage=cage  (result-to-cage u.subject-result)
      ::
      =/  subject-vase=vase  q.subject-cage
      ::
      =^  maybe-schematic-results  out
        %-  perform-schematics  :*
          "ford: %mute contained failures:"
          mutations
          %fail-on-errors
          *wing
        ==
      ?~  maybe-schematic-results
        out
      ::  all builds succeeded; retrieve vases from results
      ::
      =/  successes=(list [=wing =vase])
        %+  turn  u.maybe-schematic-results
        |=  [=wing result=build-result]
        ^-  [^wing vase]
        ::
        ?>  ?=([%success *] result)
        ::
        [wing q:(result-to-cage result)]
      ::  create and run a +build to apply all mutations in order
      ::
      =/  ride-build=^build
        :-  date.build
        :+  %ride
          ::  formula: a `%_` +hoon that applies a list of mutations
          ::
          ::    The hoon ends up looking like:
          ::    ```
          ::    %_  +2
          ::      wing-1  +6
          ::      wing-2  +14
          ::      ...
          ::    ==
          ::    ```
          ::
          ^=  formula
          ^-  hoon
          :+  %cncb  [%& 2]~
          =/  axis  3
          ::
          |-  ^-  (list [wing hoon])
          ?~  successes  ~
          ::
          :-  [wing.i.successes [%$ (peg axis 2)]]
          $(successes t.successes, axis (peg axis 3))
        ::  subject: list of :subject-vase and mutations, as literal schematic
        ::
        ::    The subject ends up as a vase of something like this:
        ::    ```
        ::    :~  original-subject
        ::        mutant-1
        ::        mutant-2
        ::        ...
        ::    ==
        ::    ```
        ::
        ^=  subject  ^-  schematic
        :+  %$  %noun
        ^-  vase
        %+  slop  subject-vase
        |-  ^-  vase
        ?~  successes  [[%atom %n ~] ~]
        ::
        (slop vase.i.successes $(successes t.successes))
      ::
      =^  ride-result  out  (depend-on ride-build)
      ?~  ride-result
        (return-blocks [ride-build]~)
      ::
      ?.  ?=([~ %success %ride *] ride-result)
        (wrap-error ride-result)
      ::
      =/  =build-result
        [%success %mute p.subject-cage vase.u.ride-result]
      ::
      (return-result build-result)
    ::
    ++  make-pact
      ~%  %make-pact  ..^^$  ~
      |=  [disc=disc start=schematic diff=schematic]
      ^-  build-receipt
      ::  first, build the inputs
      ::
      =/  initial-build=^build  [date.build start diff]
      ::
      =^  initial-result  out  (depend-on initial-build)
      ?~  initial-result
        (return-blocks [initial-build]~)
      ::
      ?>  ?=([~ %success ^ ^] initial-result)
      =/  start-result=build-result  head.u.initial-result
      =/  diff-result=build-result    tail.u.initial-result
      ::
      ?.  ?=(%success -.start-result)
        (wrap-error `start-result)
      ?.  ?=(%success -.diff-result)
        (wrap-error `diff-result)
      ::
      =/  start-cage=cage  (result-to-cage start-result)
      =/  diff-cage=cage    (result-to-cage diff-result)
      ::
      =/  start-mark=term  p.start-cage
      =/  diff-mark=term    p.diff-cage
      ::  load the starting mark from the filesystem
      ::
      =/  mark-path-build=^build  [date.build [%path disc %mar start-mark]]
      ::
      =^  mark-path-result  out
        (depend-on mark-path-build)
      ::
      ?~  mark-path-result
        (return-blocks [mark-path-build]~)
      ::
      ?.  ?=([~ %success %path *] mark-path-result)
        (wrap-error mark-path-result)
      ::
      =/  mark-build=^build  [date.build [%core rail.u.mark-path-result]]
      ::
      =^  mark-result  out  (depend-on mark-build)
      ?~  mark-result
        (return-blocks [mark-build]~)
      ::
      ?.  ?=([~ %success %core *] mark-result)
        (wrap-error mark-result)
      ::
      =/  mark-vase=vase  vase.u.mark-result
      ::  fire the +grad arm of the mark core
      ::
      ?.  (slab %grad p.mark-vase)
        %-  return-error  :_  ~  :-  %leaf
        "ford: %pact failed: %{<start-mark>} mark has no +grad arm"
      ::
      =/  grad-build=^build
        [date.build [%ride [%limb %grad] [%$ %noun mark-vase]]]
      ::
      =^  grad-result  out  (depend-on grad-build)
      ?~  grad-result
        (return-blocks [grad-build]~)
      ::
      ?.  ?=([~ %success %ride *] grad-result)
        (wrap-error grad-result)
      ::
      =/  grad-vase=vase  vase.u.grad-result
      ::  +grad can produce a term or a core
      ::
      ::    If a mark's +grad arm produces a mark (as a +term),
      ::    it means we should use that mark's machinery to run %pact.
      ::    In this way, a mark can delegate its patching machinery to
      ::    another mark.
      ::
      ::    First we cast :start-cage to the +grad mark, then we run
      ::    a new %pact build on the result of that, which will use the
      ::    +grad mark's +grad arm. Finally we cast the %pact result back to
      ::    :start-mark, since we're trying to produce a patched version of
      ::    the initial marked value (:start-cage).
      ::
      ?@  q.grad-vase
        ::  if +grad produced a term, make sure it's a valid mark
        ::
        =/  grad-mark=(unit term)  ((sand %tas) q.grad-vase)
        ?~  grad-mark
          %-  return-error  :_  ~  :-  %leaf
          "ford: %pact failed: %{<start-mark>} mark invalid +grad"
        ::  cast :start-cage to :grad-mark, %pact that, then cast back to start
        ::
        =/  cast-build=^build
          :-  date.build
          :^  %cast  disc  start-mark
          :^  %pact  disc
            :^  %cast  disc  u.grad-mark
            [%$ start-cage]
          [%$ diff-cage]
        ::
        =^  cast-result  out  (depend-on cast-build)
        ?~  cast-result
          (return-blocks [cast-build]~)
        ::
        ?.  ?=([~ %success %cast *] cast-result)
          (wrap-error cast-result)
        ::
        =/  =build-result
          [%success %pact cage.u.cast-result]
        ::
        (return-result build-result)
      ::  +grad produced a core; make sure it has a +form arm
      ::
      ::    +grad can produce a core containing +pact and +form
      ::    arms. +form:grad, which produces a mark (as a term), is used
      ::    to verify that the diff is of the correct mark.
      ::
      ::    +pact:grad produces a gate that gets slammed with the diff
      ::    as its sample and produces a mutant version of :start-cage
      ::    by applying the diff.
      ::
      ?.  (slab %form p.grad-vase)
        %-  return-error  :_  ~  :-  %leaf
        "ford: %pact failed: no +form:grad in %{<start-mark>} mark"
      ::  we also need a +pact arm in the +grad core
      ::
      ?.  (slab %pact p.grad-vase)
        %-  return-error  :_  ~  :-  %leaf
        "ford: %pact failed: no +pact:grad in %{<start-mark>} mark"
      ::  fire the +form arm in the core produced by +grad
      ::
      =/  form-build=^build
        [date.build [%ride [%limb %form] [%$ %noun grad-vase]]]
      ::
      =^  form-result  out  (depend-on form-build)
      ?~  form-result
        (return-blocks [form-build]~)
      ::
      ?.  ?=([~ %success %ride *] form-result)
        (wrap-error form-result)
      ::  +form:grad should produce a mark
      ::
      =/  form-mark=(unit @tas)  ((soft @tas) q.vase.u.form-result)
      ?~  form-mark
        %-  return-error  :_  ~  :-  %leaf
        "ford: %pact failed: %{<start-mark>} mark invalid +form:grad"
      ::  mark produced by +form:grad needs to match the mark of the diff
      ::
      ?.  =(u.form-mark diff-mark)
        %-  return-error  :_  ~  :-  %leaf
        "ford: %pact failed: %{<start-mark>} mark invalid +form:grad"
      ::  call +pact:grad on the diff
      ::
      =/  pact-build=^build
        :-  date.build
        :+  %call
          ^-  schematic
          :+  %ride
            [%tsld [%limb %pact] [%limb %grad]]
          ^-  schematic
          :+  %mute
            ^-  schematic
            [%$ %noun mark-vase]
          ^-  (list [wing schematic])
          [[%& 6]~ [%$ start-cage]]~
        ^-  schematic
        [%$ diff-cage]
      ::
      =^  pact-result  out  (depend-on pact-build)
      ?~  pact-result
        (return-blocks [pact-build]~)
      ::
      ?.  ?=([~ %success %call *] pact-result)
        (wrap-error pact-result)
      ::
      =/  =build-result
        [%success %pact start-mark vase.u.pact-result]
      ::
      (return-result build-result)
    ::
    ++  make-path
      ~%  %make-path  ..^^$  ~
      |=  [disc=disc prefix=@tas raw-path=@tas]
      ^-  build-receipt
      ::  possible-spurs: flopped paths to which :raw-path could resolve
      ::
      =/  possible-spurs=(list spur)  (turn (segments raw-path) flop)
      ::  rails-and-schematics: scrys to check each path in :possible-paths
      ::
      =/  rails-and-schematics=(list [=rail =schematic])
        %+  turn  possible-spurs
        |=  possible-spur=spur
        ^-  [rail schematic]
        ::  full-spur: wrap :possible-spur with :prefix and /hoon suffix
        ::
        =/  full-spur=spur  :(welp /hoon possible-spur /[prefix])
        ::
        :-  [disc full-spur]
        [%scry %c %x `rail`[disc full-spur]]
      ::  depend on builds of each schematic
      ::
      =^  maybe-schematic-results  out
        %-  perform-schematics  :*
          ;:  weld
            "ford: %path resolution of "  (trip raw-path)  "at prefix "
            (trip prefix)  " contained failures:"
          ==
          rails-and-schematics
          %filter-errors
          *rail
        ==
      ?~  maybe-schematic-results
        out
      ::  matches: builds that completed with a successful result
      ::
      =/  matches  u.maybe-schematic-results
      ::  if no matches, error out
      ::
      ?~  matches
        =/  =beam
          [[ship.disc desk.disc [%da date.build]] /hoon/[raw-path]/[prefix]]
        ::
        %-  return-error
        :_  ~
        :-  %leaf
        (weld "%path: no matches for " (spud (en-beam beam)))
      ::  if exactly one path matches, succeed with the matching path
      ::
      ?:  ?=([* ~] matches)
        (return-result %success %path key.i.matches)
      ::  multiple paths matched; error out
      ::
      %-  return-error
      ::
      :-  [%leaf "multiple matches for %path: "]
      ::  tmi; cast :matches back to +list
      ::
      %+  roll  `_u.maybe-schematic-results`matches
      |=  [[key=rail result=build-result] message=tang]
      ^-  tang
      ::  beam: reconstruct request from :kid's schematic and date
      ::
      =/  =beam  [[ship.disc desk.disc [%da date.build]] spur.key]
      ::
      [[%leaf (spud (en-beam beam))] message]
    ::
    ++  make-plan
      ~%  %make-plan  ..^^$  ~
      |=  [path-to-render=rail query-string=coin =scaffold]
      ^-  build-receipt
      ::  blocks: accumulator for blocked sub-builds
      ::
      =|  blocks=(list ^build)
      ::  error-message: accumulator for failed sub-builds
      ::
      =|  error-message=tang
      ::
      |^  ::  imports: structure and library +cables, with %sur/%lib prefixes
          ::
          =/  imports=(list [prefix=?(%sur %lib) =cable])
            %+  welp
              (turn structures.scaffold |=(cable [%sur +<]))
            (turn libraries.scaffold |=(cable [%lib +<]))
          ::  path-builds: %path sub-builds to resolve import paths
          ::
          =/  path-builds  (gather-path-builds imports)
          ::
          =^  path-results  ..$  (resolve-builds path-builds)
          ?^  blocks
            (return-blocks blocks)
          ::
          ?^  error-message
            (return-error error-message)
          ::  tmi; remove type specializations
          ::
          =>  .(blocks *(list ^build), error-message *tang)
          ::  core-builds: %core sub-builds to produce library vases
          ::
          =/  core-builds  (gather-core-builds path-results)
          ::
          =^  core-results  ..$  (resolve-builds core-builds)
          ?^  blocks
            (return-blocks blocks)
          ::
          ?^  error-message
            (return-error error-message)
          ::  reef-build: %reef build to produce standard library
          ::
          =/  reef-build=^build  [date.build [%reef disc.path-to-render]]
          ::
          =^  reef-result  out  (depend-on reef-build)
          ?~  reef-result
            (return-blocks [reef-build]~)
          ::
          ?.  ?=([~ %success %reef *] reef-result)
            (wrap-error reef-result)
          ::  subject: tuple of imports and standard library
          ::
          =/  subject=vase
            (link-imports imports vase.u.reef-result core-results)
          ::  tmi; remove type specializations
          ::
          =>  .(blocks *(list ^build), error-message *tang)
          ::  iterate over each crane
          ::
          =^  crane-result  ..$
            (compose-cranes [%noun subject] cranes.scaffold)
          ?:  ?=(%error -.crane-result)
            (return-error message.crane-result)
          ?:  ?=(%block -.crane-result)
            (return-blocks builds.crane-result)
          ::  combined-hoon: source hoons condensed into a single +hoon
          ::
          =/  combined-hoon=hoon  [%tssg sources.scaffold]
          ::  compile :combined-hoon against :subject
          ::
          =/  compile=^build
            [date.build [%ride combined-hoon [%$ subject.crane-result]]]
          ::
          =^  compiled  out  (depend-on compile)
          ::  compilation blocked; produce block on sub-build
          ::
          ?~  compiled
            (return-blocks ~[compile])
          ::  compilation failed; error out
          ::
          ?.  ?=([~ %success %ride *] compiled)
            (wrap-error compiled)
          ::  compilation succeeded: produce resulting +vase
          ::
          (return-result %success %plan vase.u.compiled)
      ::  +compose-result: the result of a single composition
      ::
      +=  compose-result
        $%  [%subject subject=cage]
            [%block builds=(list ^build)]
            [%error message=tang]
        ==
      ::  +compose-cranes: runs each crane and composes the results
      ::
      ::    For each crane in :cranes, runs it and composes its result into a
      ::    new subject, which is returned if there are no errors or blocks.
      ::
      ++  compose-cranes
        |=  [subject=cage cranes=(list crane)]
        ^-  $:  compose-result
                _..compose-cranes
            ==
        ::
        ?~  cranes
          [[%subject subject] ..compose-cranes]
        ::
        =^  result  ..compose-cranes  (run-crane subject i.cranes)
        ?+    -.result  [result ..compose-cranes]
        ::
            %subject
          $(cranes t.cranes, subject [%noun (slop q.subject.result q.subject)])
        ==
      ::  +run-crane: runs an individual :crane against :subject
      ::
      ++  run-crane
        |=  [subject=cage =crane]
        ^-  compose-cranes
        ::
        |^  ?-  -.crane
              %fssg  (run-fssg +.crane)
              %fsbc  (run-fsbc +.crane)
              %fsbr  (run-fsbr +.crane)
              %fsts  (run-fsts +.crane)
              %fscm  (run-fscm +.crane)
              %fspm  (run-fspm +.crane)
              %fscb  (run-fscb +.crane)
              %fsdt  (run-fsdt +.crane)
              %fssm  (run-fssm +.crane)
              %fscl  (run-fscl +.crane)
              %fskt  (run-fskt +.crane)
              %fstr  (run-fstr +.crane)
              %fszp  (run-fszp +.crane)
              %fszy  (run-fszy +.crane)
            ==
        ::  +run-fssg: runs the `/~` rune
        ::
        ++  run-fssg
          |=  =hoon
          ^-  compose-cranes
          ::
          =/  ride-build=^build
            [date.build [%ride hoon [%$ subject]]]
          =^  ride-result  out  (depend-on ride-build)
          ?~  ride-result
            [[%block [ride-build]~] ..run-crane]
          ?:  ?=([~ %error *] ride-result)
            [[%error [leaf+"/~ failed: " message.u.ride-result]] ..run-crane]
          ?>  ?=([~ %success %ride *] ride-result)
          [[%subject %noun vase.u.ride-result] ..run-crane]
        ::  +run-fsbc: runs the `/$` rune
        ::
        ++  run-fsbc
          |=  =hoon
          ^-  compose-cranes
          ::
          =/  query-compile-build=^build
            [date.build [%ride ((jock |) query-string) [%$ %noun !>(~)]]]
          =^  query-compile-result  out  (depend-on query-compile-build)
          ?~  query-compile-result
            [[%block [query-compile-build]~] ..run-crane]
          ?:  ?=([~ %error *] query-compile-result)
            :-  [%error [leaf+"/; failed: " message.u.query-compile-result]]
            ..run-crane
          ?>  ?=([~ %success %ride *] query-compile-result)
          ::
          =/  =beam
            =,  path-to-render
            [[ship.disc desk.disc [%da date.build]] spur]
          =+  arguments=(slop !>(beam) vase.u.query-compile-result)
          ::
          =/  call-build=^build
            [date.build [%call [%ride hoon [%$ subject]] [%$ %noun arguments]]]
          =^  call-result  out  (depend-on call-build)
          ?~  call-result
            [[%block [call-build]~] ..run-crane]
          ?:  ?=([~ %error *] call-result)
            [[%error [leaf+"/; failed: " message.u.call-result]] ..run-crane]
          ?>  ?=([~ %success %call *] call-result)
          ::
          [[%subject %noun vase.u.call-result] ..run-crane]
        ::  +run-fsbr: runs the `/|` rune
        ::
        ++  run-fsbr
          |=  choices=(list ^crane)
          ^-  compose-cranes
          ::
          ?~  choices
            [[%error [leaf+"/| failed: out of options"]~] ..run-crane]
          ::
          =^  child  ..run-crane  (run-crane subject i.choices)
          ?.  ?=([%error *] child)
            [child ..run-crane]
          $(choices t.choices)
        ::  +run-fsts: runs the `/=` rune
        ::
        ++  run-fsts
          |=  [face=term sub-crane=^crane]
          ^-  compose-cranes
          ::
          =^  child  ..run-crane  (run-crane subject sub-crane)
          ?.  ?=([%subject *] child)
            [child ..run-crane]
          :_  ..run-crane
          :*  %subject
              p.subject.child
              [[%face face p.q.subject.child] q.q.subject.child]
          ==
        ::  +run-fscm: runs the `/,` rune
        ::
        ++  run-fscm
          |=  cases=(list [=spur crane=^crane])
          ^-  compose-cranes
          ::
          ?~  cases
            [[%error [leaf+"/, failed: no match"]~] ..run-crane]
          ::
          ?.  .=  spur.i.cases
              (scag (lent spur.i.cases) (flop spur.path-to-render))
            $(cases t.cases)
          ::
          (run-crane subject crane.i.cases)
        ::  +run-fspm: runs the `/&` rune
        ::
        ++  run-fspm
          |=  [marks=(list mark) sub-crane=^crane]
          ^-  compose-cranes
          ::
          =^  child  ..run-crane  (run-crane subject sub-crane)
          ?.  ?=([%subject *] child)
            [child ..run-crane]
          ::
          =/  cast-build=^build
            :-  date.build
            |-
            ^-  schematic
            ?~  marks
              ::  TODO: If we were keeping track of the mark across runes, this
              ::  wouldn't have %noun here. This is a case where it might matter.
              ::
              [%$ subject.child]
            [%cast disc.source-rail.scaffold i.marks $(marks t.marks)]
          =^  cast-result  out  (depend-on cast-build)
          ?~  cast-result
            [[%block [cast-build]~] ..run-crane]
          ::
          ?:  ?=([~ %error *] cast-result)
            [[%error [leaf+"/& failed: " message.u.cast-result]] ..run-crane]
          ?>  ?=([~ %success %cast *] cast-result)
          ::
          [[%subject cage.u.cast-result] ..run-crane]
        ::  +run-fscb: runs the `/_` rune
        ::
        ++  run-fscb
          |=  sub-crane=^crane
          ^-  compose-cranes
          ::  perform a scry to get the contents of +path-to-render
          ::
          =/  toplevel-build=^build
            [date.build [%scry [%c %y path-to-render]]]
          ::
          =^  toplevel-result  out  (depend-on toplevel-build)
          ?~  toplevel-result
            [[%block ~[toplevel-build]] ..run-crane]
          ::
          ?:  ?=([~ %error *] toplevel-result)
            :-  [%error [leaf+"/_ failed: " message.u.toplevel-result]]
            ..run-crane
          ?>  ?=([~ %success %scry *] toplevel-result)
          ::
          =/  toplevel-arch=arch  ;;(arch q.q.cage.u.toplevel-result)
          ::  sub-path: each possible sub-directory to check
          ::
          =/  sub-paths=(list @ta)
            (turn ~(tap by dir.toplevel-arch) head)
          ::  for each directory in :toplevel-arch, issue a sub-build
          ::
          =/  sub-builds=(list ^build)
            %+  turn  sub-paths
            |=  sub=@ta
            ^-  ^build
            :-  date.build
            [%scry [%c %y path-to-render(spur [sub spur.path-to-render])]]
          ::  results: accumulator for results of sub-builds
          ::
          =|  $=  results
              (list [kid=^build sub-path=@ta results=(unit build-result)])
          ::  resolve all the :sub-builds
          ::
          =/  subs-results
            |-  ^+  [results out]
            ?~  sub-builds  [results out]
            ?>  ?=(^ sub-paths)
            ::
            =/  kid=^build  i.sub-builds
            =/  sub-path=@ta  i.sub-paths
            ::
            =^  result  out  (depend-on kid)
            =.  results  [[kid sub-path result] results]
            ::
            $(sub-builds t.sub-builds, sub-paths t.sub-paths)
          ::  apply mutations from depending on sub-builds
          ::
          =:  results  -.subs-results
              out      +.subs-results
          ==
          ::  split :results into completed :mades and incomplete :blocks
          ::
          =+  ^=  split-results
              (skid results |=([* * r=(unit build-result)] ?=(^ r)))
          ::
          =/  mades=_results   -.split-results
          =/  blocks=_results  +.split-results
          ::  if any builds blocked, produce them all in %blocks
          ::
          ?^  blocks
            [[%block (turn `_results`blocks head)] ..run-crane]
          ::  find the first error and return it if exists
          ::
          =/  errors=_results
            %+  skim  results
            |=  [* * r=(unit build-result)]
            ?=([~ %error *] r)
          ?^  errors
            ?>  ?=([~ %error *] results.i.errors)
            [[%error message.u.results.i.errors] ..run-crane]
          ::  get a list of valid sub-paths
          ::
          ::    :results is now a list of the :build-result of %cy on each path
          ::    in :toplevel-arch. What we want is to now filter this list so
          ::    that we filter files out.
          ::
          =/  sub-paths=(list [=rail sub-path=@ta])
            %+  murn  results
            |=  [build=^build sub-path=@ta result=(unit build-result)]
            ^-  (unit [rail @ta])
            ::
            ?>  ?=([@da %scry %c %y *] build)
            ?>  ?=([~ %success %scry *] result)
            =/  =arch  ;;(arch q.q.cage.u.result)
            ::
            ?~  dir.arch
              ~
            `[rail.resource.schematic.build sub-path]
          ::  keep track of the original value so we can reset it
          ::
          =/  old-path-to-render  path-to-render
          ::  apply each of the filtered :sub-paths to the :sub-crane.
          ::
          =^  crane-results  ..run-crane
            %+  roll  sub-paths
            |=  $:  [=rail sub-path=@ta]
                    $=  accumulator
                    [(list [sub-path=@ta =compose-result]) _..run-crane]
                ==
            =.  ..run-crane  +.accumulator
            =.  path-to-render  rail
            =^  result  ..run-crane  (run-crane subject sub-crane)
            [[[sub-path result] -.accumulator] ..run-crane]
          ::  set :path-to-render back
          ::
          =.  path-to-render  old-path-to-render
          ::  if any sub-cranes error, return the first error
          ::
          =/  error-list=(list [@ta =compose-result])
            %+  skim  crane-results
            |=  [@ta =compose-result]
            =(%error -.compose-result)
          ::
          ?^  error-list
            [compose-result.i.error-list ..run-crane]
          ::  if any sub-cranes block, return all blocks
          ::
          =/  block-list=(list ^build)
            =|  block-list=(list ^build)
            |-
            ^+  block-list
            ?~  crane-results
              block-list
            ?.  ?=(%block -.compose-result.i.crane-results)
              $(crane-results t.crane-results)
            =.  block-list
              (weld builds.compose-result.i.crane-results block-list)
            $(crane-results t.crane-results)
          ::
          ?^  block-list
            [[%block block-list] ..run-crane]
          ::  put the data in map order
          ::
          =/  result-map=(map @ta vase)
            %-  my
            %+  turn  crane-results
            |=  [path=@ta =compose-result]
            ^-  (pair @ta vase)
            ::
            ?>  ?=([%subject *] compose-result)
            [path q.subject.compose-result]
          ::  convert the map into a flat format for return
          ::
          ::    This step flattens the values out of the map for return. Let's
          ::    say we're doing a /_ over a directory of files that just have a
          ::    single @ud in them. We want the return value of /_ to have the
          ::    nest in (map @ta @ud) instead of returning a (map @ta vase).
          ::
          =/  as-vase=vase
            |-
            ^-  vase
            ::
            ?~  result-map
              [[%atom %n `0] 0]
            ::
            %+  slop
              (slop [[%atom %ta ~] p.n.result-map] q.n.result-map)
            (slop $(result-map l.result-map) $(result-map r.result-map))
          ::
          [[%subject %noun as-vase] ..run-crane]
        ::  +run-fsdt: runs the `/.` rune
        ::
        ++  run-fsdt
          |=  sub-cranes=(list ^crane)
          ^-  compose-cranes
          ::
          =^  list-results  ..run-crane
            %+  roll  sub-cranes
            |=  $:  sub-crane=^crane
                    accumulator=[(list compose-result) _..run-crane]
                ==
            =.  ..run-crane  +.accumulator
            =^  result  ..run-crane  (run-crane subject sub-crane)
            [[result -.accumulator] ..run-crane]
          ::  if any sub-cranes error, return the first error
          ::
          =/  error-list=(list compose-result)
            %+  skim  list-results
            |=  =compose-result
            =(%error -.compose-result)
          ::
          ?^  error-list
            [i.error-list ..run-crane]
          ::  if any sub-cranes block, return all blocks
          ::
          =/  block-list=(list ^build)
            =|  block-list=(list ^build)
            |-
            ^+  block-list
            ?~  list-results
              block-list
            ?.  ?=(%block -.i.list-results)
              $(list-results t.list-results)
            =.  block-list  (weld builds.i.list-results block-list)
            $(list-results t.list-results)
          ::
          ?^  block-list
            [[%block block-list] ..run-crane]
          ::  concatenate all the results together with null termination
          ::
          =.  list-results  (flop list-results)
          ::
          =/  final-result=vase
            |-
            ^-  vase
            ?~  list-results
              [[%atom %n `~] 0]
            ?>  ?=(%subject -.i.list-results)
            (slop q.subject.i.list-results $(list-results t.list-results))
          ::
          [[%subject %noun final-result] ..run-crane]
        ::  +run-fssm: runs the `/;` rune
        ::
        ++  run-fssm
          |=  [=hoon sub-crane=^crane]
          ^-  compose-cranes
          ::
          =^  child  ..run-crane  (run-crane subject sub-crane)
          ?.  ?=([%subject *] child)
            [child ..run-crane]
          ::
          =/  call-build=^build
            [date.build [%call [%ride hoon [%$ subject]] [%$ subject.child]]]
          =^  call-result  out  (depend-on call-build)
          ?~  call-result
            [[%block [call-build]~] ..run-crane]
          ?:  ?=([~ %error *] call-result)
            [[%error [leaf+"/; failed: " message.u.call-result]] ..run-crane]
          ?>  ?=([~ %success %call *] call-result)
          ::
          [[%subject %noun vase.u.call-result] ..run-crane]
        ::  +run-fscl: runs the `/:` rune
        ::
        ++  run-fscl
          |=  [=truss sub-crane=^crane]
          ^-  compose-cranes
          ::
          =/  beam-to-render=beam
            [[ship.disc desk.disc %ud 0] spur]:path-to-render
          ::
          =/  hoon-parser  (vang & (en-beam beam-to-render))
          ::
          =+  tuz=(posh:hoon-parser truss)
          ?~  tuz
            [[%error [leaf+"/: failed: bad tusk: {<truss>}"]~] ..run-crane]
          =+  pax=(plex:hoon-parser %clsg u.tuz)
          ?~  pax
            [[%error [leaf+"/: failed: bad path: {<u.tuz>}"]~] ..run-crane]
          =+  bem=(de-beam u.pax)
          ?~  bem
            [[%error [leaf+"/: failed: bad beam: {<u.pax>}"]~] ..run-crane]
          ::
          =.  path-to-render  [[p q] s]:u.bem
          (run-crane subject sub-crane)
        ::  +run-fskt: runs the `/^` rune
        ::
        ++  run-fskt
          |=  [=spec sub-crane=^crane]
          ^-  compose-cranes
          ::
          =^  child  ..run-crane  (run-crane subject sub-crane)
          ?.  ?=([%subject *] child)
            [child ..run-crane]
          ::
          =/  bunt-build=^build
            [date.build [%ride [%kttr spec] [%$ subject]]]
          =^  bunt-result  out  (depend-on bunt-build)
          ?~  bunt-result
            [[%block [bunt-build]~] ..run-crane]
          ?:  ?=([~ %error *] bunt-result)
            [[%error [leaf+"/^ failed: " message.u.bunt-result]] ..run-crane]
          ?>  ?=([~ %success %ride *] bunt-result)
          ::
          ?.  (~(nest ut p.vase.u.bunt-result) | p.q.subject.child)
            [[%error [leaf+"/^ failed: nest-fail"]~] ..run-crane]
          :_  ..run-crane
          [%subject %noun [p.vase.u.bunt-result q.q.subject.child]]
        ::  +run-fstr: runs the `/*` rune
        ::
        ::    TODO: some duplicate code with +run-fscb
        ::
        ++  run-fstr
          |=  sub-crane=^crane
          ^-  compose-cranes
          ::
          =/  tree-build=^build
            [date.build [%scry [%c %t path-to-render]]]
          ::
          =^  tree-result  out  (depend-on tree-build)
          ?~  tree-result
            [[%block ~[tree-build]] ..run-crane]
          ::
          ?:  ?=([~ %error *] tree-result)
            :-  [%error [%leaf "/* failed: "] message.u.tree-result]
            ..run-crane
          ?>  ?=([~ %success %scry *] tree-result)
          ::
          =/  file-list=(list path)  ;;((list path) q.q.cage.u.tree-result)
          ::  trim file extensions off the file paths
          ::
          ::    This is pretty ugly, but Ford expects :path-to-render not to
          ::    have a file extension, so we need to trim it off each path.
          ::
          =.  file-list
            ::  deduplicate since multiple files could share a trimmed path
            ::
            =-  ~(tap in (~(gas in *(set path)) `(list path)`-))
            %+  turn  file-list
            |=  =path
            ^+  path
            (scag (sub (lent path) 1) path)
          ::
          =/  old-path-to-render  path-to-render
          ::  apply each of the paths in :file-list to the :sub-crane
          ::
          =^  crane-results  ..run-crane
            %+  roll  file-list
            |=  $:  =path
                    $=  accumulator
                    [(list [=path =compose-result]) _..run-crane]
                ==
            =.  ..run-crane  +.accumulator
            =.  spur.path-to-render  (flop path)
            ::
            =^  result  ..run-crane  (run-crane subject sub-crane)
            [[[path result] -.accumulator] ..run-crane]
          ::
          =.  path-to-render  old-path-to-render
          ::  if any sub-cranes error, return the first error
          ::
          =/  error-list=(list [=path =compose-result])
            %+  skim  crane-results
            |=  [=path =compose-result]
            =(%error -.compose-result)
          ::
          ?^  error-list
            [compose-result.i.error-list ..run-crane]
          ::  if any sub-cranes block, return all blocks
          ::
          =/  block-list=(list ^build)
            =|  block-list=(list ^build)
            |-  ^+  block-list
            ?~  crane-results  block-list
            ::
            ?.  ?=(%block -.compose-result.i.crane-results)
              $(crane-results t.crane-results)
            =.  block-list
              (weld builds.compose-result.i.crane-results block-list)
            ::
            $(crane-results t.crane-results)
          ::
          ?^  block-list
            [[%block block-list] ..run-crane]
          ::
          =/  result-map=(map path vase)
            %-  my
            %+  turn  crane-results
            |=  [=path =compose-result]
            ^-  (pair ^path vase)
            ::
            ?>  ?=(%subject -.compose-result)
            [path q.subject.compose-result]
          ::
          =/  as-vase
            =/  path-type  -:!>(*path)
            |-  ^-  vase
            ?~  result-map  [[%atom %n `0] 0]
            ::
            %+  slop
              (slop [path-type p.n.result-map] q.n.result-map)
            (slop $(result-map l.result-map) $(result-map r.result-map))
          ::
          [[%subject %noun as-vase] ..run-crane]
        ::  +run-fszp: runs the `/!mark/` "rune"
        ::
        ++  run-fszp
          |=  =mark
          ^-  compose-cranes
          ::
          =/  hoon-path=rail
            =,  path-to-render
            [disc [%hoon spur]]
          ::
          =/  hood-build=^build  [date.build [%hood hoon-path]]
          =^  hood-result  out  (depend-on hood-build)
          ?~  hood-result
            [[%block [hood-build]~] ..run-crane]
          ?:  ?=([~ %error *] hood-result)
            [[%error [leaf+"/! failed: " message.u.hood-result]] ..run-crane]
          ?>  ?=([~ %success %hood *] hood-result)
          ::
          =/  plan-build=^build
            :-  date.build
            [%plan path-to-render query-string scaffold.u.hood-result]
          =^  plan-result  out  (depend-on plan-build)
          ?~  plan-result
            [[%block [plan-build]~] ..run-crane]
          ?:  ?=([~ %error *] plan-result)
            [[%error [leaf+"/! failed: " message.u.plan-result]] ..run-crane]
          ?>  ?=([~ %success %plan *] plan-result)
          ::  if :mark is %noun, don't perform mark translation; just return
          ::
          ::    If we were to verify the product type with %noun, this would
          ::    cast to *, which would overwrite :vase.u.plan-result's actual
          ::    product type
          ::
          ?:  =(%noun mark)
            [[%subject %noun vase.u.plan-result] ..run-crane]
          ::
          =/  vale-build=^build
            :-  date.build
            [%vale disc.source-rail.scaffold mark q.vase.u.plan-result]
          =^  vale-result  out  (depend-on vale-build)
          ?~  vale-result
            [[%block [vale-build]~] ..run-crane]
          ?:  ?=([~ %error *] vale-result)
            [[%error [leaf+"/! failed: " message.u.vale-result]] ..run-crane]
          ?>  ?=([~ %success %vale *] vale-result)
          ::
          [[%subject cage.u.vale-result] ..run-crane]
        ::  +run-fszy: runs the `/mark/` "rune"
        ::
        ++  run-fszy
          |=  =mark
          ^-  compose-cranes
          ::
          =/  bake-build=^build
            :-  date.build
            [%bake mark query-string path-to-render]
          =^  bake-result  out  (depend-on bake-build)
          ?~  bake-result
            [[%block [bake-build]~] ..run-crane]
          ?:  ?=([~ %error *] bake-result)
            :_  ..run-crane
            [%error [leaf+"/{(trip mark)}/ failed: " message.u.bake-result]]
          ?>  ?=([~ %success %bake *] bake-result)
          ::
          [[%subject cage.u.bake-result] ..run-crane]
        --
      ::  +gather-path-builds: produce %path builds to resolve import paths
      ::
      ++  gather-path-builds
        |=  imports=(list [prefix=?(%sur %lib) =cable])
        ^-  (list ^build)
        ::
        %+  turn  imports
        |=  [prefix=?(%sur %lib) =cable]
        ^-  ^build
        [date.build [%path disc.source-rail.scaffold prefix file-path.cable]]
      ::  +resolve-builds: run a list of builds and collect results
      ::
      ::    If a build blocks, put its +tang in :error-message and stop.
      ::    All builds that block get put in :blocks. Results of
      ::    successful builds are produced in :results.
      ::
      ++  resolve-builds
        =|  results=(list build-result)
        |=  builds=(list ^build)
        ^+  [results ..^$]
        ::
        ?~  builds
          [results ..^$]
        ::
        =^  result  out  (depend-on i.builds)
        ?~  result
          =.  blocks  [i.builds blocks]
          $(builds t.builds)
        ::
        ?.  ?=(%success -.u.result)
          =.  error-message  [[%leaf "%plan failed: "] message.u.result]
          [results ..^$]
        ::
        =.  results  [u.result results]
        $(builds t.builds)
      ::  +gather-core-builds: produce %core builds from resolved paths
      ::
      ++  gather-core-builds
        |=  path-results=(list build-result)
        ^-  (list ^build)
        %+  turn  path-results
        |=  result=build-result
        ^-  ^build
        ::
        ?>  ?=([%success %path *] result)
        ::
        [date.build [%core rail.result]]
      ::  +link-imports: link libraries and structures with standard library
      ::
      ::    Prepends each library vase onto the standard library vase.
      ::    Wraps a face around each library to prevent namespace leakage
      ::    unless imported as *lib-name.
      ::
      ++  link-imports
        |=  $:  imports=(list [?(%lib %sur) =cable])
                reef=vase
                core-results=(list build-result)
            ==
        ^-  vase
        ::
        =/  subject=vase  reef
        ::
        =/  core-vases=(list vase)
          %+  turn  core-results
          |=  result=build-result
          ^-  vase
          ?>  ?=([%success %core *] result)
          vase.result
        ::  link structures and libraries into a subject for compilation
        ::
        |-  ^+  subject
        ?~  core-vases  subject
        ?<  ?=(~ imports)
        ::  cons this vase onto the head of the subject
        ::
        =.  subject
          %-  slop  :_  subject
          ::  check if the programmer named the library
          ::
          ?~  face.cable.i.imports
            ::  no face assigned to this library, so use vase as-is
            ::
            i.core-vases
          ::  use the library name as a face to prevent namespace leakage
          ::
          ^-  vase
          [[%face u.face.cable.i.imports p.i.core-vases] q.i.core-vases]
        ::
        $(core-vases t.core-vases, imports t.imports)
      --
    ::
    ++  make-reef
      ~%  %make-reef  ..^^$  ~
      |=  =disc
      ^-  build-receipt
      ::
      =/  hoon-scry
        [date.build [%scry %c %x [disc /hoon/hoon/sys]]]
      ::
      =^  hoon-scry-result  out  (depend-on hoon-scry)
      ::
      =/  arvo-scry
        [date.build [%scry %c %x [disc /hoon/arvo/sys]]]
      ::
      =^  arvo-scry-result  out  (depend-on arvo-scry)
      ::
      =/  zuse-scry
        [date.build [%scry %c %x [disc /hoon/zuse/sys]]]
      ::
      =^  zuse-scry-result  out  (depend-on zuse-scry)
      ::
      =|  blocks=(list ^build)
      =?  blocks  ?=(~ hoon-scry-result)  [hoon-scry blocks]
      =?  blocks  ?=(~ arvo-scry-result)  [arvo-scry blocks]
      =?  blocks  ?=(~ zuse-scry-result)  [zuse-scry blocks]
      ::
      ?^  blocks
        (return-blocks blocks)
      ::
      ?.  ?=([~ %success %scry *] hoon-scry-result)
        (wrap-error hoon-scry-result)
      ::
      ?.  ?=([~ %success %scry *] arvo-scry-result)
        (wrap-error arvo-scry-result)
      ::
      ?.  ?=([~ %success %scry *] zuse-scry-result)
        (wrap-error zuse-scry-result)
      ::  short-circuit to :pit if asked for current %home desk
      ::
      ::    This avoids needing to recompile the kernel if we're asked
      ::    for the kernel we're already running. Note that this fails
      ::    referential transparency if |autoload is turned off.
      ::
      ?:  ?&  |(=(disc [our %home]) =(disc [our %base]))
              ::  is :date.build the latest commit on the %home desk?
              ::
              ?|  =(now date.build)
                  ::
                  =/  =beam  [[our %home [%da date.build]] /hoon/hoon/sys]
                  ::
                  .=  (scry [%141 %noun] ~ %cw beam)
                  (scry [%141 %noun] ~ %cw beam(r [%da now]))
          ==  ==
        ::
        (return-result %success %reef pit)
      ::  omit case from path to prevent cache misses
      ::
      =/  hoon-path=path
        /(scot %p ship.disc)/(scot %tas desk.disc)/hoon/hoon/sys
      =/  hoon-hoon=(each hoon tang)
        %-  mule  |.
        (rain hoon-path ;;(@t q.q.cage.u.hoon-scry-result))
      ?:  ?=(%| -.hoon-hoon)
        (return-error leaf+"ford: %reef failed to compile hoon" p.hoon-hoon)
      ::
      =/  arvo-path=path
        /(scot %p ship.disc)/(scot %tas desk.disc)/hoon/arvo/sys
      =/  arvo-hoon=(each hoon tang)
        %-  mule  |.
        (rain arvo-path ;;(@t q.q.cage.u.arvo-scry-result))
      ?:  ?=(%| -.arvo-hoon)
        (return-error leaf+"ford: %reef failed to compile arvo" p.arvo-hoon)
      ::
      =/  zuse-path=path
        /(scot %p ship.disc)/(scot %tas desk.disc)/hoon/zuse/sys
      =/  zuse-hoon=(each hoon tang)
        %-  mule  |.
        (rain zuse-path ;;(@t q.q.cage.u.zuse-scry-result))
      ?:  ?=(%| -.zuse-hoon)
        (return-error leaf+"ford: %reef failed to compile zuse" p.zuse-hoon)
      ::
      =/  zuse-build=^build
        :*  date.build
            %ride  p.zuse-hoon
            ::  hoon for `..is` to grab the :pit out of the arvo core
            ::
            %ride  [%cnts ~[[%& 1] %is] ~]
            %ride  p.arvo-hoon
            %ride  [%$ 7]
            %ride  p.hoon-hoon
            [%$ %noun !>(~)]
        ==
      ::
      =^  zuse-build-result  out  (depend-on zuse-build)
      ?~  zuse-build-result
        (return-blocks [zuse-build]~)
      ::
      ?.  ?=([~ %success %ride *] zuse-build-result)
        (wrap-error zuse-build-result)
      ::
      (return-result %success %reef vase.u.zuse-build-result)
    ::
    ++  make-ride
      ~%  %make-ride  ..^^$  ~
      |=  [formula=hoon =schematic]
      ^-  build-receipt
      ::
      =^  result  out  (depend-on [date.build schematic])
      ?~  result
        (return-blocks [date.build schematic]~)
      ::
      =*  subject-vase  q:(result-to-cage u.result)
      =/  slim-schematic=^schematic  [%slim p.subject-vase formula]
      =^  slim-result  out  (depend-on [date.build slim-schematic])
      ?~  slim-result
        (return-blocks [date.build slim-schematic]~)
      ::
      ?:  ?=([~ %error *] slim-result)
        %-  return-error
        :*  [%leaf "ford: %ride failed to compute type:"]
            message.u.slim-result
        ==
      ::
      ?>  ?=([~ %success %slim *] slim-result)
      ::
      =/  =compiler-cache-key  [%ride formula subject-vase]
      =^  cached-result  out  (access-cache compiler-cache-key)
      ?^  cached-result
        (return-result u.cached-result)
      ::
      =/  val
        (mock [q.subject-vase nock.u.slim-result] intercepted-scry)
      ::  val is a toon, which might be a list of blocks.
      ::
      ?-    -.val
      ::
          %0
        (return-result %success %ride [type.u.slim-result p.val])
      ::
          %1
        =/  blocked-paths=(list path)  ;;((list path) p.val)
        (blocked-paths-to-receipt %ride blocked-paths)
      ::
          %2
        (return-error [[%leaf "ford: %ride failed to execute:"] p.val])
      ==
    ::
    ++  make-same
      ~%  %make-same  ..^^$  ~
      |=  =schematic
      ^-  build-receipt
      ::
      =^  result  out  (depend-on [date.build schematic])
      ::
      ?~  result
        (return-blocks [date.build schematic]~)
      (return-result u.result)
    ::
    ++  make-scry
      ~%  %make-scry  ..^^$  ~
      |=  =resource
      ^-  build-receipt
      ::  construct a full +beam to make the scry request
      ::
      =/  =beam          (extract-beam resource `date.build)
      =/  =scry-request  [vane.resource care.resource beam]
      ::  perform scry operation if we don't already know the result
      ::
      ::    Look up :scry-request in :scry-results.per-event to avoid
      ::    rerunning a previously blocked +scry.
      ::
      =/  scry-response
        ?:  (~(has by scry-results) scry-request)
          (~(get by scry-results) scry-request)
        (scry [%141 %noun] ~ `@tas`(cat 3 [vane care]:resource) beam)
      ::  scry blocked
      ::
      ?~  scry-response
        (return-blocks ~)
      ::  scry failed
      ::
      ?~  u.scry-response
        %-  return-error
        :~  leaf+"scry failed for"
            leaf+:(weld "%c" (trip care.resource) " " (spud (en-beam beam)))
        ==
      ::  scry succeeded
      ::
      (return-result %success %scry u.u.scry-response)
    ::
    ++  make-slim
      ~%  %make-slim  ..^^$  ~
      |=  [subject-type=type formula=hoon]
      ^-  build-receipt
      ::
      =/  =compiler-cache-key  [%slim subject-type formula]
      =^  cached-result  out  (access-cache compiler-cache-key)
      ?^  cached-result
        (return-result u.cached-result)
      ::
      =/  compiled=(each (pair type nock) tang)
        (mule |.((~(mint ut subject-type) [%noun formula])))
      ::
      %_    out
          result
        ?-  -.compiled
          %|  [%build-result %error [leaf+"ford: %slim failed: " p.compiled]]
          %&  [%build-result %success %slim p.compiled]
        ==
      ==
    ::  TODO: Take in +type instead of +vase?
    ::
    ++  make-slit
      ~%  %make-slit  ..^^$  ~
      |=  [gate=vase sample=vase]
      ^-  build-receipt
      ::
      =/  =compiler-cache-key  [%slit p.gate p.sample]
      =^  cached-result  out  (access-cache compiler-cache-key)
      ?^  cached-result
        (return-result u.cached-result)
      ::
      =/  product=(each type tang)
        (mule |.((slit p.gate p.sample)))
      ::
      %_    out
          result
        ?-  -.product
          %|  :*  %build-result   %error
                  :*  (~(dunk ut p.sample) %have)
                      (~(dunk ut (~(peek ut p.gate) %free 6)) %want)
                      leaf+"ford: %slit failed:"
                      p.product
                  ==
              ==
          %&  [%build-result %success %slit p.product]
        ==
      ==
    ::
    ++  make-volt
      ~%  %make-volt  ..^^$  ~
      |=  [=disc mark=term input=*]
      ^-  build-receipt
      ::
      =/  bunt-build=^build  [date.build [%bunt disc mark]]
      ::
      =^  bunt-result  out  (depend-on bunt-build)
      ?~  bunt-result
        (return-blocks [bunt-build]~)
      ::
      ?:  ?=([~ %error *] bunt-result)
        %-  return-error
        :-  [%leaf "ford: %volt {<mark>} on {<disc>} failed:"]
        message.u.bunt-result
      ::
      ?>  ?=([~ %success %bunt *] bunt-result)
      ::
      =/  =build-result
        [%success %volt [mark p.q.cage.u.bunt-result input]]
      ::
      (return-result build-result)
    ::
    ++  make-vale
      ~%  %make-vale  ..^^$  ~
      ::  TODO: better docs
      ::
      |=  [=disc mark=term input=*]
      ^-  build-receipt
      ::  don't validate for the %noun mark
      ::
      ?:  =(%noun mark)
        =/  =build-result  [%success %vale [%noun %noun input]]
        ::
        (return-result build-result)
      ::
      =/  path-build  [date.build [%path disc %mar mark]]
      ::
      =^  path-result  out  (depend-on path-build)
      ?~  path-result
        (return-blocks [path-build]~)
      ::
      ?:  ?=([~ %error *] path-result)
        %-  return-error
        :-  leaf+"ford: %vale failed while searching for {<mark>}:"
        message.u.path-result
      ::
      ?>  ?=([~ %success %path *] path-result)
      ::
      =/  bunt-build=^build  [date.build [%bunt disc mark]]
      ::
      =^  bunt-result  out  (depend-on bunt-build)
      ?~  bunt-result
        (return-blocks [bunt-build]~)
      ::
      ?.  ?=([~ %success %bunt *] bunt-result)
        (wrap-error bunt-result)
      ::
      =/  mark-sample=vase  q.cage.u.bunt-result
      ::
      =/  call-build=^build
        :^    date.build
            %call
          ^=  gate
          :*  %ride
              ::  (ream 'noun:grab')
              formula=`hoon`[%tsld [%wing ~[%noun]] [%wing ~[%grab]]]
              subject=`schematic`[%core rail.u.path-result]
          ==
        sample=[%$ %noun %noun input]
      ::
      =^  call-result  out  (depend-on call-build)
      ?~  call-result
        (return-blocks [call-build]~)
      ::
      ?:  ?=([~ %error *] call-result)
        ::
        %-  return-error
        =/  =beam
          [[ship.disc desk.disc %da date.build] spur.rail.u.path-result]
        :*  :-  %leaf
            "ford: %vale failed: invalid input for mark: {<(en-beam beam)>}"
            message.u.call-result
        ==
      ::
      ?>  ?=([~ %success %call *] call-result)
      =/  product=vase  vase.u.call-result
      ::  +grab might produce the wrong type
      ::
      ?.  (~(nest ut p.mark-sample) | p.product)
        %-  return-error
        :~  leaf+"ford: %vale failed"
            leaf+"+grab has wrong type in mark {<mark>} on disc {<disc>}"
        ==
      ::
      =/  =build-result
        [%success %vale [mark p.mark-sample q.product]]
      ::
      (return-result build-result)
    ::
    ++  make-walk
      ~%  %make-walk  ..^^$  ~
      |=  [=disc source=term target=term]
      ^-  build-receipt
      ::  define some types used in this gate
      ::
      =>  |%
          ::  +load-node: a queued arm to run from a mark core
          ::
          +=  load-node  [type=?(%grab %grow) mark=term]
          ::  edge-jug: directed graph from :source mark to :target marks
          ::
          ::    :source can be converted to :target either by running
          ::    its own +grow arm, or by running the target's +grab arm.
          ::
          +=  edge-jug  (jug source=term [target=term arm=?(%grow %grab)])
          ::  mark-path: a path through the mark graph
          ::
          ::    +mark-path represents a series of mark translation
          ::    operations to be performed to 'walk' from one mark to another.
          ::
          ::    +mark-action is defined in Zuse. It represents a conversion
          ::    from a source mark to a target mark, and it specifies
          ::    whether it will use +grow or +grab.
          ::
          +=  mark-path  (list mark-action)
          --
      ::
      |^  ^-  build-receipt
          ?:  =(source target)
            (return-result %success %walk ~)
          ::  load all marks.
          ::
          =^  marks-result  out
            (load-marks-reachable-from [[%grow source] [%grab target] ~])
          ?~  -.marks-result
            out
          ::  find a path through the graph
          ::
          ::    Make a list of individual mark translation actions which will
          ::    take us from :source to :term.
          ::
          =/  path  (find-path-through u.-.marks-result)
          ::  if there is no path between these marks, give an error message
          ::
          ?~  path
            ::  we failed; surface errors from +load-marks-reachable-from
            ::
            =/  braces  [[' ' ' ' ~] ['{' ~] ['}' ~]]
            =/  errors=(list tank)
              %-  zing
              %+  turn  ~(tap in +.marks-result)
              |=  [mark=term err=tang]
              ^-  tang
              :~  [%leaf :(weld "while compiling " (trip mark) ":")]
                  [%rose braces err]
              ==
            ::
            %_    out
                result
              :*  %build-result  %error
                  :*  :-  %leaf
                      ;:  weld
                        "ford: no mark path from "  (trip source)  " to "
                        (trip target)
                      ==
                      errors
              ==  ==
            ==
          ::
          (return-result %success %walk path)
      ::  +load-marks-reachable-from: partial mark graph loading
      ::
      ::    While we can just load all marks in the %/mar directory, this is
      ::    rather slow. What we do instead is traverse forwards and backwards
      ::    from the source and target marks: we start at the source mark,
      ::    check all the grow arms, and then check their grow arms. At the
      ::    same time, we start from the target mark, check all the grab arms,
      ::    and then check their grab arms. This gives us a much smaller
      ::    dependency set than loading the entire %/mar directory.
      ::
      ++  load-marks-reachable-from
        |=  queued-nodes=(list load-node)
        ::  list of nodes in the graph that we've already checked
        ::
        =|  visited=(set load-node)
        ::  graph of the available edges
        ::
        =|  =edge-jug
        ::  compile-failures: mark files which didn't compile
        ::
        =|  compile-failures=(map term tang)
        ::
        |-
        ^-  [[(unit ^edge-jug) _compile-failures] _out]
        ::  no ?~ to prevent tmi
        ::
        ?:  =(~ queued-nodes)
          [[`edge-jug compile-failures] out]
        ::
        =/  nodes-and-schematics
          %+  turn  queued-nodes
          |=  =load-node
          ^-  [^load-node schematic]
          :-  load-node
          [%path disc %mar mark.load-node]
        ::  get the path for each mark name
        ::
        ::    For %path builds, any ambiguous path is just filtered out.
        ::
        =^  maybe-path-results  out
          %-  perform-schematics  :*
            ;:  weld
              "ford: %walk from "  (trip source)  " to "  (trip target)
              " contained failures:"
            ==
            nodes-and-schematics
            %filter-errors
            *load-node
          ==
        ?~  maybe-path-results
          [[~ ~] out]
        ::
        =/  nodes-and-cores
          %+  turn  u.maybe-path-results
          |=  [=load-node =build-result]
          ^-  [^load-node schematic]
          ::
          ?>  ?=([%success %path *] build-result)
          ::
          :-  load-node
          [%core rail.build-result]
        ::
        =^  maybe-core-results  out
          %-  perform-schematics  :*
            ;:  weld
              "ford: %walk from "  (trip source)  " to "  (trip target)
              " contained failures:"
            ==
            nodes-and-cores
            %ignore-errors
            *load-node
          ==
        ?~  maybe-core-results
          [[~ ~] out]
        ::  clear the queue before we process the new results
        ::
        =.  queued-nodes  ~
        ::
        =/  cores  u.maybe-core-results
        ::
        |-
        ?~  cores
          ^$
        ::  mark this node as visited
        ::
        =.  visited  (~(put in visited) key.i.cores)
        ::  add core errors to compile failures
        ::
        =?  compile-failures  ?=([%error *] result.i.cores)
          %+  ~(put by compile-failures)  mark.key.i.cores
          message.result.i.cores
        ::
        =/  target-arms=(list load-node)
          ?.  ?=([%success %core *] result.i.cores)
            ~
          ?:  =(%grow type.key.i.cores)
            (get-arms-of-type %grow vase.result.i.cores)
          (get-arms-of-type %grab vase.result.i.cores)
        ::  filter places we know we've already been.
        ::
        =.  target-arms
          %+  skip  target-arms  ~(has in visited)
        =.  queued-nodes  (weld target-arms queued-nodes)
        ::
        =.  edge-jug
          |-
          ?~  target-arms
            edge-jug
          ::
          =.  edge-jug
            ?-    type.i.target-arms
            ::
                %grab
              (~(put ju edge-jug) mark.i.target-arms [mark.key.i.cores %grab])
            ::
                %grow
              (~(put ju edge-jug) mark.key.i.cores [mark.i.target-arms %grow])
            ==
          $(target-arms t.target-arms)
        ::
        $(cores t.cores)
      ::
      ++  get-arms-of-type
        |=  [type=?(%grab %grow) =vase]
        ^-  (list load-node)
        ::  it is valid for this node to not have a +grow arm.
        ::
        ?.  (slob type p.vase)
          ~
        ::
        %+  turn
          (sloe p:(slap vase [%limb type]))
        |=  arm=term
        [type arm]
      ::  +find-path-through: breadth first search over the mark graph
      ::
      ++  find-path-through
        |=  edges=edge-jug
        ^-  mark-path
        ::  the source node starts out visited
        =/  visited-nodes=(set mark)  [source ~ ~]
        ::  these paths are flopped so we're always inserting to the front.
        =|  path-queue=(qeu mark-path)
        ::  start the queue with all the edges which start at the source mark
        ::
        =.  path-queue
          =/  start-links  (find-links-in-edges edges source)
          ::
          |-
          ^+  path-queue
          ?~  start-links
            path-queue
          ::
          =.  path-queue  (~(put to path-queue) [i.start-links]~)
          ::
          $(start-links t.start-links)
        ::
        |-
        ^-  mark-path
        ::
        ?:  =(~ path-queue)
          ::  no path found
          ~
        =^  current  path-queue  [p q]:~(get to path-queue)
        ?>  ?=(^ current)
        ::
        ?:  =(target target.i.current)
          ::  we have a completed path. paths in the queue are backwards
          (flop current)
        ::
        =+  next-steps=(find-links-in-edges edges target.i.current)
        ::  filter out already visited nodes
        ::
        =.  next-steps
          %+  skip  next-steps
          |=  link=mark-action
          (~(has in visited-nodes) source.link)
        ::  then add the new ones to the set of already visited nodes
        ::
        =.  visited-nodes
          (~(gas in visited-nodes) (turn next-steps |=(mark-action source)))
        ::  now all next steps go in the queue
        ::
        =.  path-queue
          %-  ~(gas to path-queue)
          %+  turn  next-steps
          |=  new-link=mark-action
          [new-link current]
        ::
        $
      ::  +find-links-in-edges: gets edges usable by +find-path-through
      ::
      ::    This deals with disambiguating between %grab and %grow so we always
      ::    pick %grab over %grow.
      ::
      ++  find-links-in-edges
        |=  [edges=edge-jug source=term]
        ^-  (list mark-action)
        ::
        =+  links=~(tap in (~(get ju edges) source))
        ::
        =|  results=(set mark-action)
        |-
        ^-  (list mark-action)
        ?~  links
          ~(tap in results)
        ::
        ?-    arm.i.links
            %grab
          ::  if :results has a %grow entry, remove it before adding our %grab
          =/  grow-entry=mark-action  [%grow source target.i.links]
          =?  results  (~(has in results) grow-entry)
            (~(del in results) grow-entry)
          ::
          =.  results  (~(put in results) [%grab source target.i.links])
          $(links t.links)
        ::
            %grow
          ::  if :results has a %grab entry, don't add a %grow entry
          ?:  (~(has in results) [%grab source target.i.links])
            $(links t.links)
          ::
          =.  results  (~(put in results) [%grow source target.i.links])
          $(links t.links)
        ==
      --
    ::  |utilities:make: helper arms
    ::
    ::+|  utilities
    ::
    ::  +perform-schematics: helper function that performs a list of builds
    ::
    ::    We often need to run a list of builds. This helper method will
    ::    depend on all :builds, will return a +build-receipt of either the
    ::    blocks or the first error, or a list of all completed results.
    ::
    ::    This is a wet gate so individual callers can associate their own
    ::    key types with schematics.
    ::
    ++  perform-schematics
      |*  $:  failure=tape
              builds=(list [key=* =schematic])
              on-error=?(%fail-on-errors %filter-errors %ignore-errors)
              key-bunt=*
          ==
      ^-  $:  (unit (list [key=_key-bunt result=build-result]))
              _out
          ==
      ::
      |^  =^  results  out
            =|  results=(list [_key-bunt ^build (unit build-result)])
            |-
            ^+  [results out]
            ::
            ?~  builds
              [results out]
            ::
            =/  sub-build=^build  [date.build schematic.i.builds]
            =^  result  out  (depend-on sub-build)
            =.  results  [[key.i.builds sub-build result] results]
            ::
            $(builds t.builds)
          ?:  =(%fail-on-errors on-error)
            (check-errors results)
          ?:  =(%filter-errors on-error)
            (filter-errors results)
          (handle-rest results)
      ::
      ++  check-errors
        |=  results=(list [_key-bunt ^build (unit build-result)])
        ::
        =/  braces  [[' ' ' ' ~] ['{' ~] ['}' ~]]
        =/  errors=(list tank)
          %+  murn  results
          |=  [* * result=(unit build-result)]
          ^-  (unit tank)
          ?.  ?=([~ %error *] result)
            ~
          `[%rose braces message.u.result]
        ::
        ?^  errors
          :-  ~
          %-  return-error
          :-  [%leaf failure]
          errors
        ::
        (handle-rest results)
      ::
      ++  filter-errors
        |=  results=(list [_key-bunt ^build (unit build-result)])
        =.  results
          %+  skip  results
          |=  [* * r=(unit build-result)]
          ?=([~ %error *] r)
        (handle-rest results)
      ::
      ++  handle-rest
        |=  results=(list [_key-bunt ^build (unit build-result)])
        ::  if any sub-builds blocked, produce all blocked sub-builds
        ::
        =/  blocks=(list ^build)
          %+  murn  `(list [* ^build (unit build-result)])`results
          |=  [* sub=^build result=(unit build-result)]
          ^-  (unit ^build)
          ?^  result
            ~
          `sub
        ::
        ?^  blocks
          [~ (return-blocks blocks)]
        ::
        :_  out
        :-  ~
        %+  turn  results
        |*  [key=_key-bunt ^build result=(unit build-result)]
        ^-  [_key-bunt build-result]
        [key (need result)]
      --
    ::  +wrap-error: wrap an error message around a failed sub-build
    ::
    ++  wrap-error
      |=  result=(unit build-result)
      ^-  build-receipt
      ::
      ?>  ?=([~ %error *] result)
      =/  message=tang
        [[%leaf "ford: {<-.schematic.build>} failed: "] message.u.result]
      ::
      (return-error message)
    ::  +return-blocks: exit +make as a blocked build
    ::
    ++  return-blocks
      |=  builds=(list ^build)
      ^-  build-receipt
      out(result [%blocks builds])
    ::  +return-error: exit +make with a specific failure message
    ::
    ++  return-error
      |=  =tang
      ^-  build-receipt
      out(result [%build-result %error tang])
    ::  +return-result: exit +make with a completed build
    ::
    ++  return-result
      |=  =build-result
      ^-  build-receipt
      out(result [%build-result build-result])
    ::
    ++  access-cache
      |=  =compiler-cache-key
      ^-  [(unit build-result) _out]
      ::
      ?~  entry=(~(get by lookup.compiler-cache.state) compiler-cache-key)
        [~ out(cache-access `[compiler-cache-key new=%.y])]
      ::
      [`val.u.entry out(cache-access `[compiler-cache-key new=%.n])]
    ::
    ++  depend-on
      |=  kid=^build
      ^-  [(unit build-result) _out]
      ::
      ?:  =(kid build)
        ~|  [%depend-on-self (build-to-tape kid)]
        !!
      ::
      =.  sub-builds.out  [kid sub-builds.out]
      ::  +access-build-record will mutate :results.state
      ::
      ::    It's okay to ignore this because the accessed-builds get gathered
      ::    and merged during the +reduce step.
      ::
      =/  maybe-build-record  -:(access-build-record kid)
      ?~  maybe-build-record
        [~ out]
      ::
      =*  build-record  u.maybe-build-record
      ?:  ?=(%tombstone -.build-record)
        [~ out]
      ::
      [`build-result.build-record out]
    ::  +blocked-paths-to-receipt: handle the %2 case for mock
    ::
    ::    Multiple schematics handle +toon instances. This handles the %2 case
    ::    for a +toon and transforms it into a +build-receipt so we depend on
    ::    the blocked paths correctly.
    ::
    ++  blocked-paths-to-receipt
      |=  [name=term blocked-paths=(list path)]
      ^-  build-receipt
      ::
      =/  blocks-or-failures=(list (each ^build tank))
        %+  turn  blocked-paths
        |=  =path
        ::
        =/  scry-request=(unit scry-request)  (path-to-scry-request path)
        ?~  scry-request
          [%| [%leaf "ford: {<name>}: invalid scry path: {<path>}"]]
        ::
        =*  case  r.beam.u.scry-request
        ::
        ?.  ?=(%da -.case)
          [%| [%leaf "ford: {<name>}: invalid case in scry path: {<path>}"]]
        ::
        =/  date=@da  p.case
        ::
        =/  resource=(unit resource)  (path-to-resource path)
        ?~  resource
          :-  %|
          [%leaf "ford: {<name>}: invalid resource in scry path: {<path>}"]
        ::
        =/  sub-schematic=schematic  [%pin date %scry u.resource]
        ::
        [%& `^build`[date sub-schematic]]
      ::
      =/  failed=tang
        %+  murn  blocks-or-failures
        |=  block=(each ^build tank)
        ^-  (unit tank)
        ?-  -.block
          %&  ~
          %|  `p.block
        ==
      ::
      ?^  failed
        ::  some failed
        ::
        out(result [%build-result %error failed])
      ::  no failures
      ::
      =/  blocks=(list ^build)
        %+  turn  blocks-or-failures
        |=  block=(each ^build tank)
        ?>  ?=(%& -.block)
        ::
        p.block
      ::
      =.  out
        %+  roll  blocks
        |=  [block=^build accumulator=_out]
        =.  out  accumulator
        +:(depend-on [date.block schematic.block])
      ::
      (return-blocks blocks)
    --
  ::  |utilities:per-event: helper arms
  ::
  ::+|  utilities
  ::
  ::  +got-build: lookup :build in state, asserting presence
  ::
  ++  got-build
    |=  =build
    ^-  build-status
    ~|  [%ford-missing-build build=(build-to-tape build) duct=duct]
    (~(got by builds.state) build)
  ::  +add-build: store a fresh, unstarted build in the state
  ::
  ++  add-build
    ~/  %add-build
    |=  =build
    ^+  state
    ::  don't overwrite an existing entry
    ::
    ?:  (~(has by builds.state) build)
      state
    ::
    %_    state
        builds-by-schematic
      (~(put by-schematic builds-by-schematic.state) build)
    ::
        builds
      %+  ~(put by builds.state)  build
      =|  =build-status
      build-status(state [%untried ~])
    ==
  ::  +remove-builds: remove builds and their sub-builds
  ::
  ++  remove-builds
    ~/  %remove-builds
    |=  builds=(list build)
    ::
    |^  ^+  state
        ::
        ?~  builds
          state
        ::
        ?~  maybe-build-status=(~(get by builds.state) i.builds)
          $(builds t.builds)
        =/  subs  ~(tap in ~(key by subs.u.maybe-build-status))
        ::
        =^  removed  state  (remove-single-build i.builds u.maybe-build-status)
        ?.  removed
          $(builds t.builds)
        ::
        $(builds (welp t.builds subs))
    ::  +remove-build: stop storing :build in the state
    ::
    ::    Removes all linkages to and from sub-builds
    ::
    ++  remove-single-build
      |=  [=build =build-status]
      ^+  [removed=| state]
      ::  never delete a build that something depends on
      ::
      ?^  clients.build-status
        [removed=| state]
      ?^  requesters.build-status
        [removed=| state]
      ::  nothing depends on :build, so we'll remove it
      ::
      :-  removed=&
      ::
      %_    state
          builds-by-schematic
        (~(del by-schematic builds-by-schematic.state) build)
      ::
          builds
        (~(del by builds.state) build)
      ==
    --
  ::  +update-build-status: replace :build's +build-status by running a function
  ::
  ++  update-build-status
    ~/  %update-build-status
    |=  [=build update-func=$-(build-status build-status)]
    ^-  [build-status builds=_builds.state]
    ::
    =/  original=build-status  (got-build build)
    =/  mutant=build-status  (update-func original)
    ::
    [mutant (~(put by builds.state) build mutant)]
  ::  +intercepted-scry: augment real scry with local %scry build results
  ::
  ::    Try to deduplicate requests for possibly remote resources by looking up
  ::    the result in local state if the real scry has no synchronous
  ::    answer (it produced `~`).
  ::
  ++  intercepted-scry
    %-  sloy  ^-  slyd
    ~/  %intercepted-scry
    |=  [ref=* (unit (set monk)) =term =beam]
    ^-  (unit (unit (cask milt)))
    ::  if the actual scry produces a value, use that value; otherwise use local
    ::
    =/  scry-response  (scry +<.$)
    ::
    ?^  scry-response
      scry-response
    ::
    =/  vane=(unit %c)  ((soft ,%c) (end 3 1 term))
    ?~  vane
      ~
    =/  care=(unit care:clay)  ((soft care:clay) (rsh 3 1 term))
    ?~  care
      ~
    ?.  ?=(%da -.r.beam)
      ~
    =/  =resource  [u.vane u.care rail=[[p.beam q.beam] s.beam]]
    =/  =build     [date=p.r.beam %scry resource]
    ::  look up the scry result from our permanent state
    ::
    ::    Note: we can't freshen :build's :last-accessed date because
    ::    we can't mutate :state from this gate. %scry results might get
    ::    deleted during %wipe more quickly than they should because of this.
    ::
    =/  local-result  -:(access-build-record build)
    ?~  local-result
      ~
    ?:  ?=(%tombstone -.u.local-result)
      ~
    ::
    =/  local-cage=cage  (result-to-cage build-result.u.local-result)
    ::  if :local-result does not nest in :type, produce an error
    ::
    ?.  -:(nets:wa +.ref `type`p.q.local-cage)
      [~ ~]
    ::
    [~ ~ local-cage]
  ::  +unblock-clients-on-duct: unblock and produce clients blocked on :build
  ::
  ++  unblock-clients-on-duct
    =|  unblocked=(list build)
    ~%  %unblock-clients-on-duct  +>+  ~
    |=  =build
    ^+  [unblocked builds.state]
    ::
    =/  =build-status  (got-build build)
    ::
    =/  clients=(list ^build)  ~(tap in (~(get ju clients.build-status) [%duct duct]))
    ::
    |-
    ^+  [unblocked builds.state]
    ?~  clients
      [unblocked builds.state]
    ::
    =^  client-status  builds.state
      %+  update-build-status  i.clients
      |=  client-status=^build-status
      ::
      =.  subs.client-status
        %+  ~(jab by subs.client-status)  build
        |=  original=build-relation
        original(blocked |)
      ::
      =?    state.client-status
          ?&  ?=(%blocked -.state.client-status)
          ::
              ?!
              %-  ~(any by subs.client-status)
              |=(build-relation &(blocked verified))
          ==
        ::
        [%unblocked ~]
      client-status
    ::
    =?  unblocked  !?=(%blocked -.state.client-status)
      [i.clients unblocked]
    ::
    $(clients t.clients)
  ::  +on-build-complete: handles completion of any build
  ::
  ++  on-build-complete
    ~/  %on-build-complete
    |=  =build
    ^+  ..execute
    ::
    =.  ..execute  (cleanup-orphaned-provisional-builds build)
    ::
    =/  duct-status  (~(got by ducts.state) duct)
    ::
    =/  =build-status  (got-build build)
    ?:  (~(has in requesters.build-status) [%duct duct])
      (on-root-build-complete build)
    ::
    =^  unblocked-clients  builds.state  (unblock-clients-on-duct build)
    =.  candidate-builds  (~(gas in candidate-builds) unblocked-clients)
    ::
    ..execute
  ::  +on-root-build-complete: handle completion or promotion of a root build
  ::
  ::    When a build completes for a duct, we might have to send a %made move
  ::    on the requesting duct and also do duct and build book-keeping.
  ::
  ++  on-root-build-complete
    ~/  %on-root-build-complete
    |=  =build
    ^+  ..execute
    ::
    =;  res=_..execute
        =/  duct-status=(unit duct-status)
          (~(get by ducts.state.res) duct)
        ?~  duct-status  res
        ::  debugging assertions to try to track down failure in
        ::  +copy-build-tree-as-provisional
        ::
        ~|  [%failed-to-preserve-live-build (build-to-tape build)]
        ?>  ?=(%live -.live.u.duct-status)
        ~|  %failed-2
        ?>  ?=(^ last-sent.live.u.duct-status)
        ~|  %failed-3
        ?>  .=  build
            [date.u.last-sent.live.u.duct-status root-schematic.u.duct-status]
        ~|  %failed-4
        ?>  (~(has by builds.state.res) build)
        ::
        res
    ::
    =/  =build-status  (got-build build)
    =/  =duct-status  (~(got by ducts.state) duct)
    ::  make sure we have something to send
    ::
    ?>  ?=([%complete %value *] state.build-status)
    ::  send a %made move unless it's an unchanged live build
    ::
    =?    moves
        ?!
        ?&  ?=(%live -.live.duct-status)
            ?=(^ last-sent.live.duct-status)
            ::
            =/  last-build-status
              %-  ~(got by builds.state)
              [date.u.last-sent.live.duct-status schematic.build]
            ::
            ?>  ?=(%complete -.state.last-build-status)
            ?&  ?=(%value -.build-record.state.last-build-status)
            ::
                .=  build-result.build-record.state.last-build-status
                    build-result.build-record.state.build-status
        ==  ==
      :_  moves
      ^-  move
      ::
      :*  duct  %give  %made  date.build  %complete
          build-result.build-record.state.build-status
      ==
    ::
    ?-    -.live.duct-status
        %once
      =.  ducts.state  (~(del by ducts.state) duct)
      =.  state  (move-root-to-cache build)
      ::
      ..execute
    ::
        %live
      ::  clean up previous build
      ::
      =?  state  ?=(^ last-sent.live.duct-status)
        =/  old-build=^build  build(date date.u.last-sent.live.duct-status)
        ~?  =(date.build date.old-build)
          :+  "old and new builds have same date, will probably crash!"
            (build-to-tape build)
          (build-to-tape old-build)
        ::
        (remove-anchor-from-root old-build [%duct duct])
      ::
      =/  resource-list=(list [=disc resources=(set resource)])
        ~(tap by (collect-live-resources build))
      ::  we can only handle a single subscription
      ::
      ::    In the long term, we need Clay's interface to change so we can
      ::    subscribe to multiple desks at the same time.
      ::
      ?:  (lth 1 (lent resource-list))
        =.  ..execute
          %+  send-incomplete  build  :~
            [%leaf "root build {(build-to-tape build)}"]
            [%leaf "on duct:"]
            [%leaf "{<duct>}"]
            [%leaf "tried to subscribe to multiple discs:"]
            [%leaf "{<resource-list>}"]
          ==
        ::  delete this instead of caching it, since it wasn't right
        ::
        =.  ducts.state  (~(del by ducts.state) duct)
        =.  state  (remove-anchor-from-root build [%duct duct])
        ..execute
      ::
      =/  subscription=(unit subscription)
        ?~  resource-list
          ~
        `[date.build disc.i.resource-list resources.i.resource-list]
      ::
      =?  ..execute  ?=(^ subscription)
        (start-clay-subscription u.subscription)
      ::
      =.  ducts.state
        %+  ~(put by ducts.state)  duct
        %_    duct-status
            live
          [%live in-progress=~ last-sent=`[date.build subscription]]
        ==
      ::
      ..execute
    ==
  ::  +send-incomplete: emit a move indicating we can't complete :build
  ::
  ++  send-incomplete
    |=  [=build message=tang]
    ^+  ..execute
    ::
    =.  moves
      :_  moves
      `move`[duct %give %made date.build %incomplete message]
    ::
    ..execute
  ::  +cleanup-orphaned-provisional-builds: delete extraneous sub-builds
  ::
  ::    Remove unverified linkages to sub builds. If a sub-build has no other
  ::    clients on this duct, then it is orphaned and we remove the duct from
  ::    its subs and call +cleanup on it.
  ::
  ++  cleanup-orphaned-provisional-builds
    ~/  %cleanup-orphaned-provisional-builds
    |=  =build
    ^+  ..execute
    ::
    =/  =build-status  (got-build build)
    ::
    =/  orphans=(list ^build)
      %+  murn  ~(tap by subs.build-status)
      |=  [sub=^build =build-relation]
      ^-  (unit ^build)
      ::
      ?:  verified.build-relation
        ~
      `sub
    ::  dequeue orphans in case we were about to run them
    ::
    =/  orphan-set        (~(gas in *(set ^build)) orphans)
    =.  next-builds       (~(dif in next-builds) orphan-set)
    =.  candidate-builds  (~(dif in candidate-builds) orphan-set)
    ::  remove links to orphans in :build's +build-status
    ::
    =^  build-status  builds.state
      %+  update-build-status  build
      |=  build-status=^build-status
      %_    build-status
          subs
        ::
        |-  ^+  subs.build-status
        ?~  orphans  subs.build-status
        ::
        =.  subs.build-status  (~(del by subs.build-status) i.orphans)
        ::
        $(orphans t.orphans)
      ==
    ::
    =/  =anchor  [%duct duct]
    ::
    |-  ^+  ..execute
    ?~  orphans  ..execute
    ::  remove link to :build in :i.orphan's +build-status
    ::
    =^  orphan-status  builds.state
      %+  update-build-status  i.orphans
      |=  orphan-status=_build-status
      %_  orphan-status
        clients  (~(del ju clients.orphan-status) anchor build)
      ==
    ::
    ?:  (~(has by clients.orphan-status) anchor)
      $(orphans t.orphans)
    ::  :build was the last client on this duct so remove it
    ::
    =.  builds.state  (remove-anchor-from-subs i.orphans anchor)
    =.  state  (cleanup i.orphans)
    $(orphans t.orphans)
  ::  +access-build-record: access a +build-record, updating :last-accessed
  ::
  ::    Usage:
  ::    ```
  ::    =^  maybe-build-record  builds.state  (access-build-record build)
  ::    ```
  ::
  ++  access-build-record
    ~/  %access-build-record
    |=  =build
    ^-  [(unit build-record) _builds.state]
    ::
    ?~  maybe-build-status=(~(get by builds.state) build)
      [~ builds.state]
    ::
    =/  =build-status  u.maybe-build-status
    ::
    ?.  ?=(%complete -.state.build-status)
      [~ builds.state]
    ::
    ?:  ?=(%tombstone -.build-record.state.build-status)
      [`build-record.state.build-status builds.state]
    ::
    =.  last-accessed.build-record.state.build-status  now
    ::
    :-  `build-record.state.build-status
    (~(put by builds.state) build build-status)
  ::  +cleanup: try to clean up a build and its sub-builds
  ::
  ++  cleanup
    ~/  %cleanup
    |=  =build
    ^+  state
    ::   does this build even exist?!
    ::
    ?~  maybe-build-status=(~(get by builds.state) build)
      state
    ::
    =/  =build-status  u.maybe-build-status
    ::  never delete a build that something depends on
    ::
    ?^  clients.build-status
      state
    ?^  requesters.build-status
      state
    ::
    (remove-builds ~[build])
  ::  +collect-live-resources: produces all live resources from sub-scrys
  ::
  ++  collect-live-resources
    ~/  %collect-live-resources
    |=  =build
    ^-  (jug disc resource)
    ::
    ?:  ?=(%scry -.schematic.build)
      =*  resource  resource.schematic.build
      (my [(extract-disc resource) (sy [resource]~)]~)
    ::
    ?:  ?=(%pin -.schematic.build)
      ~
    ::
    =/  subs  ~(tap in ~(key by subs:(got-build build)))
    =|  resources=(jug disc resource)
    |-
    ?~  subs
      resources
    ::
    =/  sub-resources=(jug disc resource)  ^$(build i.subs)
    =.  resources  (unify-jugs resources sub-resources)
    $(subs t.subs)
  ::  +collect-blocked-resources: produces all blocked resources from sub-scrys
  ::
  ++  collect-blocked-sub-scrys
    ~/  %collect-blocked-sub-scrys
    |=  =build
    ^-  (set scry-request)
    ::
    ?:  ?=(%scry -.schematic.build)
      =,  resource.schematic.build
      =/  =scry-request
        :+  vane  care
        ^-  beam
        [[ship.disc.rail desk.disc.rail [%da date.build]] spur.rail]
      (sy [scry-request ~])
    ::  only recurse on blocked sub-builds
    ::
    =/  subs=(list ^build)
      %+  murn  ~(tap by subs:(got-build build))
      |=  [sub=^build =build-relation]
      ^-  (unit ^build)
      ::
      ?.  blocked.build-relation
        ~
      `sub
    ::
    =|  scrys=(set scry-request)
    |-
    ^+  scrys
    ?~  subs
      scrys
    ::
    =.  scrys  (~(uni in scrys) ^$(build i.subs))
    $(subs t.subs)
  ::  +start-clay-subscription: listen for changes in the filesystem
  ::
  ++  start-clay-subscription
    ~/  %start-clay-subscription
    |=  =subscription
    ^+  ..execute
    ::
    =/  already-subscribed=?
      (~(has by pending-subscriptions.state) subscription)
    ::
    =.  pending-subscriptions.state
      (put-request pending-subscriptions.state subscription duct)
    ::  don't send a duplicate move if we're already subscribed
    ::
    ?:  already-subscribed
      ..execute
    ::
    =/  =wire  (clay-subscription-wire [date disc]:subscription)
    ::
    =/  =note
      ::  request-contents: the set of [care path]s to subscribe to in clay
      ::
      =/  request-contents=(set [care:clay path])
        %-  sy  ^-  (list [care:clay path])
        %+  murn  ~(tap in `(set resource)`resources.subscription)
        |=  =resource  ^-  (unit [care:clay path])
        ::
        `[care.resource (flop spur.rail.resource)]
      ::  if :request-contents is `~`, this code is incorrect
      ::
      ?<  ?=(~ request-contents)
      ::  their: requestee +ship
      ::
      =+  [their desk]=disc.subscription
      ::
      :^  %c  %warp  ship=their
      ^-  riff:clay
      [desk `[%mult `case`[%da date.subscription] request-contents]]
    ::
    =.  moves  [`move`[duct [%pass wire note]] moves]
    ::
    ..execute
  ::  +cancel-clay-subscription: remove a subscription on :duct
  ::
  ++  cancel-clay-subscription
    ~/  %cancel-clay-subscription
    |=  =subscription
    ^+  ..execute
    ::
    =^  originator  pending-subscriptions.state
      (del-request pending-subscriptions.state subscription duct)
    ::  if there are still other ducts on this subscription, don't send a move
    ::
    ?~  originator
      ..execute
    ::
    =/  =wire  (clay-subscription-wire [date disc]:subscription)
    ::
    =/  =note
      =+  [their desk]=disc.subscription
      [%c %warp ship=their `riff:clay`[desk ~]]
    ::
    =.  moves  [`move`[u.originator [%pass wire note]] moves]
    ::
    ..execute
  ::  +clay-sub-wire: the wire to use for a clay subscription
  ::
  ::    While it is possible for two different root builds to make
  ::    subscriptions with the same wire, those wires will always be associated
  ::    with different ducts, so there's no risk of duplicates.
  ::
  ++  clay-subscription-wire
    |=  [date=@da =disc]
    ^-  wire
    ::
    =+  [their desk]=disc
    ::
    /clay-sub/(scot %p their)/[desk]/(scot %da date)
  ::  +start-scry-request: kick off an asynchronous request for a resource
  ::
  ++  start-scry-request
    |=  =scry-request
    ^+  ..execute
    ::  if we are the first block depending on this scry, send a move
    ::
    =/  already-started=?  (~(has by pending-scrys.state) scry-request)
    ::
    =.  pending-scrys.state
      (put-request pending-scrys.state scry-request duct)
    ::  don't send a duplicate move if we've already sent one
    ::
    ?:  already-started
      ..execute
    ::
    =/  =wire  (scry-request-wire scry-request)
    ::
    =/  =note
      =,  scry-request
      =/  =disc  [p q]:beam
      :*  %c  %warp  their=ship.disc  desk.disc
          `[%sing care case=r.beam (flop s.beam)]
      ==
    ::
    =.  moves  [`move`[duct [%pass wire note]] moves]
    ::
    ..execute
  ::  +cancel-scry-request: cancel a pending asynchronous scry request
  ::
  ++  cancel-scry-request
    |=  =scry-request
    ^+  ..execute
    ::
    =^  originator  pending-scrys.state
      (del-request pending-scrys.state scry-request duct)
    ::  if there are still other ducts on this subscription, don't send a move
    ::
    ?~  originator
      ..execute
    ::
    =/  =wire  (scry-request-wire scry-request)
    ::
    =/  =note
      =+  [their desk]=[p q]:beam.scry-request
      [%c %warp ship=their `riff:clay`[desk ~]]
    ::
    =.  moves  [`move`[u.originator [%pass wire note]] moves]
    ::
    ..execute
  ::  +scry-request-wire
  ::
  ++  scry-request-wire
    |=  =scry-request
    ^-  wire
    (welp /scry-request (scry-request-to-path scry-request))
  --
--
::
::  end the =~
::
.  ==
::
::::  vane interface
  ::
::  begin with a default +axle as a blank slate
::
=|  ax=axle
::  a vane is activated with identity, the current date, entropy,
::  and a namespace function
::
|=  [our=ship now=@da eny=@uvJ scry-gate=sley]
=*  ford-gate  .
::  allow jets to be registered within this core
::
~%  %ford  ..is  ~
|%
::  +call: handle a +task:able from arvo
::
::    Ford can be tasked with:
::
::      %build: perform a build
::      %keep: resize caches
::      %kill: cancel a build
::      %wipe: clear memory
::
::    Most requests get converted into operations to be performed inside
::    the +per-event core, which is Ford's main build engine.
::
++  call
  |=  [=duct type=* wrapped-task=(hobo task:able)]
  ^-  [(list move) _ford-gate]
  ::
  =/  task=task:able  ((harden task:able) wrapped-task)
  ::  we wrap +per-event with a call that binds our event args
  ::
  =*  this-event  (per-event [our duct now scry-gate] state.ax)
  ::
  ?-    -.task
      ::  %build: request to perform a build
      ::
      %build
    ::  perform the build indicated by :task
    ::
    ::    We call :start-build on :this-event, which is the |per-event core
    ::    with the our event-args already bound. :start-build performs the
    ::    build and produces a pair of :moves and a mutant :state.
    ::    We update our :state and produce it along with :moves.
    ::
    =/  =build  [now schematic.task]
    =^  moves  state.ax  (start-build:this-event build live.task)
    ::
    [moves ford-gate]
  ::
      ::  %keep: keep :count cache entries
      ::
      %keep
    ::
    =.  state.ax  (keep:this-event [compiler-cache build-cache]:task)
    ::
    [~ ford-gate]
  ::
      ::  %kill: cancel a %build
      ::
      %kill
    ::
    =^  moves  state.ax  cancel:this-event
    ::
    [moves ford-gate]
  ::
      ::  %trim: in response to memory pressure
      ::
      %trim
    ::
    ?.  =(0 p.task)
      ::  low-priority: remove 50% of cache/stored-builds
      ::
      ~>  %slog.[0 leaf+"ford: trim: pruning caches"]
      =.  state.ax  (wipe:this-event 50)
      [~ ford-gate]
    ::
    ::  high-priority: remove 100% of cache/stored-builds
    ::
    ::    We use %keep to ensure that cache-keys are also purged,
    ::    then restore original limits to allow future caching.
    ::
    ::    XX cancel in-progress builds?
    ::
    ~>  %slog.[0 leaf+"ford: trim: clearing caches"]
    =/  b-max  max-size.queue.build-cache.state.ax
    =/  c-max  max-size.compiler-cache.state.ax
    =.  state.ax  (keep:this-event 0 0)
    =.  state.ax  (keep:this-event c-max b-max)
    [~ ford-gate]
  ::
      ::  %vega: learn of kernel upgrade
      ::
      ::    XX clear cache, rebuild live builds
      ::
      %vega
    ::
    [~ ford-gate]
  ::
      ::  %wipe: wipe stored builds, clearing :percent-to-remove of the entries
      ::
      %wipe
    ::
    =.  state.ax  (wipe:this-event percent-to-remove.task)
    ::
    [~ ford-gate]
  ::
      %wegh
    :_  ford-gate
    :_  ~
    :^  duct  %give  %mass
    ^-  mass
    :+  %ford  %|
    :~  builds+&+builds.state.ax
        compiler-cache+&+compiler-cache.state.ax
        dot+&+ax
    ==
  ==
::  +take: receive a response from another vane
::
::    A +take is a response to a request that Ford made of another vane.
::
::    Ford decodes the type of response based on the +wire in the +take.
::    The possibilities are:
::
::      %clay-sub: Clay notification of an update to a subscription
::
::        If Ford receives this, it will rebuild one or more live builds,
::        taking into account the new date and changed resources.
::
::      %scry-request: Clay response to a request for a resource
::
::        If Ford receives this, it will continue building one or more builds
::        that were blocked on this resource.
::
::    The +sign gets converted into operations to be performed inside
::    the +per-event core, which is Ford's main build engine.
::
++  take
  |=  [=wire =duct wrapped-sign=(hypo sign)]
  ^-  [(list move) _ford-gate]
  ::  unwrap :sign, ignoring unneeded +type in :p.wrapped-sign
  ::
  =/  =sign  q.wrapped-sign
  ::  :wire must at least contain a tag for dispatching
  ::
  ?>  ?=([@ *] wire)
  ::
  |^  ^-  [(list move) _ford-gate]
      ::
      =^  moves  state.ax
        ?+  i.wire     ~|([%bad-take-wire wire] !!)
          %clay-sub      take-rebuilds
          %scry-request  take-unblocks
        ==
      ::
      [moves ford-gate]
    ::  +take-rebuilds: rebuild all live builds affected by the Clay changes
    ::
    ++  take-rebuilds
      ^-  [(list move) ford-state]
      ::
      ~|  [%ford-take-rebuilds wire=wire duct=duct]
      ?>  ?=([@tas %wris *] sign)
      =*  case-sign  p.sign
      =*  care-paths-sign  q.sign
      =+  [ship desk date]=(raid:wired t.wire ~[%p %tas %da])
      =/  disc  [ship desk]
      ::  ignore spurious clay updates
      ::
      ::    Due to asynchronicity of Clay notifications, we might get a
      ::    subscription update on an already-canceled duct.  This is
      ::    normal; no-op.
      ::
      ?~  duct-status=(~(get by ducts.state.ax) duct)
        [~ state.ax]
      ::
      =/  =subscription
        ?>  ?=(%live -.live.u.duct-status)
        (need subscription:(need last-sent.live.u.duct-status))
      ::
      =/  ducts=(list ^duct)
        ::  sanity check; there must be at least one duct per subscription
        ::
        =-  ?<(=(~ -) -)
        (get-request-ducts pending-subscriptions.state.ax subscription)
      ::
      =|  moves=(list move)
      |-  ^+  [moves state.ax]
      ?~  ducts  [moves state.ax]
      ::
      =*  event-args  [[our i.ducts now scry-gate] state.ax]
      =*  rebuild  rebuild:(per-event event-args)
      =^  duct-moves  state.ax
        (rebuild subscription p.case-sign disc care-paths-sign)
      ::
      $(ducts t.ducts, moves (weld moves duct-moves))
    ::  +take-unblocks: unblock all builds waiting on this scry request
    ::
    ++  take-unblocks
      ^-  [(list move) ford-state]
      ::
      ~|  [%ford-take-unblocks wire=wire duct=duct]
      ?>  ?=([@tas %writ *] sign)
      =*  riot-sign  p.sign
      ::  scry-request: the +scry-request we had previously blocked on
      ::
      =/  =scry-request  (need (path-to-scry-request t.wire))
      ::  scry-result: parse a (unit cage) from :sign
      ::
      ::    If the result is `~`, the requested resource was not available.
      ::
      =/  scry-result=(unit cage)
        ?~  riot-sign
          ~
        `r.u.riot-sign
      ::  if spurious Clay response, :ducts will be empty, causing no-op
      ::
      =/  ducts=(list ^duct)
        (get-request-ducts pending-scrys.state.ax scry-request)
      ::
      =|  moves=(list move)
      |-  ^+  [moves state.ax]
      ?~  ducts  [moves state.ax]
      ::
      =*  event-args  [[our i.ducts now scry-gate] state.ax]
      ::  unblock the builds that had blocked on :resource
      ::
      =*  unblock  unblock:(per-event event-args)
      =^  duct-moves  state.ax  (unblock scry-request scry-result)
      ::
      $(ducts t.ducts, moves (weld moves duct-moves))
  --
::  +load: migrate old state to new state (called on vane reload)
::
::    Trim builds completely in case a change to our code invalidated an
::    old build result.
::
++  load
  |=  old=axle
  ^+  ford-gate
  ::
  =.  ax  old
  =.  ford-gate  +:(call ~[/ford-load-self] *type %trim 0)
  ford-gate
::  +stay: produce current state
::
++  stay  `axle`ax
::  +scry: request a path in the urbit namespace
::
++  scry
  |=  *
  [~ ~]
--