2026-02-07 19:46:59 +01:00
|
|
|
#!/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"
|
2026-02-08 00:06:46 +01:00
|
|
|
ASL_BIN="${ROOT_DIR}/build/vendor/amduat/amduat-asl"
|
|
|
|
|
if [[ ! -x "${ASL_BIN}" ]]; then
|
|
|
|
|
ASL_BIN="${ROOT_DIR}/vendor/amduat/build/amduat-asl"
|
|
|
|
|
fi
|
2026-02-07 19:46:59 +01:00
|
|
|
|
|
|
|
|
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-graph-queries-XXXXXX)"
|
|
|
|
|
root="${tmp_root}/root"
|
|
|
|
|
sock="${tmp_root}/amduatd.sock"
|
|
|
|
|
space_id="graphq"
|
|
|
|
|
log_file="${tmp_root}/amduatd.log"
|
|
|
|
|
|
|
|
|
|
cleanup() {
|
|
|
|
|
if [[ -n "${pid:-}" ]]; then
|
|
|
|
|
kill "${pid}" >/dev/null 2>&1 || true
|
|
|
|
|
fi
|
|
|
|
|
rm -rf "${tmp_root}"
|
|
|
|
|
}
|
|
|
|
|
trap cleanup EXIT
|
|
|
|
|
|
|
|
|
|
http_get() {
|
|
|
|
|
local path="$1"
|
|
|
|
|
shift
|
|
|
|
|
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_post() {
|
|
|
|
|
local path="$1"
|
|
|
|
|
local data="$2"
|
|
|
|
|
shift 2
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extract_json_string() {
|
|
|
|
|
local key="$1"
|
|
|
|
|
awk -v k="\"${key}\":\"" '
|
|
|
|
|
{
|
|
|
|
|
pos = index($0, k);
|
|
|
|
|
if (pos > 0) {
|
|
|
|
|
rest = substr($0, pos + length(k));
|
|
|
|
|
end = index(rest, "\"");
|
|
|
|
|
if (end > 0) {
|
|
|
|
|
print substr(rest, 1, end - 1);
|
|
|
|
|
exit 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cursor_plus_one() {
|
|
|
|
|
local token="$1"
|
|
|
|
|
local n
|
|
|
|
|
if [[ -z "${token}" ]]; then
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
if [[ "${token}" == g1_* ]]; then
|
|
|
|
|
n="${token#g1_}"
|
|
|
|
|
printf 'g1_%s' "$((n + 1))"
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
printf '%s' "$((token + 1))"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wait_for_ready() {
|
|
|
|
|
local i
|
|
|
|
|
for i in $(seq 1 100); do
|
|
|
|
|
if ! kill -0 "${pid}" >/dev/null 2>&1; then
|
|
|
|
|
if [[ -f "${log_file}" ]] && grep -q "bind: Operation not permitted" "${log_file}"; then
|
|
|
|
|
echo "skip: bind not permitted for unix socket" >&2
|
|
|
|
|
exit 77
|
|
|
|
|
fi
|
|
|
|
|
[[ -f "${log_file}" ]] && cat "${log_file}" >&2
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
if [[ -S "${sock}" ]] && http_get "/v1/meta" >/dev/null 2>&1; then
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
sleep 0.1
|
|
|
|
|
done
|
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
create_artifact() {
|
|
|
|
|
local payload="$1"
|
|
|
|
|
local resp
|
|
|
|
|
local ref
|
|
|
|
|
resp="$(
|
|
|
|
|
http_post "/v1/artifacts" "${payload}" \
|
|
|
|
|
--header "Content-Type: application/octet-stream" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
ref="$(printf '%s\n' "${resp}" | extract_json_string "ref")"
|
|
|
|
|
if [[ -z "${ref}" ]]; then
|
|
|
|
|
echo "failed to parse artifact ref: ${resp}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
printf '%s' "${ref}"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
create_node() {
|
|
|
|
|
local name="$1"
|
|
|
|
|
local ref="$2"
|
|
|
|
|
http_post "/v2/graph/nodes" "{\"name\":\"${name}\",\"ref\":\"${ref}\"}" \
|
|
|
|
|
--header "Content-Type: application/json" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}" >/dev/null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
create_edge() {
|
|
|
|
|
local subject="$1"
|
|
|
|
|
local predicate="$2"
|
|
|
|
|
local object="$3"
|
|
|
|
|
http_post "/v2/graph/edges" \
|
|
|
|
|
"{\"subject\":\"${subject}\",\"predicate\":\"${predicate}\",\"object\":\"${object}\"}" \
|
|
|
|
|
--header "Content-Type: application/json" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}" >/dev/null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
create_edge_with_metadata() {
|
|
|
|
|
local subject="$1"
|
|
|
|
|
local predicate="$2"
|
|
|
|
|
local object="$3"
|
|
|
|
|
local metadata_ref="$4"
|
|
|
|
|
http_post "/v2/graph/edges" \
|
|
|
|
|
"{\"subject\":\"${subject}\",\"predicate\":\"${predicate}\",\"object\":\"${object}\",\"metadata_ref\":\"${metadata_ref}\"}" \
|
|
|
|
|
--header "Content-Type: application/json" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}" >/dev/null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
start_daemon() {
|
|
|
|
|
"${AMDUATD_BIN}" --root "${root}" --sock "${sock}" --store-backend index --space "${space_id}" \
|
|
|
|
|
>"${log_file}" 2>&1 &
|
|
|
|
|
pid=$!
|
|
|
|
|
if ! wait_for_ready; then
|
|
|
|
|
echo "daemon not ready" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
restart_daemon() {
|
|
|
|
|
if [[ -n "${pid:-}" ]]; then
|
|
|
|
|
kill "${pid}" >/dev/null 2>&1 || true
|
|
|
|
|
wait "${pid}" >/dev/null 2>&1 || true
|
|
|
|
|
fi
|
|
|
|
|
start_daemon
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mkdir -p "${root}"
|
|
|
|
|
"${ASL_BIN}" init --root "${root}"
|
|
|
|
|
start_daemon
|
|
|
|
|
|
|
|
|
|
ref_a="$(create_artifact "payload-a")"
|
|
|
|
|
ref_b="$(create_artifact "payload-b")"
|
|
|
|
|
ref_c="$(create_artifact "payload-c")"
|
|
|
|
|
ref_d="$(create_artifact "payload-d")"
|
|
|
|
|
ref_e="$(create_artifact "payload-e")"
|
|
|
|
|
ref_e2="$(create_artifact "payload-e2")"
|
|
|
|
|
ref_v1="$(create_artifact "payload-v1")"
|
|
|
|
|
ref_v2="$(create_artifact "payload-v2")"
|
|
|
|
|
ref_p1="$(create_artifact "payload-provenance-1")"
|
|
|
|
|
ref_p2="$(create_artifact "payload-provenance-2")"
|
|
|
|
|
|
|
|
|
|
create_node "gq-a" "${ref_a}"
|
|
|
|
|
create_node "gq-b" "${ref_b}"
|
|
|
|
|
create_node "gq-c" "${ref_c}"
|
|
|
|
|
create_node "gq-d" "${ref_d}"
|
|
|
|
|
create_node "gq-e" "${ref_e}"
|
|
|
|
|
create_node "gq-v" "${ref_v1}"
|
|
|
|
|
create_node "gq-prov1" "${ref_p1}"
|
|
|
|
|
create_node "gq-prov2" "${ref_p2}"
|
|
|
|
|
|
|
|
|
|
# Seed path and neighbor data in a controlled edge order.
|
|
|
|
|
create_edge "gq-a" "ms.within_domain" "gq-b"
|
|
|
|
|
create_edge "gq-b" "ms.within_domain" "gq-c"
|
|
|
|
|
create_edge "gq-a" "ms.computed_by" "gq-b"
|
|
|
|
|
|
|
|
|
|
cutoff_resp="$(
|
|
|
|
|
http_get "/v2/graph/changes?event_types[]=edge_appended&limit=100" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
cutoff_cursor="$(printf '%s\n' "${cutoff_resp}" | extract_json_string "next_cursor")"
|
|
|
|
|
if [[ -z "${cutoff_cursor}" ]]; then
|
|
|
|
|
echo "failed to parse cutoff cursor: ${cutoff_resp}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
cutoff_cursor="$(cursor_plus_one "${cutoff_cursor}")"
|
|
|
|
|
|
|
|
|
|
create_edge "gq-a" "ms.computed_by" "gq-d"
|
|
|
|
|
create_edge "gq-a" "ms.within_domain" "gq-c"
|
|
|
|
|
|
|
|
|
|
search_page1="$(
|
|
|
|
|
http_get "/v2/graph/search?name_prefix=gq-&limit=2" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
search_cursor="$(printf '%s\n' "${search_page1}" | extract_json_string "next_cursor")"
|
|
|
|
|
if [[ -z "${search_cursor}" ]]; then
|
|
|
|
|
echo "missing search cursor in page1: ${search_page1}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
search_page2="$(
|
|
|
|
|
http_get "/v2/graph/search?name_prefix=gq-&limit=10&cursor=${search_cursor}" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
search_joined="${search_page1} ${search_page2}"
|
|
|
|
|
echo "${search_joined}" | grep -q '"name":"gq-a"' || { echo "search missing gq-a" >&2; exit 1; }
|
|
|
|
|
echo "${search_joined}" | grep -q '"name":"gq-b"' || { echo "search missing gq-b" >&2; exit 1; }
|
|
|
|
|
echo "${search_joined}" | grep -q '"name":"gq-c"' || { echo "search missing gq-c" >&2; exit 1; }
|
|
|
|
|
echo "${search_joined}" | grep -q '"name":"gq-d"' || { echo "search missing gq-d" >&2; exit 1; }
|
|
|
|
|
|
|
|
|
|
neighbors_page1="$(
|
|
|
|
|
http_get "/v2/graph/nodes/gq-a/neighbors?dir=outgoing&predicate=ms.computed_by&limit=1&expand_names=true" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
neighbors_cursor="$(printf '%s\n' "${neighbors_page1}" | extract_json_string "next_cursor")"
|
|
|
|
|
if [[ -z "${neighbors_cursor}" ]]; then
|
|
|
|
|
echo "missing neighbors cursor in page1: ${neighbors_page1}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
neighbors_page2="$(
|
|
|
|
|
http_get "/v2/graph/nodes/gq-a/neighbors?dir=outgoing&predicate=ms.computed_by&limit=10&cursor=${neighbors_cursor}&expand_names=true" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
neighbors_joined="${neighbors_page1} ${neighbors_page2}"
|
|
|
|
|
echo "${neighbors_joined}" | grep -q '"neighbor_name":"gq-b"' || { echo "neighbors missing gq-b" >&2; exit 1; }
|
|
|
|
|
echo "${neighbors_joined}" | grep -q '"neighbor_name":"gq-d"' || { echo "neighbors missing gq-d" >&2; exit 1; }
|
|
|
|
|
|
|
|
|
|
neighbors_asof="$(
|
|
|
|
|
http_get "/v2/graph/nodes/gq-a/neighbors?dir=outgoing&predicate=ms.computed_by&limit=10&as_of=${cutoff_cursor}&expand_names=true" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
echo "${neighbors_asof}" | grep -q '"neighbor_name":"gq-b"' || { echo "neighbors as_of missing gq-b" >&2; exit 1; }
|
|
|
|
|
if echo "${neighbors_asof}" | grep -q '"neighbor_name":"gq-d"'; then
|
|
|
|
|
echo "neighbors as_of unexpectedly includes gq-d" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
paths_latest="$(
|
|
|
|
|
http_get "/v2/graph/paths?from=gq-a&to=gq-c&predicate=ms.within_domain&max_depth=4&expand_names=true" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
echo "${paths_latest}" | grep -q '"depth":1' || {
|
|
|
|
|
echo "paths latest expected direct depth 1: ${paths_latest}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
paths_asof="$(
|
|
|
|
|
http_get "/v2/graph/paths?from=gq-a&to=gq-c&predicate=ms.within_domain&max_depth=4&as_of=${cutoff_cursor}&expand_names=true" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
echo "${paths_asof}" | grep -q '"depth":2' || {
|
|
|
|
|
echo "paths as_of expected historical depth 2: ${paths_asof}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
subgraph_page1="$(
|
|
|
|
|
http_get "/v2/graph/subgraph?roots[]=gq-a&max_depth=2&predicates[]=ms.computed_by&dir=outgoing&limit_edges=1&limit_nodes=10&include_versions=true" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
subgraph_cursor="$(printf '%s\n' "${subgraph_page1}" | extract_json_string "next_cursor")"
|
|
|
|
|
if [[ -z "${subgraph_cursor}" ]]; then
|
|
|
|
|
echo "missing subgraph cursor in page1: ${subgraph_page1}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
subgraph_page2="$(
|
|
|
|
|
http_get "/v2/graph/subgraph?roots[]=gq-a&max_depth=2&predicates[]=ms.computed_by&dir=outgoing&limit_edges=10&limit_nodes=10&cursor=${subgraph_cursor}" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
subgraph_joined="${subgraph_page1} ${subgraph_page2}"
|
|
|
|
|
echo "${subgraph_joined}" | grep -q '"name":"gq-a"' || { echo "subgraph missing root node gq-a" >&2; exit 1; }
|
|
|
|
|
echo "${subgraph_joined}" | grep -q '"name":"gq-b"' || { echo "subgraph missing gq-b" >&2; exit 1; }
|
|
|
|
|
echo "${subgraph_joined}" | grep -q '"name":"gq-d"' || { echo "subgraph missing gq-d" >&2; exit 1; }
|
|
|
|
|
echo "${subgraph_joined}" | grep -q '"versions":\[' || { echo "subgraph include_versions missing versions" >&2; exit 1; }
|
|
|
|
|
|
|
|
|
|
subgraph_asof="$(
|
|
|
|
|
http_get "/v2/graph/subgraph?roots[]=gq-a&max_depth=2&predicates[]=ms.computed_by&dir=outgoing&as_of=${cutoff_cursor}&limit_edges=10&limit_nodes=10" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
echo "${subgraph_asof}" | grep -q '"name":"gq-b"' || { echo "subgraph as_of missing gq-b" >&2; exit 1; }
|
|
|
|
|
if echo "${subgraph_asof}" | grep -q '"name":"gq-d"'; then
|
|
|
|
|
echo "subgraph as_of unexpectedly includes gq-d" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
gqd_edge_resp="$(
|
|
|
|
|
http_get "/v2/graph/edges?subject=gq-a&predicate=ms.computed_by&object=gq-d&dir=outgoing&limit=1" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
gqd_edge_ref="$(printf '%s\n' "${gqd_edge_resp}" | extract_json_string "edge_ref")"
|
|
|
|
|
if [[ -z "${gqd_edge_ref}" ]]; then
|
|
|
|
|
echo "failed to parse gq-a->gq-d edge ref: ${gqd_edge_resp}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
http_post "/v2/graph/edges/tombstone" \
|
|
|
|
|
"{\"edge_ref\":\"${gqd_edge_ref}\"}" \
|
|
|
|
|
--header "Content-Type: application/json" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}" >/dev/null
|
|
|
|
|
|
|
|
|
|
subgraph_after_tombstone="$(
|
|
|
|
|
http_get "/v2/graph/subgraph?roots[]=gq-a&max_depth=2&predicates[]=ms.computed_by&dir=outgoing&limit_edges=10&limit_nodes=10" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
if echo "${subgraph_after_tombstone}" | grep -q '"name":"gq-d"'; then
|
|
|
|
|
echo "subgraph after tombstone unexpectedly includes gq-d: ${subgraph_after_tombstone}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
subgraph_include_tombstoned="$(
|
|
|
|
|
http_get "/v2/graph/subgraph?roots[]=gq-a&max_depth=2&predicates[]=ms.computed_by&dir=outgoing&include_tombstoned=true&limit_edges=10&limit_nodes=10" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
echo "${subgraph_include_tombstoned}" | grep -q '"name":"gq-d"' || {
|
|
|
|
|
echo "subgraph include_tombstoned missing gq-d: ${subgraph_include_tombstoned}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
edges_include_tombstoned="$(
|
|
|
|
|
http_get "/v2/graph/edges?subject=gq-a&predicate=ms.computed_by&dir=outgoing&include_tombstoned=true&limit=10&expand_names=true" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
echo "${edges_include_tombstoned}" | grep -q '"object_name":"gq-d"' || {
|
|
|
|
|
echo "edges include_tombstoned missing gq-d: ${edges_include_tombstoned}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
neighbors_after_tombstone="$(
|
|
|
|
|
http_get "/v2/graph/nodes/gq-a/neighbors?dir=outgoing&predicate=ms.computed_by&limit=10&expand_names=true" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
if echo "${neighbors_after_tombstone}" | grep -q '"neighbor_name":"gq-d"'; then
|
|
|
|
|
echo "neighbors default should exclude tombstoned gq-d edge: ${neighbors_after_tombstone}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
neighbors_include_tombstoned="$(
|
|
|
|
|
http_get "/v2/graph/nodes/gq-a/neighbors?dir=outgoing&predicate=ms.computed_by&include_tombstoned=true&limit=10&expand_names=true" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
echo "${neighbors_include_tombstoned}" | grep -q '"neighbor_name":"gq-d"' || {
|
|
|
|
|
echo "neighbors include_tombstoned missing gq-d: ${neighbors_include_tombstoned}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
paths_excluding_tombstoned="$(
|
|
|
|
|
http_get "/v2/graph/paths?from=gq-a&to=gq-d&predicate=ms.computed_by&max_depth=2" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
echo "${paths_excluding_tombstoned}" | grep -q '"paths":\[\]' || {
|
|
|
|
|
echo "paths default should exclude tombstoned edge: ${paths_excluding_tombstoned}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
paths_include_tombstoned="$(
|
|
|
|
|
http_get "/v2/graph/paths?from=gq-a&to=gq-d&predicate=ms.computed_by&max_depth=2&include_tombstoned=true" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
echo "${paths_include_tombstoned}" | grep -q '"depth":1' || {
|
|
|
|
|
echo "paths include_tombstoned expected depth 1 path: ${paths_include_tombstoned}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
create_edge_with_metadata "gq-b" "ms.computed_by" "gq-d" "gq-prov1"
|
|
|
|
|
create_edge_with_metadata "gq-b" "ms.computed_by" "gq-a" "gq-prov2"
|
|
|
|
|
|
|
|
|
|
neighbors_provenance="$(
|
|
|
|
|
http_get "/v2/graph/nodes/gq-b/neighbors?dir=outgoing&predicate=ms.computed_by&provenance_ref=gq-prov1&limit=10&expand_names=true" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
echo "${neighbors_provenance}" | grep -q '"neighbor_name":"gq-d"' || {
|
|
|
|
|
echo "neighbors provenance filter missing gq-d: ${neighbors_provenance}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
neighbors_provenance_missing="$(
|
|
|
|
|
http_get "/v2/graph/nodes/gq-b/neighbors?dir=outgoing&predicate=ms.computed_by&provenance_ref=gq-prov-missing&limit=10" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
echo "${neighbors_provenance_missing}" | grep -q '"neighbors":\[\]' || {
|
|
|
|
|
echo "neighbors unresolved provenance expected empty result: ${neighbors_provenance_missing}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
paths_provenance_match="$(
|
|
|
|
|
http_get "/v2/graph/paths?from=gq-b&to=gq-d&predicate=ms.computed_by&provenance_ref=gq-prov1&max_depth=2&expand_names=true" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
echo "${paths_provenance_match}" | grep -q '"depth":1' || {
|
|
|
|
|
echo "paths provenance filter expected depth 1 path: ${paths_provenance_match}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
echo "${paths_provenance_match}" | grep -q '"object_name":"gq-d"' || {
|
|
|
|
|
echo "paths provenance filter missing gq-d: ${paths_provenance_match}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
paths_provenance_excluded="$(
|
|
|
|
|
http_get "/v2/graph/paths?from=gq-b&to=gq-a&predicate=ms.computed_by&provenance_ref=gq-prov1&max_depth=2" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
echo "${paths_provenance_excluded}" | grep -q '"paths":\[\]' || {
|
|
|
|
|
echo "paths provenance filter unexpectedly includes gq-b->gq-a path: ${paths_provenance_excluded}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
batch_resp="$(
|
|
|
|
|
http_post "/v2/graph/batch" \
|
|
|
|
|
"{\"edges\":[{\"subject\":\"gq-c\",\"predicate\":\"ms.computed_by\",\"object\":\"gq-d\",\"metadata_ref\":\"gq-prov1\"}]}" \
|
|
|
|
|
--header "Content-Type: application/json" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
echo "${batch_resp}" | grep -q '"ok":true' || {
|
|
|
|
|
echo "batch edge with metadata_ref failed: ${batch_resp}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
echo "${batch_resp}" | grep -q '"results":\[' || {
|
|
|
|
|
echo "batch response missing results array: ${batch_resp}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
echo "${batch_resp}" | grep -q '"status":"applied"' || {
|
|
|
|
|
echo "batch response missing applied item status: ${batch_resp}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
batch_version_provenance="$(
|
|
|
|
|
http_post "/v2/graph/batch" \
|
|
|
|
|
"{\"versions\":[{\"name\":\"gq-e\",\"ref\":\"${ref_e2}\",\"provenance\":{\"source_uri\":\"urn:test:gq-e-v2\",\"extractor\":\"graph-test\",\"confidence\":\"0.91\",\"observed_at\":1730000000000,\"ingested_at\":1730000000100,\"license\":\"test-only\",\"trace_id\":\"trace-gq-e-v2\"}}]}" \
|
|
|
|
|
--header "Content-Type: application/json" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
echo "${batch_version_provenance}" | grep -q '"ok":true' || {
|
|
|
|
|
echo "batch version with provenance failed: ${batch_version_provenance}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
echo "${batch_version_provenance}" | grep -q '"status":"applied"' || {
|
|
|
|
|
echo "batch version with provenance missing applied status: ${batch_version_provenance}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
batch_edge_provenance="$(
|
|
|
|
|
http_post "/v2/graph/batch" \
|
|
|
|
|
"{\"edges\":[{\"subject\":\"gq-c\",\"predicate\":\"ms.computed_by\",\"object\":\"gq-a\",\"provenance\":{\"source_uri\":\"urn:test:gq-c-a\",\"extractor\":\"graph-test\",\"confidence\":\"0.87\",\"observed_at\":1730000000200,\"ingested_at\":1730000000300,\"trace_id\":\"trace-gq-c-a\"}}]}" \
|
|
|
|
|
--header "Content-Type: application/json" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
echo "${batch_edge_provenance}" | grep -q '"ok":true' || {
|
|
|
|
|
echo "batch edge with provenance failed: ${batch_edge_provenance}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
echo "${batch_edge_provenance}" | grep -q '"status":"applied"' || {
|
|
|
|
|
echo "batch edge with provenance missing applied status: ${batch_edge_provenance}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
http_post "/v2/graph/nodes/gq-v/versions" \
|
|
|
|
|
"{\"ref\":\"${ref_v2}\"}" \
|
|
|
|
|
--header "Content-Type: application/json" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}" >/dev/null
|
|
|
|
|
|
|
|
|
|
http_post "/v2/graph/nodes/gq-v/versions/tombstone" \
|
|
|
|
|
"{\"ref\":\"${ref_v2}\"}" \
|
|
|
|
|
--header "Content-Type: application/json" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}" >/dev/null
|
|
|
|
|
|
|
|
|
|
gqv_after_tombstone="$(
|
|
|
|
|
http_get "/v2/graph/nodes/gq-v" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
gqv_latest="$(printf '%s\n' "${gqv_after_tombstone}" | extract_json_string "latest_ref")"
|
|
|
|
|
if [[ "${gqv_latest}" != "${ref_v1}" ]]; then
|
|
|
|
|
echo "version tombstone expected latest_ref=${ref_v1}, got ${gqv_latest}: ${gqv_after_tombstone}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
if echo "${gqv_after_tombstone}" | grep -q "\"ref\":\"${ref_v2}\""; then
|
|
|
|
|
echo "version tombstone expected ${ref_v2} hidden by default: ${gqv_after_tombstone}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
gqv_include_tombstoned="$(
|
|
|
|
|
http_get "/v2/graph/nodes/gq-v?include_tombstoned=true" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
gqv_latest_all="$(printf '%s\n' "${gqv_include_tombstoned}" | extract_json_string "latest_ref")"
|
|
|
|
|
if [[ "${gqv_latest_all}" != "${ref_v2}" ]]; then
|
|
|
|
|
echo "include_tombstoned expected latest_ref=${ref_v2}, got ${gqv_latest_all}: ${gqv_include_tombstoned}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
echo "${gqv_include_tombstoned}" | grep -q "\"ref\":\"${ref_v2}\"" || {
|
|
|
|
|
echo "include_tombstoned expected historical version ${ref_v2}: ${gqv_include_tombstoned}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
history_default="$(
|
|
|
|
|
http_get "/v2/graph/history/gq-v" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
history_default_latest="$(printf '%s\n' "${history_default}" | extract_json_string "latest_ref")"
|
|
|
|
|
if [[ "${history_default_latest}" != "${ref_v1}" ]]; then
|
|
|
|
|
echo "history default expected latest_ref=${ref_v1}, got ${history_default_latest}: ${history_default}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
if echo "${history_default}" | grep -q "\"ref\":\"${ref_v2}\""; then
|
|
|
|
|
echo "history default expected tombstoned version ${ref_v2} hidden: ${history_default}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
history_include_tombstoned="$(
|
|
|
|
|
http_get "/v2/graph/history/gq-v?include_tombstoned=true" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
history_all_latest="$(printf '%s\n' "${history_include_tombstoned}" | extract_json_string "latest_ref")"
|
|
|
|
|
if [[ "${history_all_latest}" != "${ref_v2}" ]]; then
|
|
|
|
|
echo "history include_tombstoned expected latest_ref=${ref_v2}, got ${history_all_latest}: ${history_include_tombstoned}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
echo "${history_include_tombstoned}" | grep -q "\"ref\":\"${ref_v2}\"" || {
|
|
|
|
|
echo "history include_tombstoned expected tombstoned version ${ref_v2}: ${history_include_tombstoned}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
batch_continue="$(
|
|
|
|
|
http_post "/v2/graph/batch" \
|
|
|
|
|
"{\"mode\":\"continue_on_error\",\"edges\":[{\"subject\":\"gq-a\",\"predicate\":\"ms.computed_by\",\"object\":\"gq-b\"},{\"subject\":\"gq-missing\",\"predicate\":\"ms.computed_by\",\"object\":\"gq-b\"}]}" \
|
|
|
|
|
--header "Content-Type: application/json" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
echo "${batch_continue}" | grep -q '"ok":false' || {
|
|
|
|
|
echo "batch continue_on_error expected ok=false: ${batch_continue}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
echo "${batch_continue}" | grep -q '"mode":"continue_on_error"' || {
|
|
|
|
|
echo "batch continue_on_error mode echo missing: ${batch_continue}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
echo "${batch_continue}" | grep -q '"status":"applied"' || {
|
|
|
|
|
echo "batch continue_on_error missing applied result: ${batch_continue}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
echo "${batch_continue}" | grep -q '"status":"error"' || {
|
|
|
|
|
echo "batch continue_on_error missing error result: ${batch_continue}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
idem_payload='{"idempotency_key":"gq-idem-1","mode":"continue_on_error","edges":[{"subject":"gq-b","predicate":"ms.computed_by","object":"gq-c"},{"subject":"gq-nope","predicate":"ms.computed_by","object":"gq-c"}]}'
|
|
|
|
|
idem_first="$(
|
|
|
|
|
http_post "/v2/graph/batch" \
|
|
|
|
|
"${idem_payload}" \
|
|
|
|
|
--header "Content-Type: application/json" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
idem_second="$(
|
|
|
|
|
http_post "/v2/graph/batch" \
|
|
|
|
|
"${idem_payload}" \
|
|
|
|
|
--header "Content-Type: application/json" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
if [[ "${idem_first}" != "${idem_second}" ]]; then
|
|
|
|
|
echo "idempotency replay response mismatch" >&2
|
|
|
|
|
echo "first=${idem_first}" >&2
|
|
|
|
|
echo "second=${idem_second}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
restart_daemon
|
|
|
|
|
idem_third="$(
|
|
|
|
|
http_post "/v2/graph/batch" \
|
|
|
|
|
"${idem_payload}" \
|
|
|
|
|
--header "Content-Type: application/json" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
if [[ "${idem_first}" != "${idem_third}" ]]; then
|
|
|
|
|
echo "idempotency replay after restart mismatch" >&2
|
|
|
|
|
echo "first=${idem_first}" >&2
|
|
|
|
|
echo "third=${idem_third}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
query_include_versions="$(
|
|
|
|
|
http_post "/v2/graph/query" \
|
|
|
|
|
"{\"where\":{\"subject\":\"gq-a\"},\"predicates\":[\"ms.within_domain\"],\"direction\":\"outgoing\",\"include_versions\":true,\"limit\":10}" \
|
|
|
|
|
--header "Content-Type: application/json" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
echo "${query_include_versions}" | grep -q '"versions":\[' || {
|
|
|
|
|
echo "graph query include_versions missing versions: ${query_include_versions}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
query_with_stats="$(
|
|
|
|
|
http_post "/v2/graph/query" \
|
|
|
|
|
"{\"where\":{\"subject\":\"gq-a\"},\"predicates\":[\"ms.within_domain\"],\"direction\":\"outgoing\",\"include_stats\":true,\"max_result_bytes\":1048576,\"limit\":10}" \
|
|
|
|
|
--header "Content-Type: application/json" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
echo "${query_with_stats}" | grep -q '"stats":{' || {
|
|
|
|
|
echo "graph query include_stats missing stats block: ${query_with_stats}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
echo "${query_with_stats}" | grep -q '"plan":"' || {
|
|
|
|
|
echo "graph query include_stats missing plan: ${query_with_stats}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
query_provenance="$(
|
|
|
|
|
http_post "/v2/graph/query" \
|
|
|
|
|
"{\"where\":{\"subject\":\"gq-b\",\"provenance_ref\":\"gq-prov1\"},\"predicates\":[\"ms.computed_by\"],\"direction\":\"outgoing\",\"limit\":10}" \
|
|
|
|
|
--header "Content-Type: application/json" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
echo "${query_provenance}" | grep -q '"name":"gq-d"' || {
|
|
|
|
|
echo "graph query provenance filter missing expected node gq-d: ${query_provenance}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
if echo "${query_provenance}" | grep -q '"name":"gq-a"'; then
|
|
|
|
|
echo "graph query provenance filter unexpectedly includes gq-a: ${query_provenance}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
query_provenance_count="$(printf '%s\n' "${query_provenance}" | grep -o '"edge_ref":"' | wc -l | awk '{print $1}')"
|
|
|
|
|
if [[ "${query_provenance_count}" != "1" ]]; then
|
|
|
|
|
echo "graph query provenance expected exactly one edge, got ${query_provenance_count}: ${query_provenance}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
edges_provenance="$(
|
|
|
|
|
http_get "/v2/graph/edges?subject=gq-b&predicate=ms.computed_by&dir=outgoing&provenance_ref=gq-prov1&limit=10&expand_names=true" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
echo "${edges_provenance}" | grep -q '"object_name":"gq-d"' || {
|
|
|
|
|
echo "graph edges provenance filter missing gq-d: ${edges_provenance}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
if echo "${edges_provenance}" | grep -q '"object_name":"gq-a"'; then
|
|
|
|
|
echo "graph edges provenance filter unexpectedly includes gq-a: ${edges_provenance}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
edges_with_stats="$(
|
|
|
|
|
http_get "/v2/graph/edges?subject=gq-b&predicate=ms.computed_by&dir=outgoing&include_stats=true&max_result_bytes=1048576&limit=10" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
echo "${edges_with_stats}" | grep -q '"stats":{' || {
|
|
|
|
|
echo "graph edges include_stats missing stats block: ${edges_with_stats}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
echo "${edges_with_stats}" | grep -q '"plan":"' || {
|
|
|
|
|
echo "graph edges include_stats missing plan: ${edges_with_stats}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
subgraph_provenance="$(
|
|
|
|
|
http_get "/v2/graph/subgraph?roots[]=gq-b&max_depth=1&predicates[]=ms.computed_by&dir=outgoing&provenance_ref=gq-prov1&limit_edges=10&limit_nodes=10" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
echo "${subgraph_provenance}" | grep -q '"name":"gq-d"' || {
|
|
|
|
|
echo "subgraph provenance filter missing gq-d: ${subgraph_provenance}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
if echo "${subgraph_provenance}" | grep -q '"name":"gq-a"'; then
|
|
|
|
|
echo "subgraph provenance filter unexpectedly includes gq-a: ${subgraph_provenance}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
subgraph_with_stats="$(
|
|
|
|
|
http_get "/v2/graph/subgraph?roots[]=gq-a&max_depth=2&max_fanout=4096&include_stats=true&max_result_bytes=1048576&limit_edges=10&limit_nodes=10" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
echo "${subgraph_with_stats}" | grep -q '"stats":{' || {
|
|
|
|
|
echo "subgraph include_stats missing stats block: ${subgraph_with_stats}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
echo "${subgraph_with_stats}" | grep -q '"plan":"' || {
|
|
|
|
|
echo "subgraph include_stats missing plan: ${subgraph_with_stats}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
paths_with_stats="$(
|
|
|
|
|
http_get "/v2/graph/paths?from=gq-a&to=gq-c&predicate=ms.within_domain&max_depth=4&include_stats=true&max_fanout=4096&max_result_bytes=1048576" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
echo "${paths_with_stats}" | grep -q '"stats":{' || {
|
|
|
|
|
echo "paths include_stats missing stats block: ${paths_with_stats}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
echo "${paths_with_stats}" | grep -q '"plan":"' || {
|
|
|
|
|
echo "paths include_stats missing plan: ${paths_with_stats}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gqb_node="$(
|
|
|
|
|
http_get "/v2/graph/nodes/gq-b" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
gqb_ref="$(printf '%s\n' "${gqb_node}" | extract_json_string "concept_ref")"
|
|
|
|
|
if [[ -z "${gqb_ref}" ]]; then
|
|
|
|
|
echo "failed to resolve gq-b concept ref: ${gqb_node}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
changes_tombstone="$(
|
|
|
|
|
http_get "/v2/graph/changes?event_types[]=tombstone_applied&limit=100" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
echo "${changes_tombstone}" | grep -q '"event":"tombstone_applied"' || {
|
|
|
|
|
echo "changes tombstone filter missing tombstone event: ${changes_tombstone}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
changes_filtered="$(
|
|
|
|
|
http_get "/v2/graph/changes?since_as_of=${cutoff_cursor}&predicates[]=ms.computed_by&roots[]=gq-b&limit=100" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
echo "${changes_filtered}" | grep -q "\"${gqb_ref}\"" || {
|
|
|
|
|
echo "changes root/predicate filter missing gq-b involvement: ${changes_filtered}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
if echo "${changes_filtered}" | grep -q '"event":"version_published"'; then
|
|
|
|
|
echo "changes predicate filter unexpectedly includes version events: ${changes_filtered}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
changes_wait_empty="$(
|
|
|
|
|
http_get "/v2/graph/changes?since_cursor=g1_999999&wait_ms=1&limit=1" \
|
|
|
|
|
--header "X-Amduat-Space: ${space_id}"
|
|
|
|
|
)"
|
|
|
|
|
echo "${changes_wait_empty}" | grep -q '"events":\[\]' || {
|
|
|
|
|
echo "changes wait_ms empty poll expected no events: ${changes_wait_empty}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
echo "ok: v2 graph query tests passed"
|