#!/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 } http_post_allow() { 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}" --allow-status \ "$@" else curl --silent --show-error \ --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\":"; then echo "pull plan malformed" >&2 echo "plan response: ${plan_resp}" >&2 exit 1 fi 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 payload_push="fed-smoke-push" artifact_resp_push="$( http_post "${sock_b}" "/v1/artifacts" "${payload_push}" \ --header "Content-Type: application/octet-stream" \ --header "X-Amduat-Space: ${space_id}" )" || { echo "artifact POST failed on B" >&2 if [[ -f "${log_b}" ]]; then cat "${log_b}" >&2 fi exit 1 } ref_push="$( printf '%s' "${artifact_resp_push}" \ | tr -d '\r\n' \ | awk 'match($0, /"ref":"[^"]+"/) {print substr($0, RSTART+7, RLENGTH-8)}' )" if [[ -z "${ref_push}" ]]; then echo "failed to parse ref from daemon B" >&2 echo "artifact response: ${artifact_resp_push}" >&2 if [[ -f "${log_b}" ]]; then cat "${log_b}" >&2 fi exit 1 fi push_plan_resp="$( http_get_allow "${sock_b}" "/v1/fed/push/plan?peer=1&limit=8" \ --header "X-Amduat-Space: ${space_id}" )" || { echo "push plan failed" >&2 if [[ -f "${log_b}" ]]; then cat "${log_b}" >&2 fi exit 1 } if ! echo "${push_plan_resp}" | grep -q "\"record_count\":"; then echo "push plan malformed (missing endpoint?)" >&2 echo "push plan response: ${push_plan_resp}" >&2 exit 1 fi if echo "${push_plan_resp}" | grep -q "\"record_count\":0"; then echo "push plan empty" >&2 echo "push plan response: ${push_plan_resp}" >&2 exit 1 fi push_cursor_before="$( printf '%s' "${push_plan_resp}" \ | tr -d '\r\n' \ | awk 'match($0, /"cursor":\{[^}]*\}/) {seg=substr($0, RSTART, RLENGTH); if (match(seg, /"last_logseq":[0-9]+/)) {print substr(seg, RSTART+14, RLENGTH-14)}}' )" push_resp="$( http_post_allow "${sock_b}" "/v1/fed/push?peer=1&limit=8" "" \ --header "X-Amduat-Space: ${space_id}" )" || { echo "push apply failed" >&2 if [[ -f "${log_b}" ]]; then cat "${log_b}" >&2 fi exit 1 } if ! echo "${push_resp}" | grep -q "\"advanced\":true"; then echo "push did not advance cursor" >&2 echo "push response: ${push_resp}" >&2 exit 1 fi payload_a="$( http_get "${sock_a}" "/v1/artifacts/${ref_push}" \ --header "X-Amduat-Space: ${space_id}" )" || { echo "artifact fetch failed on A" >&2 if [[ -f "${log_a}" ]]; then cat "${log_a}" >&2 fi exit 1 } if [[ "${payload_a}" != "${payload_push}" ]]; then echo "payload mismatch after push" >&2 exit 1 fi push_plan_after="$( http_get_allow "${sock_b}" "/v1/fed/push/plan?peer=1&limit=1" \ --header "X-Amduat-Space: ${space_id}" )" || { echo "push plan after failed" >&2 if [[ -f "${log_b}" ]]; then cat "${log_b}" >&2 fi exit 1 } push_cursor_after="$( printf '%s' "${push_plan_after}" \ | tr -d '\r\n' \ | awk 'match($0, /"cursor":\{[^}]*\}/) {seg=substr($0, RSTART, RLENGTH); if (match(seg, /"last_logseq":[0-9]+/)) {print substr(seg, RSTART+14, RLENGTH-14)}}' )" if [[ -n "${push_cursor_before}" && -n "${push_cursor_after}" ]]; then if [[ "${push_cursor_after}" -lt "${push_cursor_before}" ]]; then echo "push cursor did not advance" >&2 echo "cursor before: ${push_cursor_before}" >&2 echo "cursor after: ${push_cursor_after}" >&2 exit 1 fi fi echo "fed smoke ok"