@rulemetric/hooks 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/scripts/_post.sh +75 -0
- package/scripts/assistant-response.sh +8 -21
- package/scripts/post-retry.test.sh +118 -0
- package/scripts/post-tool-use.sh +9 -23
- package/scripts/user-prompt.sh +10 -23
package/package.json
CHANGED
package/scripts/_post.sh
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# RuleMetric: shared event-POST helper with bounded retry on transient failures.
|
|
3
|
+
#
|
|
4
|
+
# Why this exists: the event-capture hooks (post-tool-use, assistant-response,
|
|
5
|
+
# user-prompt) POST to /api/sessions/:id/events fire-and-forget. The always-on
|
|
6
|
+
# API may fast-503 under transient DB-pool load (the admission gate from
|
|
7
|
+
# docs/plans/2026-06-28-db-ingest-load-admission-control.md, Phase 1). Without a
|
|
8
|
+
# retry, a sheddable 503 silently drops the event — the highest-value capture
|
|
9
|
+
# stream. This helper retries transient failures (503 / 429 / network) a few
|
|
10
|
+
# times, honoring a capped Retry-After, and treats 4xx as permanent (no retry),
|
|
11
|
+
# preserving the prior "log on failure, never block the hook" contract.
|
|
12
|
+
#
|
|
13
|
+
# Designed to be called inside the existing backgrounded `( ... ) &` subshell, so
|
|
14
|
+
# it never delays the hook and a non-zero return cannot abort the caller. It is
|
|
15
|
+
# defensively written to be safe under `set -euo pipefail`.
|
|
16
|
+
|
|
17
|
+
# Bounded so the total wait stays inside the hooks' fire-and-forget budget.
|
|
18
|
+
_RM_POST_MAX_ATTEMPTS="${RULEMETRIC_POST_MAX_ATTEMPTS:-3}"
|
|
19
|
+
_RM_POST_RETRY_CAP_S="${RULEMETRIC_POST_RETRY_CAP_S:-2}"
|
|
20
|
+
|
|
21
|
+
# _rulemetric_post_event <url> <auth_header> <payload> <log_tag> <seq>
|
|
22
|
+
# Returns 0 on 2xx; 1 otherwise (after logging the final failure).
|
|
23
|
+
_rulemetric_post_event() {
|
|
24
|
+
local url="$1" auth="$2" payload="$3" tag="$4" seq="$5"
|
|
25
|
+
local attempt=1 http_code="000" body="" hdr_file retry_after sleep_s
|
|
26
|
+
|
|
27
|
+
hdr_file=$(mktemp "${TMPDIR:-/tmp}/rm-post.XXXXXX" 2>/dev/null || true)
|
|
28
|
+
|
|
29
|
+
while [ "$attempt" -le "$_RM_POST_MAX_ATTEMPTS" ]; do
|
|
30
|
+
if [ -n "$hdr_file" ]; then
|
|
31
|
+
body=$(curl -s --noproxy '*' --max-time 4 -D "$hdr_file" -w '\n%{http_code}' \
|
|
32
|
+
-X POST "$url" -H "$auth" -H "Content-Type: application/json" \
|
|
33
|
+
-d "$payload" 2>&1) || true
|
|
34
|
+
else
|
|
35
|
+
body=$(curl -s --noproxy '*' --max-time 4 -w '\n%{http_code}' \
|
|
36
|
+
-X POST "$url" -H "$auth" -H "Content-Type: application/json" \
|
|
37
|
+
-d "$payload" 2>&1) || true
|
|
38
|
+
fi
|
|
39
|
+
http_code=$(printf '%s' "$body" | tail -1)
|
|
40
|
+
body=$(printf '%s' "$body" | sed '$d')
|
|
41
|
+
|
|
42
|
+
if [ "$http_code" = "200" ] || [ "$http_code" = "201" ]; then
|
|
43
|
+
[ -n "$hdr_file" ] && rm -f "$hdr_file"
|
|
44
|
+
return 0
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# Transient (service busy / rate-limited / network) → retry unless this was
|
|
48
|
+
# the last allowed attempt. Everything else (4xx) is permanent.
|
|
49
|
+
if [ "$http_code" = "503" ] || [ "$http_code" = "429" ] || [ "$http_code" = "000" ]; then
|
|
50
|
+
if [ "$attempt" -lt "$_RM_POST_MAX_ATTEMPTS" ]; then
|
|
51
|
+
retry_after=""
|
|
52
|
+
if [ -n "$hdr_file" ]; then
|
|
53
|
+
retry_after=$(grep -i '^Retry-After:' "$hdr_file" 2>/dev/null | tail -1 | tr -dc '0-9' || true)
|
|
54
|
+
fi
|
|
55
|
+
if [ -n "$retry_after" ] && [ "$retry_after" -gt 0 ] 2>/dev/null; then
|
|
56
|
+
sleep_s="$retry_after"
|
|
57
|
+
else
|
|
58
|
+
sleep_s="$attempt" # 1s, 2s, ... linear backoff
|
|
59
|
+
fi
|
|
60
|
+
if [ "$sleep_s" -gt "$_RM_POST_RETRY_CAP_S" ] 2>/dev/null; then
|
|
61
|
+
sleep_s="$_RM_POST_RETRY_CAP_S"
|
|
62
|
+
fi
|
|
63
|
+
sleep "$sleep_s" 2>/dev/null || true
|
|
64
|
+
attempt=$((attempt + 1))
|
|
65
|
+
continue
|
|
66
|
+
fi
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
break
|
|
70
|
+
done
|
|
71
|
+
|
|
72
|
+
_rulemetric_log "$tag" "error" "http_${http_code}_seq_${seq}_attempt_${attempt}: ${body}"
|
|
73
|
+
[ -n "$hdr_file" ] && rm -f "$hdr_file"
|
|
74
|
+
return 1
|
|
75
|
+
}
|
|
@@ -16,9 +16,7 @@ trap 'echo "rulemetric assistant-response failed at line $LINENO: $BASH_COMMAND"
|
|
|
16
16
|
# Load config (env vars, ~/.config/rulemetric/env, or .env.local)
|
|
17
17
|
source "$SCRIPT_DIR/_config.sh" 2>/dev/null || true
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
INPUT=""
|
|
21
|
-
IFS= read -r -d '' -t 2 INPUT || true
|
|
19
|
+
INPUT=$(cat)
|
|
22
20
|
source "$SCRIPT_DIR/_normalize.sh"
|
|
23
21
|
|
|
24
22
|
if [ -z "$HOOK_SESSION_ID" ] || [ -z "${RULEMETRIC_API_URL:-}" ]; then
|
|
@@ -76,15 +74,7 @@ TOKEN_USAGE=$(jq -n \
|
|
|
76
74
|
# Increment per-session sequence (same locking pattern as post-tool-use)
|
|
77
75
|
SEQ_FILE="$TEMP_DIR/$HOOK_SESSION_ID.seq"
|
|
78
76
|
LOCK_DIR="$TEMP_DIR/$HOOK_SESSION_ID.seqlock"
|
|
79
|
-
|
|
80
|
-
while ! mkdir "$LOCK_DIR" 2>/dev/null; do
|
|
81
|
-
sleep 0.01
|
|
82
|
-
SEQ_LOCK_TRIES=$((SEQ_LOCK_TRIES + 1))
|
|
83
|
-
if [ "$SEQ_LOCK_TRIES" -ge 500 ]; then
|
|
84
|
-
_rulemetric_log "assistant-response" "skip" "seq_lock_timeout"
|
|
85
|
-
exit 0
|
|
86
|
-
fi
|
|
87
|
-
done
|
|
77
|
+
while ! mkdir "$LOCK_DIR" 2>/dev/null; do sleep 0.01; done
|
|
88
78
|
SEQ=1
|
|
89
79
|
if [ -f "$SEQ_FILE" ]; then
|
|
90
80
|
SEQ=$(( $(cat "$SEQ_FILE") + 1 ))
|
|
@@ -125,12 +115,9 @@ PAYLOAD=$(jq -n \
|
|
|
125
115
|
_rulemetric_log "assistant-response" "success"
|
|
126
116
|
|
|
127
117
|
# Fire-and-forget (same error-logging pattern as post-tool-use)
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
BODY=$(echo "$CURL_OUT" | sed '$d')
|
|
135
|
-
_rulemetric_log "assistant-response" "error" "http_${HTTP_CODE}_seq_${SEQ}: ${BODY}"
|
|
136
|
-
fi) &
|
|
118
|
+
# Fire-and-forget event POST, with bounded retry on a transient 503/429 so the
|
|
119
|
+
# admission gate (db-wedge plan Phase 1) sheds load without dropping the event.
|
|
120
|
+
source "$SCRIPT_DIR/_post.sh"
|
|
121
|
+
_rulemetric_post_event \
|
|
122
|
+
"${RULEMETRIC_API_URL}/api/sessions/${RW_SESSION_ID}/events" \
|
|
123
|
+
"$AUTH_HEADER" "$PAYLOAD" "assistant-response" "$SEQ" &
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# RuleMetric: test for the shared event-POST retry helper (_post.sh).
|
|
3
|
+
#
|
|
4
|
+
# Run with: bash packages/hooks/scripts/post-retry.test.sh
|
|
5
|
+
# Exits non-zero on any failed assertion.
|
|
6
|
+
#
|
|
7
|
+
# Proves the Phase-0 contract for the db-wedge admission gate
|
|
8
|
+
# (docs/plans/2026-06-28-db-ingest-load-admission-control.md):
|
|
9
|
+
# 1. a transient 503 + Retry-After is RETRIED and the event lands (not dropped)
|
|
10
|
+
# 2. a permanent 4xx is NOT retried (one request, then give up)
|
|
11
|
+
# 3. sustained 503 gives up after RULEMETRIC_POST_MAX_ATTEMPTS and logs an error
|
|
12
|
+
#
|
|
13
|
+
# Uses a tiny stdlib-only Python stub server; no network, no real API.
|
|
14
|
+
|
|
15
|
+
set -uo pipefail
|
|
16
|
+
|
|
17
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
18
|
+
|
|
19
|
+
if [ -t 1 ]; then C_OK="\033[32m"; C_FAIL="\033[31m"; C_RST="\033[0m"; else C_OK=""; C_FAIL=""; C_RST=""; fi
|
|
20
|
+
FAILURES=0; PASSES=0
|
|
21
|
+
ok() { printf "${C_OK}OK${C_RST} %s\n" "$1"; PASSES=$((PASSES + 1)); }
|
|
22
|
+
fail() { printf "${C_FAIL}FAIL${C_RST} %s\n" "$1"; FAILURES=$((FAILURES + 1)); }
|
|
23
|
+
assert_eq() { if [ "$2" = "$3" ]; then ok "$1"; else fail "$1 (expected [$2], got [$3])"; fi; }
|
|
24
|
+
|
|
25
|
+
PYTHON="$(command -v python3 || command -v python || true)"
|
|
26
|
+
if [ -z "$PYTHON" ]; then echo "SKIP: python3 not found"; exit 0; fi
|
|
27
|
+
|
|
28
|
+
WORK_ROOT=$(mktemp -d "${TMPDIR:-/tmp}/post-retry-test.XXXXXX")
|
|
29
|
+
cleanup() { [ -n "${SERVER_PID:-}" ] && kill "$SERVER_PID" 2>/dev/null; rm -rf "$WORK_ROOT" 2>/dev/null || true; }
|
|
30
|
+
trap cleanup EXIT
|
|
31
|
+
|
|
32
|
+
# --- Stub server: counts requests, programmed by $MODE in a control file --------
|
|
33
|
+
cat > "$WORK_ROOT/stub.py" <<'PY'
|
|
34
|
+
import http.server, json, os, sys, threading
|
|
35
|
+
mode = sys.argv[1] # fail-once | always-503 | always-400
|
|
36
|
+
count_file = sys.argv[2]
|
|
37
|
+
n = 0
|
|
38
|
+
lock = threading.Lock()
|
|
39
|
+
class H(http.server.BaseHTTPRequestHandler):
|
|
40
|
+
def log_message(self, *a): pass
|
|
41
|
+
def do_POST(self):
|
|
42
|
+
global n
|
|
43
|
+
with lock:
|
|
44
|
+
n += 1; cur = n
|
|
45
|
+
open(count_file, "w").write(str(n))
|
|
46
|
+
length = int(self.headers.get('Content-Length', 0) or 0)
|
|
47
|
+
self.rfile.read(length)
|
|
48
|
+
if mode == "always-400":
|
|
49
|
+
self.send_response(400); self.end_headers(); self.wfile.write(b'{"error":"bad"}'); return
|
|
50
|
+
if mode == "always-503":
|
|
51
|
+
self.send_response(503); self.send_header("Retry-After", "1"); self.end_headers()
|
|
52
|
+
self.wfile.write(b'{"error":"busy"}'); return
|
|
53
|
+
# fail-once: 503 on first request, 200 thereafter
|
|
54
|
+
if cur == 1:
|
|
55
|
+
self.send_response(503); self.send_header("Retry-After", "1"); self.end_headers()
|
|
56
|
+
self.wfile.write(b'{"error":"busy"}')
|
|
57
|
+
else:
|
|
58
|
+
self.send_response(200); self.end_headers(); self.wfile.write(b'{"recorded":true}')
|
|
59
|
+
srv = http.server.HTTPServer(("127.0.0.1", 0), H)
|
|
60
|
+
print(srv.server_address[1], flush=True)
|
|
61
|
+
srv.serve_forever()
|
|
62
|
+
PY
|
|
63
|
+
|
|
64
|
+
start_server() {
|
|
65
|
+
# $1 = mode; sets SERVER_PORT, SERVER_PID, COUNT_FILE
|
|
66
|
+
COUNT_FILE="$WORK_ROOT/count.$1"
|
|
67
|
+
: > "$COUNT_FILE"
|
|
68
|
+
exec 3< <("$PYTHON" "$WORK_ROOT/stub.py" "$1" "$COUNT_FILE")
|
|
69
|
+
SERVER_PID=$!
|
|
70
|
+
read -r SERVER_PORT <&3
|
|
71
|
+
}
|
|
72
|
+
stop_server() { [ -n "${SERVER_PID:-}" ] && kill "$SERVER_PID" 2>/dev/null; SERVER_PID=""; }
|
|
73
|
+
req_count() { cat "$COUNT_FILE" 2>/dev/null || echo 0; }
|
|
74
|
+
|
|
75
|
+
# --- Load the helper under test ----------------------------------------------
|
|
76
|
+
LOG_FILE="$WORK_ROOT/log"
|
|
77
|
+
_rulemetric_log() { echo "$*" >> "$LOG_FILE"; } # stub: record log calls
|
|
78
|
+
export RULEMETRIC_POST_MAX_ATTEMPTS=3
|
|
79
|
+
export RULEMETRIC_POST_RETRY_CAP_S=1
|
|
80
|
+
# shellcheck source=/dev/null
|
|
81
|
+
source "$SCRIPT_DIR/_post.sh"
|
|
82
|
+
|
|
83
|
+
AUTH="Authorization: Bearer test"
|
|
84
|
+
PAYLOAD='{"sequence":1,"eventType":"tool_call"}'
|
|
85
|
+
|
|
86
|
+
# --- Test 1: transient 503 + Retry-After is retried, event lands --------------
|
|
87
|
+
start_server fail-once
|
|
88
|
+
: > "$LOG_FILE"
|
|
89
|
+
_rulemetric_post_event "http://127.0.0.1:$SERVER_PORT/events" "$AUTH" "$PAYLOAD" "test" 1
|
|
90
|
+
rc=$?
|
|
91
|
+
assert_eq "fail-once: returns success (0) after retry" "0" "$rc"
|
|
92
|
+
assert_eq "fail-once: server saw exactly 2 requests (fail + retry)" "2" "$(req_count)"
|
|
93
|
+
assert_eq "fail-once: no error logged on eventual success" "0" "$(wc -l < "$LOG_FILE" | tr -d ' ')"
|
|
94
|
+
stop_server
|
|
95
|
+
|
|
96
|
+
# --- Test 2: permanent 4xx is NOT retried ------------------------------------
|
|
97
|
+
start_server always-400
|
|
98
|
+
: > "$LOG_FILE"
|
|
99
|
+
_rulemetric_post_event "http://127.0.0.1:$SERVER_PORT/events" "$AUTH" "$PAYLOAD" "test" 2
|
|
100
|
+
rc=$?
|
|
101
|
+
assert_eq "always-400: returns failure (1)" "1" "$rc"
|
|
102
|
+
assert_eq "always-400: server saw exactly 1 request (no retry on 4xx)" "1" "$(req_count)"
|
|
103
|
+
stop_server
|
|
104
|
+
|
|
105
|
+
# --- Test 3: sustained 503 gives up after MAX_ATTEMPTS and logs ---------------
|
|
106
|
+
start_server always-503
|
|
107
|
+
: > "$LOG_FILE"
|
|
108
|
+
_rulemetric_post_event "http://127.0.0.1:$SERVER_PORT/events" "$AUTH" "$PAYLOAD" "test" 3
|
|
109
|
+
rc=$?
|
|
110
|
+
assert_eq "always-503: returns failure (1) after exhausting attempts" "1" "$rc"
|
|
111
|
+
assert_eq "always-503: server saw exactly MAX_ATTEMPTS (3) requests" "3" "$(req_count)"
|
|
112
|
+
grep -q "http_503" "$LOG_FILE" && ok "always-503: logged an http_503 error" || fail "always-503: expected http_503 in log"
|
|
113
|
+
stop_server
|
|
114
|
+
|
|
115
|
+
# --- Summary -----------------------------------------------------------------
|
|
116
|
+
echo
|
|
117
|
+
echo "Passed: $PASSES Failed: $FAILURES"
|
|
118
|
+
[ "$FAILURES" -eq 0 ]
|
package/scripts/post-tool-use.sh
CHANGED
|
@@ -9,10 +9,8 @@ trap 'echo "rulemetric post-tool-use failed at line $LINENO: $BASH_COMMAND" >&2;
|
|
|
9
9
|
# Load config (env vars, ~/.config/rulemetric/env, or .env.local)
|
|
10
10
|
source "$SCRIPT_DIR/_config.sh" 2>/dev/null || true
|
|
11
11
|
|
|
12
|
-
# Read hook input from stdin and normalize across tools
|
|
13
|
-
|
|
14
|
-
INPUT=""
|
|
15
|
-
IFS= read -r -d '' -t 2 INPUT || true
|
|
12
|
+
# Read hook input from stdin and normalize across tools
|
|
13
|
+
INPUT=$(cat)
|
|
16
14
|
source "$SCRIPT_DIR/_normalize.sh"
|
|
17
15
|
|
|
18
16
|
# Session paper-trail: snapshot the full working tree to a shadow git ref for
|
|
@@ -53,15 +51,7 @@ fi
|
|
|
53
51
|
# Increment sequence counter (atomic mkdir lock, works on macOS + Linux)
|
|
54
52
|
SEQ_FILE="$TEMP_DIR/$HOOK_SESSION_ID.seq"
|
|
55
53
|
LOCK_DIR="$TEMP_DIR/$HOOK_SESSION_ID.seqlock"
|
|
56
|
-
|
|
57
|
-
while ! mkdir "$LOCK_DIR" 2>/dev/null; do
|
|
58
|
-
sleep 0.01
|
|
59
|
-
SEQ_LOCK_TRIES=$((SEQ_LOCK_TRIES + 1))
|
|
60
|
-
if [ "$SEQ_LOCK_TRIES" -ge 500 ]; then
|
|
61
|
-
_rulemetric_log "post-tool-use" "skip" "seq_lock_timeout"
|
|
62
|
-
exit 0
|
|
63
|
-
fi
|
|
64
|
-
done
|
|
54
|
+
while ! mkdir "$LOCK_DIR" 2>/dev/null; do sleep 0.01; done
|
|
65
55
|
SEQ=1
|
|
66
56
|
if [ -f "$SEQ_FILE" ]; then
|
|
67
57
|
SEQ=$(( $(cat "$SEQ_FILE") + 1 ))
|
|
@@ -80,13 +70,9 @@ PAYLOAD=$(jq -n \
|
|
|
80
70
|
|
|
81
71
|
_rulemetric_log "post-tool-use" "success"
|
|
82
72
|
|
|
83
|
-
# Fire-and-forget
|
|
84
|
-
(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if [ "$HTTP_CODE" != "200" ] && [ "$HTTP_CODE" != "201" ]; then
|
|
90
|
-
BODY=$(echo "$CURL_OUT" | sed '$d')
|
|
91
|
-
_rulemetric_log "post-tool-use" "error" "http_${HTTP_CODE}_seq_${SEQ}: ${BODY}"
|
|
92
|
-
fi) &
|
|
73
|
+
# Fire-and-forget event POST, with bounded retry on a transient 503/429 so the
|
|
74
|
+
# admission gate (db-wedge plan Phase 1) sheds load without dropping the event.
|
|
75
|
+
source "$SCRIPT_DIR/_post.sh"
|
|
76
|
+
_rulemetric_post_event \
|
|
77
|
+
"${RULEMETRIC_API_URL}/api/sessions/${RW_SESSION_ID}/events" \
|
|
78
|
+
"$AUTH_HEADER" "$PAYLOAD" "post-tool-use" "$SEQ" &
|
package/scripts/user-prompt.sh
CHANGED
|
@@ -9,10 +9,8 @@ trap 'echo "rulemetric user-prompt failed at line $LINENO: $BASH_COMMAND" >&2; _
|
|
|
9
9
|
# Load config (env vars, ~/.config/rulemetric/env, or .env.local)
|
|
10
10
|
source "$SCRIPT_DIR/_config.sh" 2>/dev/null || true
|
|
11
11
|
|
|
12
|
-
# Read hook input from stdin and normalize across tools
|
|
13
|
-
|
|
14
|
-
INPUT=""
|
|
15
|
-
IFS= read -r -d '' -t 2 INPUT || true
|
|
12
|
+
# Read hook input from stdin and normalize across tools
|
|
13
|
+
INPUT=$(cat)
|
|
16
14
|
source "$SCRIPT_DIR/_normalize.sh"
|
|
17
15
|
|
|
18
16
|
if [ -z "$HOOK_SESSION_ID" ] || [ -z "${RULEMETRIC_API_URL:-}" ]; then
|
|
@@ -41,15 +39,7 @@ fi
|
|
|
41
39
|
# Increment sequence counter (atomic mkdir lock, works on macOS + Linux)
|
|
42
40
|
SEQ_FILE="$TEMP_DIR/$HOOK_SESSION_ID.seq"
|
|
43
41
|
LOCK_DIR="$TEMP_DIR/$HOOK_SESSION_ID.seqlock"
|
|
44
|
-
|
|
45
|
-
while ! mkdir "$LOCK_DIR" 2>/dev/null; do
|
|
46
|
-
sleep 0.01
|
|
47
|
-
SEQ_LOCK_TRIES=$((SEQ_LOCK_TRIES + 1))
|
|
48
|
-
if [ "$SEQ_LOCK_TRIES" -ge 500 ]; then
|
|
49
|
-
_rulemetric_log "user-prompt" "skip" "seq_lock_timeout"
|
|
50
|
-
exit 0
|
|
51
|
-
fi
|
|
52
|
-
done
|
|
42
|
+
while ! mkdir "$LOCK_DIR" 2>/dev/null; do sleep 0.01; done
|
|
53
43
|
SEQ=1
|
|
54
44
|
if [ -f "$SEQ_FILE" ]; then
|
|
55
45
|
SEQ=$(( $(cat "$SEQ_FILE") + 1 ))
|
|
@@ -57,16 +47,13 @@ fi
|
|
|
57
47
|
echo "$SEQ" > "$SEQ_FILE"
|
|
58
48
|
rmdir "$LOCK_DIR"
|
|
59
49
|
|
|
60
|
-
# Fire-and-forget
|
|
61
|
-
(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
BODY=$(echo "$CURL_OUT" | sed '$d')
|
|
68
|
-
_rulemetric_log "user-prompt" "error" "http_${HTTP_CODE}_seq_${SEQ}: ${BODY}"
|
|
69
|
-
fi) &
|
|
50
|
+
# Fire-and-forget event POST, with bounded retry on a transient 503/429 so the
|
|
51
|
+
# admission gate (db-wedge plan Phase 1) sheds load without dropping the event.
|
|
52
|
+
PAYLOAD="{\"sequence\":$SEQ,\"eventType\":\"user_message\",\"content\":$(printf '%s' "$HOOK_PROMPT" | jq -Rs .),\"timestamp\":\"$(date -u +%Y-%m-%dT%H:%M:%S.000Z)\"}"
|
|
53
|
+
source "$SCRIPT_DIR/_post.sh"
|
|
54
|
+
_rulemetric_post_event \
|
|
55
|
+
"${RULEMETRIC_API_URL}/api/sessions/${RW_SESSION_ID}/events" \
|
|
56
|
+
"$AUTH_HEADER" "$PAYLOAD" "user-prompt" "$SEQ" &
|
|
70
57
|
|
|
71
58
|
_rulemetric_log "user-prompt" "success"
|
|
72
59
|
|