#!/usr/bin/env bash # next/tests/pipeline_signature.sh — Step 6b-sig acceptance test. # # Exercises pipeline:stage_signature/2 (direct) and stage_signature/1 # (factory). The factory returns a 1-arity stage fun bound to the # given actor-state so it can be folded into a stage list by the # pipeline driver alongside stage_envelope. 10 cases. set -uo pipefail cd "$(git rev-parse --show-toplevel)" SX_SERVER="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}" if [ ! -x "$SX_SERVER" ]; then SX_SERVER="/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe" fi if [ ! -x "$SX_SERVER" ]; then echo "ERROR: sx_server.exe not found." >&2 exit 1 fi VERBOSE="${1:-}" PASS=0; FAIL=0; ERRORS="" TMPFILE=$(mktemp); trap "rm -f $TMPFILE" EXIT # Shared Erlang prelude builds a valid signed envelope + actor # state — same shape as next/tests/envelope_sig.sh from Step 2c. PRELUDE='KM = <<1,2,3,4>>, U = [{actor,alice},{id,1},{published,100},{type,create}], CB = envelope:canonical_bytes(U), Sig = crypto:hash(sha256, <>), Env = [{actor,alice},{id,1},{published,100},{type,create},{signature,[{algorithm,ed25519},{key_id,k1},{value,Sig}]}], AS = [{public_keys, [[{id,k1},{created,50},{value,KM}]]}],' cat > "$TMPFILE" < no_signature (epoch 12) (eval "(get (erlang-eval-ast \"${PRELUDE} pipeline:stage_signature(U, AS) =:= {error,no_signature}\") :name)") ;; stage_signature/1 returns a function (epoch 13) (eval "(get (erlang-eval-ast \"is_function(pipeline:stage_signature([{public_keys, []}]))\") :name)") ;; stage_signature/1 factory: built stage returns ok on valid input (epoch 14) (eval "(get (erlang-eval-ast \"${PRELUDE} Stage = pipeline:stage_signature(AS), Stage(Env) =:= ok\") :name)") ;; stage_signature/1 factory: built stage returns error on tampered input (epoch 15) (eval "(get (erlang-eval-ast \"${PRELUDE} Stage = pipeline:stage_signature(AS), Tampered = [{actor,alice},{id,999},{published,100},{type,create},{signature,[{algorithm,ed25519},{key_id,k1},{value,Sig}]}], Stage(Tampered) =:= {error,bad_signature}\") :name)") ;; Composable: envelope + signature stages folded together via run_stages (epoch 16) (eval "(get (erlang-eval-ast \"${PRELUDE} Stages = [fun (A) -> pipeline:stage_envelope(A) end, pipeline:stage_signature(AS)], pipeline:run_stages(Env, Stages) =:= ok\") :name)") ;; Composable + halt: envelope stage fails first, signature never runs (epoch 17) (eval "(get (erlang-eval-ast \"${PRELUDE} BadShape = [{type,create}], Stages = [fun (A) -> pipeline:stage_envelope(A) end, pipeline:stage_signature(AS)], case pipeline:run_stages(BadShape, Stages) of {error, {missing_field, _}} -> ok; _ -> bad end\") :name)") ;; Composable + halt: envelope OK, signature fails -> sig error surfaces (epoch 18) (eval "(get (erlang-eval-ast \"${PRELUDE} Tampered = [{actor,alice},{id,999},{published,100},{type,create},{signature,[{algorithm,ed25519},{key_id,k1},{value,Sig}]}], Stages = [fun (A) -> pipeline:stage_envelope(A) end, pipeline:stage_signature(AS)], pipeline:run_stages(Tampered, Stages) =:= {error,bad_signature}\") :name)") EPOCHS OUTPUT=$(timeout 180 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) check() { local epoch="$1" desc="$2" expected="$3" local actual actual=$(echo "$OUTPUT" | awk -v e="$epoch" ' $0 ~ "^\\(ok-len " e " " { getline; print; exit } $0 ~ "^\\(ok " e " " { print; exit } $0 ~ "^\\(error " e " " { print; exit } ') [ -z "$actual" ] && actual="" if echo "$actual" | grep -qF -- "$expected"; then PASS=$((PASS+1)) [ "$VERBOSE" = "-v" ] && echo " ok $desc" else FAIL=$((FAIL+1)) ERRORS+=" FAIL [$desc] (epoch $epoch) expected: $expected | actual: $actual " fi } check 2 "envelope module loaded" "envelope" check 3 "pipeline module loaded" "pipeline" check 10 "stage_signature/2 valid -> ok" "true" check 11 "stage_signature/2 tampered" "true" check 12 "stage_signature/2 no sig" "true" check 13 "stage_signature/1 returns fun" "true" check 14 "factory stage valid -> ok" "true" check 15 "factory stage tampered" "true" check 16 "envelope+sig composed ok" "true" check 17 "halt on envelope before sig" "ok" check 18 "sig error after envelope ok" "true" TOTAL=$((PASS+FAIL)) if [ $FAIL -eq 0 ]; then echo "ok $PASS/$TOTAL next/tests/pipeline_signature.sh passed" else echo "FAIL $PASS/$TOTAL passed, $FAIL failed:" echo "$ERRORS" fi [ $FAIL -eq 0 ]