#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" HTTP_HELPER="${ROOT_DIR}/build/amduatd_http_unix" USE_HTTP_HELPER=0 TMPDIR="${TMPDIR:-/tmp}" mkdir -p "${TMPDIR}" if ! command -v grep >/dev/null 2>&1; then echo "skip: grep not found" >&2 exit 77 fi if ! command -v awk >/dev/null 2>&1; then echo "skip: awk not found" >&2 exit 77 fi if command -v curl >/dev/null 2>&1; then if curl --help 2>/dev/null | grep -q -- '--unix-socket'; then USE_HTTP_HELPER=0 else USE_HTTP_HELPER=1 fi else USE_HTTP_HELPER=1 fi if [[ "${USE_HTTP_HELPER}" -eq 1 && ! -x "${HTTP_HELPER}" ]]; then echo "skip: curl lacks --unix-socket support and helper missing" >&2 exit 77 fi AMDUATD_BIN="${ROOT_DIR}/build/amduatd" ASL_BIN="${ROOT_DIR}/vendor/amduat/build/amduat-asl" if [[ ! -x "${AMDUATD_BIN}" || ! -x "${ASL_BIN}" ]]; then echo "missing binaries; build amduatd and amduat-asl first" >&2 exit 1 fi tmp_root="$(mktemp -d -p "${TMPDIR}" amduatd-fed-smoke-XXXXXX)" root_a="${tmp_root}/a" root_b="${tmp_root}/b" sock_a="${tmp_root}/amduatd-a.sock" sock_b="${tmp_root}/amduatd-b.sock" space_id="smoke" log_a="${tmp_root}/amduatd-a.log" log_b="${tmp_root}/amduatd-b.log" cleanup() { if [[ -n "${pid_a:-}" ]]; then kill "${pid_a}" >/dev/null 2>&1 || true fi if [[ -n "${pid_b:-}" ]]; then kill "${pid_b}" >/dev/null 2>&1 || true fi rm -rf "${tmp_root}" } trap cleanup EXIT mkdir -p "${root_a}" "${root_b}" "${ASL_BIN}" init --root "${root_a}" "${ASL_BIN}" init --root "${root_b}" "${AMDUATD_BIN}" --root "${root_a}" --sock "${sock_a}" \ --store-backend index --space "${space_id}" \ --fed-enable --fed-transport unix \ --fed-unix-sock "${sock_b}" --fed-domain-id 1 \ >"${log_a}" 2>&1 & pid_a=$! "${AMDUATD_BIN}" --root "${root_b}" --sock "${sock_b}" \ --store-backend index --space "${space_id}" \ --fed-enable --fed-transport unix \ --fed-unix-sock "${sock_a}" --fed-domain-id 2 \ >"${log_b}" 2>&1 & pid_b=$! http_get() { local sock="$1" local path="$2" shift 2 if [[ "${USE_HTTP_HELPER}" -eq 1 ]]; then "${HTTP_HELPER}" --sock "${sock}" --method GET --path "${path}" "$@" else curl --silent --show-error --fail \ --unix-socket "${sock}" \ "$@" \ "http://localhost${path}" fi } http_get_allow() { local sock="$1" local path="$2" shift 2 if [[ "${USE_HTTP_HELPER}" -eq 1 ]]; then "${HTTP_HELPER}" --sock "${sock}" --method GET --path "${path}" \ --allow-status "$@" else curl --silent --show-error \ --unix-socket "${sock}" \ "$@" \ "http://localhost${path}" fi } http_post() { local sock="$1" local path="$2" local data="$3" shift 3 if [[ "${USE_HTTP_HELPER}" -eq 1 ]]; then "${HTTP_HELPER}" --sock "${sock}" --method POST --path "${path}" \ --data "${data}" \ "$@" else curl --silent --show-error --fail \ --unix-socket "${sock}" \ "$@" \ --data-binary "${data}" \ "http://localhost${path}" fi } wait_for_ready() { local sock="$1" local pid="$2" local log_path="$3" local i for i in $(seq 1 100); do if ! kill -0 "${pid}" >/dev/null 2>&1; then if [[ -f "${log_path}" ]] && grep -q "bind: Operation not permitted" "${log_path}"; then echo "skip: bind not permitted for unix socket" >&2 exit 77 fi if [[ -f "${log_path}" ]]; then cat "${log_path}" >&2 fi return 1 fi if [[ -S "${sock}" ]]; then if http_get "${sock}" "/v1/meta" >/dev/null 2>&1; then return 0 fi fi sleep 0.1 done return 1 } if ! wait_for_ready "${sock_a}" "${pid_a}" "${log_a}"; then echo "daemon A not ready" >&2 exit 1 fi if ! wait_for_ready "${sock_b}" "${pid_b}" "${log_b}"; then echo "daemon B not ready" >&2 exit 1 fi payload="fed-smoke" artifact_resp="$( http_post "${sock_a}" "/v1/artifacts" "${payload}" \ --header "Content-Type: application/octet-stream" \ --header "X-Amduat-Space: ${space_id}" )" || { echo "artifact POST failed" >&2 if [[ -f "${log_a}" ]]; then cat "${log_a}" >&2 fi exit 1 } ref="$( printf '%s' "${artifact_resp}" \ | tr -d '\r\n' \ | awk 'match($0, /"ref":"[^"]+"/) {print substr($0, RSTART+7, RLENGTH-8)}' )" if [[ -z "${ref}" ]]; then echo "failed to parse ref from daemon A" >&2 echo "artifact response: ${artifact_resp}" >&2 if [[ -f "${log_a}" ]]; then cat "${log_a}" >&2 fi exit 1 fi plan_resp="$( http_get_allow "${sock_b}" "/v1/fed/pull/plan?peer=1&limit=8" \ --header "X-Amduat-Space: ${space_id}" )" || { echo "pull plan failed" >&2 if [[ -f "${log_b}" ]]; then cat "${log_b}" >&2 fi exit 1 } if echo "${plan_resp}" | grep -q "\"record_count\":0"; then echo "pull plan empty" >&2 echo "plan response: ${plan_resp}" >&2 exit 1 fi pull_resp="$( http_post "${sock_b}" "/v1/fed/pull?peer=1&limit=8" "" \ --header "X-Amduat-Space: ${space_id}" )" || { echo "pull apply failed" >&2 if [[ -f "${log_b}" ]]; then cat "${log_b}" >&2 fi exit 1 } if ! echo "${pull_resp}" | grep -q "\"advanced\":true"; then echo "pull did not advance cursor" >&2 echo "pull response: ${pull_resp}" >&2 exit 1 fi cursor_json="$( http_get_allow "${sock_b}" "/v1/fed/cursor?peer=1" \ --header "X-Amduat-Space: ${space_id}" )" || { echo "cursor fetch failed" >&2 if [[ -f "${log_a}" ]]; then cat "${log_a}" >&2 fi if [[ -f "${log_b}" ]]; then cat "${log_b}" >&2 fi exit 1 } echo "${cursor_json}" | grep -q "\"last_logseq\":" || { echo "cursor missing last_logseq" >&2 echo "cursor response: ${cursor_json}" >&2 exit 1 } payload_b="$( http_get "${sock_b}" "/v1/artifacts/${ref}" \ --header "X-Amduat-Space: ${space_id}" )" || { echo "artifact fetch failed" >&2 if [[ -f "${log_b}" ]]; then cat "${log_b}" >&2 fi exit 1 } if [[ "${payload_b}" != "${payload}" ]]; then echo "payload mismatch after pull" >&2 exit 1 fi echo "fed smoke ok"