artifacts.tri (4995B)
1 !import base as B 2 !import native_math as N 3 !import http as HTTP 4 !import io_v2 as IO 5 !import io_v2_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.scopeV2 (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)