tricu

A lazy functional language based on Tree Calculus
Log | Files | Refs | README | LICENSE

artifacts.tri (4987B)


      1 !import base as B
      2 !import native_math as N
      3 !import http as HTTP
      4 !import io as IO
      5 !import io_storage as Storage
      6 !import handlers as H
      7 !import arboricx_server as Store
      8 !export write
      9 !export publish
     10 !export publishWith
     11 
     12 -- Source-owned upload policy. Only writes observed by this boundary advance
     13 -- its byte count; producer acknowledgements report producer status.
     14 write bytes = H.request "artifact-stream" "write" bytes
     15 onOk action next = H.bind (H.liftIO action) (reply : case reply of
     16   ((t t), (value, t)) = next value
     17   (t, (_, t)) = H.pure reply
     18   _ = H.fail "invalid-response" "artifact-stream" "host" "Invalid host Result")
     19 
     20 hashReader reader digest remaining = IO.onOk (IO.readSome reader 4096) (reply : case reply of
     21   ("eof", t) = case remaining of
     22     0 = IO.finishDigest digest
     23     _ = IO.pure (B.err "Existing artifact length mismatch" t)
     24   ("data", bytes) = let count = HTTP.httpBodyLength bytes in case N.lte count remaining of
     25     (t t) = IO.onOk (IO.updateDigest digest bytes)
     26       (_ : hashReader reader digest (N.subtract remaining count))
     27     _ = IO.pure (B.err "Existing artifact length mismatch" t)
     28   _ = IO.fail "Invalid read reply")
     29 
     30 -- Accept an existing name only after verifying its complete contents and length
     31 -- independently with fresh reader/digest resources.
     32 verifyWinner destination expected wanted = IO.scope (owner : do IO.onOk
     33   reader <- IO.openReader owner destination
     34   digest <- IO.openDigest owner "sha256"
     35   actual <- hashReader reader digest expected
     36   case B.equal? actual wanted of
     37     (t t) = do IO.onOk
     38       IO.syncFile reader
     39       IO.syncDirectory (Storage.parentPath destination)
     40       IO.pure (B.ok t t)
     41     _ = IO.pure (B.err "Existing artifact digest mismatch" t))
     42 
     43 -- Destination policy runs only after exact-length completion and sealing. It
     44 -- returns a handler Result path and can create a digest-derived shard. Native
     45 -- publication remains generic and rejects cross-filesystem moves without copying.
     46 finish destination expected stage digest = onOk (IO.finishDigest digest) (hash :
     47   case B.checkContract Store.bundleHashC hash of
     48     ((t t), (checked, t)) = onOk (IO.sealStage stage) (_ :
     49       H.bind (destination checked) (reply : case reply of
     50         ((t t), (path, t)) = onOk (IO.publishNew stage path) (outcome : case outcome of
     51           "published" = H.pure (B.ok checked t)
     52           "exists" = onOk (verifyWinner path expected checked) (_ : H.pure (B.ok checked t))
     53           _ = H.fail "invalid-response" "artifact-stream" "publish" "Invalid publication reply")
     54         (t, (_, t)) = H.pure reply
     55         _ = H.fail "invalid-response" "artifact-stream" "destination" "Expected destination Result"))
     56     _ = H.fail "invalid-response" "artifact-stream" "digest" "Invalid digest reply")
     57 
     58 publish directory expected producer = publishWith directory expected
     59   (digest : H.pure (B.ok (B.append directory (B.pair 47 digest)) t)) producer
     60 publishWith directory expected destination producer = case B.checkContract B.nat? expected of
     61   ((t t), (count, t)) = case N.lte count 67108864 of
     62     (t t) = H.scope (owner : onOk (IO.openStage owner directory) (stage :
     63       onOk (IO.openDigest owner "sha256") (digest : let rec
     64         walk current remaining = case current of
     65           (100, reply) = case reply of
     66             ((t t), (t, t)) = case remaining of
     67               0 = finish destination count stage digest
     68               _ = H.pure (B.err "Incomplete artifact stream" t)
     69             (t, (_, t)) = H.pure reply
     70             _ = H.fail "invalid-response" "artifact-stream" "producer" "Expected Result unit"
     71           (103, [kind service operation detail labels]) = B.pair 105
     72             (B.pair labels (H.fail kind service operation detail))
     73           (106, [["artifact-stream" "write" bytes] labels next]) =
     74             onOk (IO.writeAll stage bytes) (_ : case bytes of
     75               [] = H.pure (B.err "Empty artifact chunk" t)
     76               _ = let length = HTTP.httpBodyLength bytes in case N.lte length remaining of
     77                 (t t) = onOk (IO.updateDigest digest bytes)
     78                   (_ : walk (next (B.ok t t)) (N.subtract remaining length))
     79                 _ = H.pure (B.err "Artifact exceeds declared length" t))
     80           (106, [[service operation argument] labels next]) = H.bind
     81             (B.pair 105 (B.pair labels (H.request service operation argument)))
     82             (reply : walk (next reply) remaining)
     83           (107, [labels body next]) = B.pair 105 (B.pair labels
     84             (H.fail "producer-boundary" "artifact-stream" "publish" "Producer must be sequential"))
     85           (108, [supervised keep handles labels body next]) = B.pair 105 (B.pair labels
     86             (H.fail "producer-boundary" "artifact-stream" "publish" "Producer must be sequential"))
     87           _ = H.fail "invalid-view" "artifact-stream" "publish" "Invalid producer view"
     88         in walk (H.view producer) count)))
     89     _ = H.pure (B.err "Artifact length exceeds limit" t)
     90   _ = H.pure (B.err "Invalid artifact length" t)