From ed488daa2311824c61f26a2fc17bede96e5de4da Mon Sep 17 00:00:00 2001 From: Pranay Pratyush Date: Sat, 29 Aug 2026 18:01:45 +0530 Subject: [PATCH 1/5] fix(remote): relay autonomous secondmate status --- bin/fm-procevent-remote-reply.sh | 20 ++++++--- docs/remote-secondmates.md | 2 +- tests/fm-remote-reply.test.sh | 76 +++++++++++++++++++++++++++----- 3 files changed, 80 insertions(+), 18 deletions(-) diff --git a/bin/fm-procevent-remote-reply.sh b/bin/fm-procevent-remote-reply.sh index 6ee985cb05e..969eb891ced 100755 --- a/bin/fm-procevent-remote-reply.sh +++ b/bin/fm-procevent-remote-reply.sh @@ -17,9 +17,11 @@ # cursor-anchored source. A continuity break is escalated and not re-armed. # # Ingest accepts only bounded, printable status lines with an allowed lifecycle -# verb and corr=<16hex>. Exact lines are appended at most once to the parent's -# state/.status. A data/*.md pointer is fetched through the path-confined -# remote file reader and rewritten to its local private copy before append. +# verb. Autonomous lifecycle reports need no correlation token, but only a +# corr=<16hex> report can resolve a matching pending parent request. Exact lines +# are appended at most once to the parent's state/.status. A data/*.md +# pointer is fetched through the path-confined remote file reader and rewritten +# to its local private copy before append. set -u SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -245,8 +247,14 @@ line_valid() { # bytes=$(printf '%s' "$line" | LC_ALL=C wc -c | tr -d ' ') [ "$bytes" -le "$MAX_LINE_BYTES" ] || return 1 [ -z "$(printf '%s' "$line" | LC_ALL=C tr -d '\11\40-\176')" ] || return 1 - printf '%s' "$line" | grep -Eq '^(working|needs-decision|blocked|paused|done|failed|resolved)([[:space:]]+\[[^]]+\])?:' || return 1 - printf '%s' "$line" | grep -Eq 'corr=[A-Fa-f0-9]{16}' + printf '%s' "$line" | grep -Eq '^(working|needs-decision|blocked|paused|done|failed|resolved)([[:space:]]+\[[^]]+\])?:' +} + +payload_lines_valid() { # + local line + while IFS= read -r line || [ -n "$line" ]; do + line_valid "$line" || return 1 + done < "$1" } cmd_ingest() { @@ -303,8 +311,8 @@ cmd_ingest() { return 3 fi [ "$status" = delta ] && [ "$payload_bytes" -gt 0 ] || { fm_lock_release "$lock"; die "delta result has no payload"; } + payload_lines_valid "$payload" || { fm_lock_release "$lock"; die "delta contains an invalid status line"; } while IFS= read -r line || [ -n "$line" ]; do - line_valid "$line" || { fm_lock_release "$lock"; die "delta contains an invalid or uncorrelated status line"; } rewritten=$line while IFS= read -r doc; do [ -n "$doc" ] || continue diff --git a/docs/remote-secondmates.md b/docs/remote-secondmates.md index 84dda2e1aed..a9f1b7183cc 100644 --- a/docs/remote-secondmates.md +++ b/docs/remote-secondmates.md @@ -188,7 +188,7 @@ The primary records its own durable marker and watcher wake for either verdict, Marked requests keep the existing correlation contract. The remote charter appends replies to `state/parent-replies.status` in the remote home. -A process-event source performs a non-destructive, cursor-anchored delta read, validates bounded correlated status lines, fetches only referenced `data/*.md` documents through the confined reader, and appends each accepted line at most once to the primary status channel. +A process-event source performs a non-destructive, cursor-anchored delta read, validates bounded lifecycle status lines, resolves marked parent requests only from their exact correlated reports, fetches only referenced `data/*.md` documents through the confined reader, and appends each accepted line at most once to the primary status channel. The source log is never truncated or consumed. A shortened or changed prefix stops the relay and surfaces a continuity failure instead of silently resetting the cursor. diff --git a/tests/fm-remote-reply.test.sh b/tests/fm-remote-reply.test.sh index b8eee2c8a39..6690bfe431a 100755 --- a/tests/fm-remote-reply.test.sh +++ b/tests/fm-remote-reply.test.sh @@ -4,6 +4,8 @@ set -u # shellcheck source=tests/lib.sh . "$(dirname "${BASH_SOURCE[0]}")/lib.sh" +# shellcheck source=bin/fm-pending-reply-lib.sh +. "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)/bin/fm-pending-reply-lib.sh" ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P) TMP_ROOT=$(fm_test_tmproot fm-remote-reply) @@ -173,14 +175,66 @@ assert_contains "$out" 'handled: remote-reply-ios 2' "earlier generation remaine || fail "earlier generation replay duplicated its parent status" pass "later generations cannot invalidate an unacknowledged ingested result" -# A digest-valid but uncorrelated line is still rejected at the public ingest +# Autonomous lifecycle reports are valid status input but cannot resolve a +# marked parent request. The handled generation must still advance and re-arm. +AUTONOMOUS_CORR=$(fm_pending_reply_create "$PARENT" "$PARENT/state" ios "await autonomous report") +fm_pending_reply_mark_delivered "$PARENT/state" "$AUTONOMOUS_CORR" \ + || fail "could not create autonomous reply expectation" +printf 'blocked [key=remote-review]: waiting for external review\n' \ + >> "$REMOTE/state/parent-replies.status" +remote_env "$ROOT/bin/fm-procevent.sh" start "$SID" >/dev/null \ + || fail "autonomous reply generation was not captured" +RESULT_FOUR="$PARENT/state/procevent-inbox/$SID.4.result" +out=$(remote_env "$ADAPTER" handle ios 4 "$RESULT_FOUR") +assert_contains "$out" 'ingested: ios appended=1' "autonomous report was not ingested" +assert_contains "$out" 'handled: remote-reply-ios 4' "autonomous capture was not acknowledged" +assert_grep 'blocked [key=remote-review]: waiting for external review' "$PARENT/state/ios.status" \ + "autonomous lifecycle report did not reach parent status" +[ "$(fm_pending_reply_get "$(fm_pending_reply_path "$PARENT/state" "$AUTONOMOUS_CORR")" phase)" = awaiting_report ] \ + || fail "autonomous lifecycle report resolved a marked parent request" +assert_present "$PARENT/state/procevent/$SID.source" "autonomous handling did not re-arm the source" +pass "autonomous lifecycle reports ingest without resolving marked requests" + +# One captured delta may mix autonomous lifecycle reports with a correlated +# parent reply. It must preserve line order, resolve only the exact request, +# advance the cursor, acknowledge the capture, and re-arm normally. +MATCHING_CORR=$(fm_pending_reply_create "$PARENT" "$PARENT/state" ios "await matching report") +WRONG_CORR=$(fm_pending_reply_create "$PARENT" "$PARENT/state" ios "await different report") +fm_pending_reply_mark_delivered "$PARENT/state" "$MATCHING_CORR" \ + || fail "could not create matching reply expectation" +fm_pending_reply_mark_delivered "$PARENT/state" "$WRONG_CORR" \ + || fail "could not create wrong-correlation expectation" +printf 'blocked [key=remote-build]: remote build needs an approval\nresolved [key=remote-build]: approval arrived\ndone [corr=%s]: remote build passed\n' "$MATCHING_CORR" \ + >> "$REMOTE/state/parent-replies.status" +remote_env "$ROOT/bin/fm-procevent.sh" start "$SID" >/dev/null \ + || fail "mixed reply generation was not captured" +RESULT_FIVE="$PARENT/state/procevent-inbox/$SID.5.result" +out=$(remote_env "$ADAPTER" handle ios 5 "$RESULT_FIVE") +assert_contains "$out" 'ingested: ios appended=3' "mixed reply generation was not ingested as one delta" +assert_contains "$out" 'handled: remote-reply-ios 5' "mixed capture was not acknowledged" +blocked_line=$(grep -nF 'blocked [key=remote-build]: remote build needs an approval' "$PARENT/state/ios.status" | cut -d: -f1) +resolved_line=$(grep -nF 'resolved [key=remote-build]: approval arrived' "$PARENT/state/ios.status" | cut -d: -f1) +done_line=$(grep -nF "done [corr=$MATCHING_CORR]: remote build passed" "$PARENT/state/ios.status" | cut -d: -f1) +[ "$blocked_line" -lt "$resolved_line" ] && [ "$resolved_line" -lt "$done_line" ] \ + || fail "mixed reply generation did not preserve status-line order" +[ "$(fm_pending_reply_get "$(fm_pending_reply_path "$PARENT/state" "$MATCHING_CORR")" phase)" = resolved ] \ + || fail "matching correlated reply did not resolve its parent request" +[ "$(fm_pending_reply_get "$(fm_pending_reply_path "$PARENT/state" "$WRONG_CORR")" phase)" = awaiting_report ] \ + || fail "wrong correlation resolved a different parent request" +mixed_offset=$(LC_ALL=C wc -c < "$REMOTE/state/parent-replies.status" | tr -d ' ') +assert_grep "offset=$mixed_offset" "$PARENT/state/remote-replies/ios.cursor" \ + "mixed reply generation did not advance the cursor" +assert_present "$PARENT/state/procevent/$SID.source" "mixed handling did not re-arm the source" +pass "mixed autonomous and correlated reports preserve exact resolution and cursor continuity" + +# A digest-valid unknown lifecycle verb is still rejected at the public ingest # boundary. Recalculate its payload commitment so the behavioral assertion is # specifically about status validation, not incidental digest failure. BAD_RESULT="$TMP_ROOT/bad.result" cp "$RESULT" "$BAD_RESULT" boundary=$(grep -n -m 1 '^$' "$BAD_RESULT" | cut -d: -f1) tail -n "+$((boundary + 1))" "$BAD_RESULT" \ - | sed 's/corr=0123456789abcdef/no-correlation/' > "$TMP_ROOT/bad.payload" + | sed 's/^done /unknown /' > "$TMP_ROOT/bad.payload" bad_bytes=$(LC_ALL=C wc -c < "$TMP_ROOT/bad.payload" | tr -d ' ') bad_hash=$(sha256_file "$TMP_ROOT/bad.payload") head -n "$boundary" "$BAD_RESULT" \ @@ -188,11 +242,11 @@ head -n "$boundary" "$BAD_RESULT" \ > "$TMP_ROOT/bad.header" cat "$TMP_ROOT/bad.header" "$TMP_ROOT/bad.payload" > "$BAD_RESULT" if remote_env "$ADAPTER" ingest ios "$BAD_RESULT" >/dev/null 2>&1; then - fail "ingest accepted a status line with no correlation token" + fail "ingest accepted a status line with an unknown lifecycle verb" fi [ "$(grep -cF 'done [corr=0123456789abcdef]' "$PARENT/state/ios.status")" -eq 1 ] \ || fail "invalid ingest disturbed the accepted parent status line" -pass "ingest rejects uncorrelated payload even when its transport digest is valid" +pass "ingest rejects invalid lifecycle payloads even when their transport digest is valid" # The adapter re-armed at the committed cursor. Truncation is detected from the # next blocking source and escalated once; it is never silently treated as a new @@ -201,23 +255,23 @@ printf 'failed [corr=fedcba9876543210]: source was replaced\n' > "$REMOTE/state/ remote_env "$ROOT/bin/fm-procevent.sh" start "$SID" > "$TMP_ROOT/start-two.out" 2>&1 & RUNNER=$! wait "$RUNNER" || fail "continuity break was not captured as a structured result" -RESULT_FOUR=$(find "$PARENT/state/procevent-inbox" -name "$SID.4.result" -print -quit) -[ -n "$RESULT_FOUR" ] || fail "continuity break produced no durable result" -[ "$(remote_env "$ADAPTER" classify "$RESULT_FOUR")" = continuity-broken ] \ +RESULT_SIX=$(find "$PARENT/state/procevent-inbox" -name "$SID.6.result" -print -quit) +[ -n "$RESULT_SIX" ] || fail "continuity break produced no durable result" +[ "$(remote_env "$ADAPTER" classify "$RESULT_SIX")" = continuity-broken ] \ || fail "truncated source was not classified as a continuity break" set +e -remote_env "$ADAPTER" handle ios 4 "$RESULT_FOUR" > "$TMP_ROOT/handle-four.out" 2>&1 +remote_env "$ADAPTER" handle ios 6 "$RESULT_SIX" > "$TMP_ROOT/handle-six.out" 2>&1 handle_rc=$? set -e [ "$handle_rc" -eq 3 ] || fail "continuity handling returned an unexpected status: $handle_rc" assert_grep 'blocked [key=remote-reply-continuity-ios]' "$PARENT/state/ios.status" "continuity break did not escalate" assert_absent "$PARENT/state/procevent/$SID.source" "continuity break was re-armed without an operator rebase" -remote_env "$ADAPTER" ingest ios "$RESULT_FOUR" >/dev/null 2>&1 || true +remote_env "$ADAPTER" ingest ios "$RESULT_SIX" >/dev/null 2>&1 || true [ "$(grep -cF 'blocked [key=remote-reply-continuity-ios]' "$PARENT/state/ios.status")" -eq 1 ] \ || fail "continuity replay duplicated the escalation" pass "truncation is detected, escalated once, and not silently rebased" -rm -f "$PARENT/state/procevent-inbox/$SID.4.handled" +rm -f "$PARENT/state/procevent-inbox/$SID.6.handled" if remote_env "$ADAPTER" retire ios > "$TMP_ROOT/retire-pending.out" 2>&1; then fail "remote reply retirement accepted an unhandled captured result" fi @@ -225,7 +279,7 @@ assert_grep 'unhandled captured result' "$TMP_ROOT/retire-pending.out" \ "remote reply retirement did not explain its pending-result refusal" assert_absent "$PARENT/state/procevent/$SID.source" \ "refused retirement left the reply source running past its pending-result check" -remote_env "$ADAPTER" handle ios 4 "$RESULT_FOUR" >/dev/null 2>&1 || [ "$?" -eq 3 ] \ +remote_env "$ADAPTER" handle ios 6 "$RESULT_SIX" >/dev/null 2>&1 || [ "$?" -eq 3 ] \ || fail "pending continuity result could not be acknowledged after retirement refusal" remote_env "$ADAPTER" retire ios >/dev/null assert_absent "$PARENT/state/remote-replies/ios.cursor" "adapter retirement left its cursor" From affcc26c4d8597d124f8df3999785c2d6e6e3903 Mon Sep 17 00:00:00 2001 From: Pranay Pratyush Date: Sat, 29 Aug 2026 18:10:20 +0530 Subject: [PATCH 2/5] no-mistakes(review): Enforce exact remote reply correlation boundaries --- bin/fm-pending-reply-lib.sh | 10 ++++------ tests/fm-remote-reply.test.sh | 28 ++++++++++++++++++---------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/bin/fm-pending-reply-lib.sh b/bin/fm-pending-reply-lib.sh index e9bc511dd45..b44fe21edd9 100755 --- a/bin/fm-pending-reply-lib.sh +++ b/bin/fm-pending-reply-lib.sh @@ -135,12 +135,10 @@ fm_pending_reply_extract_corr() { # # 0 if carries the exact correlation token for . fm_pending_reply_text_has_corr() { # - local text=$1 corr=$2 token - token=$(fm_pending_reply_corr_token "$corr") - case "$text" in - *"$token"*) return 0 ;; - esac - return 1 + local text=$1 corr=$2 + printf '%s' "$corr" | grep -Eq '^[A-Fa-f0-9]{16}$' || return 1 + printf '%s\n' "$text" \ + | LC_ALL=C grep -Eq "(^|[^[:alnum:]_])corr=${corr}([^[:alnum:]_]|$)" } # Sanitize a short request summary: single line, bounded, no control chars. diff --git a/tests/fm-remote-reply.test.sh b/tests/fm-remote-reply.test.sh index 6690bfe431a..5820212931b 100755 --- a/tests/fm-remote-reply.test.sh +++ b/tests/fm-remote-reply.test.sh @@ -95,10 +95,13 @@ SID=$(remote_env "$ADAPTER" source-id ios) out=$(remote_env "$ADAPTER" arm ios) assert_contains "$out" "armed: $SID offset=0" "remote reply source was not armed at the empty cursor" +INITIAL_CORR=$(fm_pending_reply_create "$PARENT" "$PARENT/state" ios "await initial correlated report") +fm_pending_reply_mark_delivered "$PARENT/state" "$INITIAL_CORR" \ + || fail "could not create initial reply expectation" remote_env "$ROOT/bin/fm-procevent.sh" start "$SID" > "$TMP_ROOT/start-one.out" 2>&1 & RUNNER=$! wait_for "$CLAIMS/$SID.claim" || fail "process-event runner never claimed the remote reply source" -printf 'done [corr=0123456789abcdef]: build verified (data/reply/report.md)\n' \ +printf 'done [corr=%s]: build verified (data/reply/report.md)\n' "$INITIAL_CORR" \ >> "$REMOTE/state/parent-replies.status" wait "$RUNNER" || fail "remote reply source failed to capture its first delta" RESULT=$(find "$PARENT/state/procevent-inbox" -name "$SID.1.result" -print -quit 2>/dev/null) @@ -106,7 +109,7 @@ if [ -z "$RESULT" ]; then printf 'runner output:\n%s\n' "$(cat "$TMP_ROOT/start-one.out")" >&2 fail "the remote reply delta was not durably captured" fi -assert_grep 'done [corr=0123456789abcdef]' "$RESULT" "captured delta lost the correlated status line" +assert_grep "done [corr=$INITIAL_CORR]" "$RESULT" "captured delta lost the correlated status line" assert_grep "procevent remote-reply $SID 1" "$PARENT/state/.wake-queue" "runner did not publish the normalized remote-reply event" assert_no_grep 'build verified' "$PARENT/state/.wake-queue" "reply payload leaked into the event queue" cmp -s "$SOURCE_BEFORE" "$REMOTE/state/parent-replies.status" \ @@ -122,7 +125,7 @@ remote_env "$ADAPTER" handle ios 1 "$RESULT" > "$TMP_ROOT/handle-arm-fail.out" 2 handle_arm_rc=$? set -e [ "$handle_arm_rc" -ne 0 ] || fail "reply handling acknowledged a result whose re-arm failed" -assert_grep 'done [corr=0123456789abcdef]' "$PARENT/state/ios.status" "failed re-arm lost the ingested reply" +assert_grep "done [corr=$INITIAL_CORR]" "$PARENT/state/ios.status" "failed re-arm lost the ingested reply" assert_grep 'ingested: ios appended=1' "$TMP_ROOT/handle-arm-fail.out" "failed re-arm did not commit the reply before retry" rm -f "$PARENT/state/procevent" mkdir "$PARENT/state/procevent" @@ -131,8 +134,10 @@ assert_contains "$reconcile_out" 'published=1' "failed re-arm did not leave the out=$(remote_env "$ADAPTER" handle ios 1 "$RESULT") assert_contains "$out" 'ingested: ios appended=0' "retried reply ingest was not idempotent" assert_contains "$out" 'handled: remote-reply-ios 1' "captured generation was not acknowledged" -assert_grep 'done [corr=0123456789abcdef]' "$PARENT/state/ios.status" "parent status did not receive the correlated reply" +assert_grep "done [corr=$INITIAL_CORR]" "$PARENT/state/ios.status" "parent status did not receive the correlated reply" assert_grep 'data/remote-secondmates/ios/data/reply/report.md' "$PARENT/state/ios.status" "remote document pointer was not rewritten locally" +[ "$(fm_pending_reply_get "$(fm_pending_reply_path "$PARENT/state" "$INITIAL_CORR")" phase)" = resolved ] \ + || fail "exact correlated report did not resolve its parent request" cmp -s "$REMOTE/data/reply/report.md" "$PARENT/data/remote-secondmates/ios/data/reply/report.md" \ || fail "the path-confined remote document copy is not byte-identical" cmp -s "$SOURCE_AFTER" "$REMOTE/state/parent-replies.status" \ @@ -144,7 +149,7 @@ pass "ingest appends one validated line, fetches its document, and advances the out=$(remote_env "$ADAPTER" handle ios 1 "$RESULT") assert_contains "$out" 'ingested: ios appended=0' "replayed result was not deduplicated" assert_contains "$out" 'already-handled: remote-reply-ios 1' "replayed generation was not acknowledged idempotently" -[ "$(grep -cF 'done [corr=0123456789abcdef]' "$PARENT/state/ios.status")" -eq 1 ] \ +[ "$(grep -cF "done [corr=$INITIAL_CORR]" "$PARENT/state/ios.status")" -eq 1 ] \ || fail "replayed ingest duplicated the parent status line" pass "replayed capture has one deduplicated append and one durable handling identity" @@ -176,22 +181,25 @@ assert_contains "$out" 'handled: remote-reply-ios 2' "earlier generation remaine pass "later generations cannot invalidate an unacknowledged ingested result" # Autonomous lifecycle reports are valid status input but cannot resolve a -# marked parent request. The handled generation must still advance and re-arm. +# marked parent request, even when a corr-like substring names it. +# The handled generation must still advance and re-arm. AUTONOMOUS_CORR=$(fm_pending_reply_create "$PARENT" "$PARENT/state" ios "await autonomous report") fm_pending_reply_mark_delivered "$PARENT/state" "$AUTONOMOUS_CORR" \ || fail "could not create autonomous reply expectation" -printf 'blocked [key=remote-review]: waiting for external review\n' \ +printf 'blocked [key=remote-review]: waiting for external review\ndone: notcorr=%s is not a parent reply\n' "$AUTONOMOUS_CORR" \ >> "$REMOTE/state/parent-replies.status" remote_env "$ROOT/bin/fm-procevent.sh" start "$SID" >/dev/null \ || fail "autonomous reply generation was not captured" RESULT_FOUR="$PARENT/state/procevent-inbox/$SID.4.result" out=$(remote_env "$ADAPTER" handle ios 4 "$RESULT_FOUR") -assert_contains "$out" 'ingested: ios appended=1' "autonomous report was not ingested" +assert_contains "$out" 'ingested: ios appended=2' "autonomous reports were not ingested" assert_contains "$out" 'handled: remote-reply-ios 4' "autonomous capture was not acknowledged" assert_grep 'blocked [key=remote-review]: waiting for external review' "$PARENT/state/ios.status" \ "autonomous lifecycle report did not reach parent status" +assert_grep "done: notcorr=$AUTONOMOUS_CORR is not a parent reply" "$PARENT/state/ios.status" \ + "corr-like autonomous report did not reach parent status" [ "$(fm_pending_reply_get "$(fm_pending_reply_path "$PARENT/state" "$AUTONOMOUS_CORR")" phase)" = awaiting_report ] \ - || fail "autonomous lifecycle report resolved a marked parent request" + || fail "corr-like autonomous lifecycle report resolved a marked parent request" assert_present "$PARENT/state/procevent/$SID.source" "autonomous handling did not re-arm the source" pass "autonomous lifecycle reports ingest without resolving marked requests" @@ -244,7 +252,7 @@ cat "$TMP_ROOT/bad.header" "$TMP_ROOT/bad.payload" > "$BAD_RESULT" if remote_env "$ADAPTER" ingest ios "$BAD_RESULT" >/dev/null 2>&1; then fail "ingest accepted a status line with an unknown lifecycle verb" fi -[ "$(grep -cF 'done [corr=0123456789abcdef]' "$PARENT/state/ios.status")" -eq 1 ] \ +[ "$(grep -cF "done [corr=$INITIAL_CORR]" "$PARENT/state/ios.status")" -eq 1 ] \ || fail "invalid ingest disturbed the accepted parent status line" pass "ingest rejects invalid lifecycle payloads even when their transport digest is valid" From ca6c95f18c8275e5a9f00d50579f09511e2ec408 Mon Sep 17 00:00:00 2001 From: Pranay Pratyush Date: Sat, 29 Aug 2026 18:21:49 +0530 Subject: [PATCH 3/5] no-mistakes(review): Harden remote reply correlation parsing --- bin/fm-pending-reply-lib.sh | 19 ++++++++++++++++--- bin/fm-procevent-remote-reply.sh | 6 +++++- docs/scripts.md | 2 +- tests/fm-remote-reply.test.sh | 21 +++++++++++++-------- 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/bin/fm-pending-reply-lib.sh b/bin/fm-pending-reply-lib.sh index b44fe21edd9..acf74c4f8d4 100755 --- a/bin/fm-pending-reply-lib.sh +++ b/bin/fm-pending-reply-lib.sh @@ -133,12 +133,25 @@ fm_pending_reply_extract_corr() { # printf '%s' "$text" | grep -oE "$FM_PENDING_REPLY_CORR_RE" 2>/dev/null | head -1 | cut -d= -f2- | tr 'A-F' 'a-f' || true } +# Print exact corr=<16hex> status tokens, canonicalized to lowercase. +fm_pending_reply_extract_status_corrs() { # + local text=$1 + printf '%s\n' "$text" \ + | tr $'\t []()' '\n' \ + | grep -E "^${FM_PENDING_REPLY_CORR_RE}$" 2>/dev/null \ + | cut -d= -f2- \ + | tr 'A-F' 'a-f' || true +} + # 0 if carries the exact correlation token for . fm_pending_reply_text_has_corr() { # - local text=$1 corr=$2 + local text=$1 corr=$2 candidate printf '%s' "$corr" | grep -Eq '^[A-Fa-f0-9]{16}$' || return 1 - printf '%s\n' "$text" \ - | LC_ALL=C grep -Eq "(^|[^[:alnum:]_])corr=${corr}([^[:alnum:]_]|$)" + corr=$(printf '%s' "$corr" | tr 'A-F' 'a-f') + while IFS= read -r candidate; do + [ "$candidate" = "$corr" ] && return 0 + done < <(fm_pending_reply_extract_status_corrs "$text") + return 1 } # Sanitize a short request summary: single line, bounded, no control chars. diff --git a/bin/fm-procevent-remote-reply.sh b/bin/fm-procevent-remote-reply.sh index 969eb891ced..13db201e48c 100755 --- a/bin/fm-procevent-remote-reply.sh +++ b/bin/fm-procevent-remote-reply.sh @@ -327,7 +327,11 @@ cmd_ingest() { while IFS= read -r corr; do [ -n "$corr" ] || continue fm_pending_reply_try_resolve "$STATE" "$corr" "$status_file" >/dev/null 2>&1 || true - done < <(grep -Eo 'corr=[A-Fa-f0-9]{16}' "$payload" | cut -d= -f2- | tr 'A-F' 'a-f' | awk '!seen[$0]++') + done < <( + while IFS= read -r line || [ -n "$line" ]; do + fm_pending_reply_extract_status_corrs "$line" + done < "$payload" | awk '!seen[$0]++' + ) if [ -n "$seq" ]; then write_ingest_receipt "$id" "$seq" "$result" \ || { fm_lock_release "$lock"; die "cannot commit remote reply ingestion receipt"; } diff --git a/docs/scripts.md b/docs/scripts.md index 0af5f67c2e1..c7ea7b81ca8 100644 --- a/docs/scripts.md +++ b/docs/scripts.md @@ -77,7 +77,7 @@ The shared no-mistakes gate refusal for fleet lifecycle entrypoints is summarize | `fm-pending-reply-lib.sh` | Parent-owned secondmate pending-reply expectations, recovery, and one-shot escalation | | `fm-secondmate-parent-lib.sh` | Parse durable secondmate parent-route binding records | | `fm-secondmate-report.sh` | Optional helper to append a correlated parent status or document-pointer report | -| `fm-procevent-remote-reply.sh` | Relay non-destructive correlated remote-secondmate reply deltas through process events | +| `fm-procevent-remote-reply.sh` | Relay non-destructive remote-secondmate lifecycle deltas and resolve exact correlated pending replies through process events | | `fm-gate-refuse-lib.sh` | Shared no-mistakes gate-context refusal for fleet lifecycle entrypoints | | `fm-primary-watch-core.ts` | Harness-neutral watcher lifecycle core bound by the Pi and OMP primary extensions (docs/watcher-continuity.md) | | `fm-primary-watch-version-lib.sh` | The one definition of a primary watcher marker version, hashing that adapter plus the shared core | diff --git a/tests/fm-remote-reply.test.sh b/tests/fm-remote-reply.test.sh index 5820212931b..c0d5927d372 100755 --- a/tests/fm-remote-reply.test.sh +++ b/tests/fm-remote-reply.test.sh @@ -98,10 +98,11 @@ assert_contains "$out" "armed: $SID offset=0" "remote reply source was not armed INITIAL_CORR=$(fm_pending_reply_create "$PARENT" "$PARENT/state" ios "await initial correlated report") fm_pending_reply_mark_delivered "$PARENT/state" "$INITIAL_CORR" \ || fail "could not create initial reply expectation" +INITIAL_CORR_UPPER=$(printf '%s' "$INITIAL_CORR" | tr 'a-f' 'A-F') remote_env "$ROOT/bin/fm-procevent.sh" start "$SID" > "$TMP_ROOT/start-one.out" 2>&1 & RUNNER=$! wait_for "$CLAIMS/$SID.claim" || fail "process-event runner never claimed the remote reply source" -printf 'done [corr=%s]: build verified (data/reply/report.md)\n' "$INITIAL_CORR" \ +printf 'done [corr=%s]: build verified (data/reply/report.md)\n' "$INITIAL_CORR_UPPER" \ >> "$REMOTE/state/parent-replies.status" wait "$RUNNER" || fail "remote reply source failed to capture its first delta" RESULT=$(find "$PARENT/state/procevent-inbox" -name "$SID.1.result" -print -quit 2>/dev/null) @@ -109,7 +110,7 @@ if [ -z "$RESULT" ]; then printf 'runner output:\n%s\n' "$(cat "$TMP_ROOT/start-one.out")" >&2 fail "the remote reply delta was not durably captured" fi -assert_grep "done [corr=$INITIAL_CORR]" "$RESULT" "captured delta lost the correlated status line" +assert_grep "done [corr=$INITIAL_CORR_UPPER]" "$RESULT" "captured delta lost the correlated status line" assert_grep "procevent remote-reply $SID 1" "$PARENT/state/.wake-queue" "runner did not publish the normalized remote-reply event" assert_no_grep 'build verified' "$PARENT/state/.wake-queue" "reply payload leaked into the event queue" cmp -s "$SOURCE_BEFORE" "$REMOTE/state/parent-replies.status" \ @@ -125,7 +126,7 @@ remote_env "$ADAPTER" handle ios 1 "$RESULT" > "$TMP_ROOT/handle-arm-fail.out" 2 handle_arm_rc=$? set -e [ "$handle_arm_rc" -ne 0 ] || fail "reply handling acknowledged a result whose re-arm failed" -assert_grep "done [corr=$INITIAL_CORR]" "$PARENT/state/ios.status" "failed re-arm lost the ingested reply" +assert_grep "done [corr=$INITIAL_CORR_UPPER]" "$PARENT/state/ios.status" "failed re-arm lost the ingested reply" assert_grep 'ingested: ios appended=1' "$TMP_ROOT/handle-arm-fail.out" "failed re-arm did not commit the reply before retry" rm -f "$PARENT/state/procevent" mkdir "$PARENT/state/procevent" @@ -134,7 +135,7 @@ assert_contains "$reconcile_out" 'published=1' "failed re-arm did not leave the out=$(remote_env "$ADAPTER" handle ios 1 "$RESULT") assert_contains "$out" 'ingested: ios appended=0' "retried reply ingest was not idempotent" assert_contains "$out" 'handled: remote-reply-ios 1' "captured generation was not acknowledged" -assert_grep "done [corr=$INITIAL_CORR]" "$PARENT/state/ios.status" "parent status did not receive the correlated reply" +assert_grep "done [corr=$INITIAL_CORR_UPPER]" "$PARENT/state/ios.status" "parent status did not receive the correlated reply" assert_grep 'data/remote-secondmates/ios/data/reply/report.md' "$PARENT/state/ios.status" "remote document pointer was not rewritten locally" [ "$(fm_pending_reply_get "$(fm_pending_reply_path "$PARENT/state" "$INITIAL_CORR")" phase)" = resolved ] \ || fail "exact correlated report did not resolve its parent request" @@ -149,7 +150,7 @@ pass "ingest appends one validated line, fetches its document, and advances the out=$(remote_env "$ADAPTER" handle ios 1 "$RESULT") assert_contains "$out" 'ingested: ios appended=0' "replayed result was not deduplicated" assert_contains "$out" 'already-handled: remote-reply-ios 1' "replayed generation was not acknowledged idempotently" -[ "$(grep -cF "done [corr=$INITIAL_CORR]" "$PARENT/state/ios.status")" -eq 1 ] \ +[ "$(grep -cF "done [corr=$INITIAL_CORR_UPPER]" "$PARENT/state/ios.status")" -eq 1 ] \ || fail "replayed ingest duplicated the parent status line" pass "replayed capture has one deduplicated append and one durable handling identity" @@ -186,18 +187,22 @@ pass "later generations cannot invalidate an unacknowledged ingested result" AUTONOMOUS_CORR=$(fm_pending_reply_create "$PARENT" "$PARENT/state" ios "await autonomous report") fm_pending_reply_mark_delivered "$PARENT/state" "$AUTONOMOUS_CORR" \ || fail "could not create autonomous reply expectation" -printf 'blocked [key=remote-review]: waiting for external review\ndone: notcorr=%s is not a parent reply\n' "$AUTONOMOUS_CORR" \ +printf 'blocked [key=remote-review]: waiting for external review\ndone: notcorr=%s is not a parent reply\ndone: not-corr=%s is not a parent reply\ndone: not.corr=%s is not a parent reply\n' "$AUTONOMOUS_CORR" "$AUTONOMOUS_CORR" "$AUTONOMOUS_CORR" \ >> "$REMOTE/state/parent-replies.status" remote_env "$ROOT/bin/fm-procevent.sh" start "$SID" >/dev/null \ || fail "autonomous reply generation was not captured" RESULT_FOUR="$PARENT/state/procevent-inbox/$SID.4.result" out=$(remote_env "$ADAPTER" handle ios 4 "$RESULT_FOUR") -assert_contains "$out" 'ingested: ios appended=2' "autonomous reports were not ingested" +assert_contains "$out" 'ingested: ios appended=4' "autonomous reports were not ingested" assert_contains "$out" 'handled: remote-reply-ios 4' "autonomous capture was not acknowledged" assert_grep 'blocked [key=remote-review]: waiting for external review' "$PARENT/state/ios.status" \ "autonomous lifecycle report did not reach parent status" assert_grep "done: notcorr=$AUTONOMOUS_CORR is not a parent reply" "$PARENT/state/ios.status" \ "corr-like autonomous report did not reach parent status" +assert_grep "done: not-corr=$AUTONOMOUS_CORR is not a parent reply" "$PARENT/state/ios.status" \ + "punctuated corr-like autonomous report did not reach parent status" +assert_grep "done: not.corr=$AUTONOMOUS_CORR is not a parent reply" "$PARENT/state/ios.status" \ + "dotted corr-like autonomous report did not reach parent status" [ "$(fm_pending_reply_get "$(fm_pending_reply_path "$PARENT/state" "$AUTONOMOUS_CORR")" phase)" = awaiting_report ] \ || fail "corr-like autonomous lifecycle report resolved a marked parent request" assert_present "$PARENT/state/procevent/$SID.source" "autonomous handling did not re-arm the source" @@ -252,7 +257,7 @@ cat "$TMP_ROOT/bad.header" "$TMP_ROOT/bad.payload" > "$BAD_RESULT" if remote_env "$ADAPTER" ingest ios "$BAD_RESULT" >/dev/null 2>&1; then fail "ingest accepted a status line with an unknown lifecycle verb" fi -[ "$(grep -cF "done [corr=$INITIAL_CORR]" "$PARENT/state/ios.status")" -eq 1 ] \ +[ "$(grep -cF "done [corr=$INITIAL_CORR_UPPER]" "$PARENT/state/ios.status")" -eq 1 ] \ || fail "invalid ingest disturbed the accepted parent status line" pass "ingest rejects invalid lifecycle payloads even when their transport digest is valid" From edda17fda24f9a304b1324581a16a59074c46a84 Mon Sep 17 00:00:00 2001 From: Pranay Pratyush Date: Sat, 29 Aug 2026 18:26:57 +0530 Subject: [PATCH 4/5] no-mistakes(review): Require whole-token remote reply correlations --- bin/fm-pending-reply-lib.sh | 7 +++++-- tests/fm-remote-reply.test.sh | 6 ++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/bin/fm-pending-reply-lib.sh b/bin/fm-pending-reply-lib.sh index acf74c4f8d4..517f2323f7e 100755 --- a/bin/fm-pending-reply-lib.sh +++ b/bin/fm-pending-reply-lib.sh @@ -137,8 +137,11 @@ fm_pending_reply_extract_corr() { # fm_pending_reply_extract_status_corrs() { # local text=$1 printf '%s\n' "$text" \ - | tr $'\t []()' '\n' \ - | grep -E "^${FM_PENDING_REPLY_CORR_RE}$" 2>/dev/null \ + | tr $'\t ' '\n' \ + | sed -nE \ + -e 's/^(corr=[A-Fa-f0-9]{16})$/\1/p' \ + -e 's/^\[(corr=[A-Fa-f0-9]{16})\]:?$/\1/p' \ + -e 's/^\((corr=[A-Fa-f0-9]{16})\)$/\1/p' \ | cut -d= -f2- \ | tr 'A-F' 'a-f' || true } diff --git a/tests/fm-remote-reply.test.sh b/tests/fm-remote-reply.test.sh index c0d5927d372..374daeeb0f6 100755 --- a/tests/fm-remote-reply.test.sh +++ b/tests/fm-remote-reply.test.sh @@ -187,13 +187,13 @@ pass "later generations cannot invalidate an unacknowledged ingested result" AUTONOMOUS_CORR=$(fm_pending_reply_create "$PARENT" "$PARENT/state" ios "await autonomous report") fm_pending_reply_mark_delivered "$PARENT/state" "$AUTONOMOUS_CORR" \ || fail "could not create autonomous reply expectation" -printf 'blocked [key=remote-review]: waiting for external review\ndone: notcorr=%s is not a parent reply\ndone: not-corr=%s is not a parent reply\ndone: not.corr=%s is not a parent reply\n' "$AUTONOMOUS_CORR" "$AUTONOMOUS_CORR" "$AUTONOMOUS_CORR" \ +printf 'blocked [key=remote-review]: waiting for external review\ndone: notcorr=%s is not a parent reply\ndone: not-corr=%s is not a parent reply\ndone: not.corr=%s is not a parent reply\ndone: not[corr=%s] is not a parent reply\n' "$AUTONOMOUS_CORR" "$AUTONOMOUS_CORR" "$AUTONOMOUS_CORR" "$AUTONOMOUS_CORR" \ >> "$REMOTE/state/parent-replies.status" remote_env "$ROOT/bin/fm-procevent.sh" start "$SID" >/dev/null \ || fail "autonomous reply generation was not captured" RESULT_FOUR="$PARENT/state/procevent-inbox/$SID.4.result" out=$(remote_env "$ADAPTER" handle ios 4 "$RESULT_FOUR") -assert_contains "$out" 'ingested: ios appended=4' "autonomous reports were not ingested" +assert_contains "$out" 'ingested: ios appended=5' "autonomous reports were not ingested" assert_contains "$out" 'handled: remote-reply-ios 4' "autonomous capture was not acknowledged" assert_grep 'blocked [key=remote-review]: waiting for external review' "$PARENT/state/ios.status" \ "autonomous lifecycle report did not reach parent status" @@ -203,6 +203,8 @@ assert_grep "done: not-corr=$AUTONOMOUS_CORR is not a parent reply" "$PARENT/sta "punctuated corr-like autonomous report did not reach parent status" assert_grep "done: not.corr=$AUTONOMOUS_CORR is not a parent reply" "$PARENT/state/ios.status" \ "dotted corr-like autonomous report did not reach parent status" +assert_grep "done: not[corr=$AUTONOMOUS_CORR] is not a parent reply" "$PARENT/state/ios.status" \ + "embedded corr-like autonomous report did not reach parent status" [ "$(fm_pending_reply_get "$(fm_pending_reply_path "$PARENT/state" "$AUTONOMOUS_CORR")" phase)" = awaiting_report ] \ || fail "corr-like autonomous lifecycle report resolved a marked parent request" assert_present "$PARENT/state/procevent/$SID.source" "autonomous handling did not re-arm the source" From b821cd8bdc33f376533e7a1343fec4e8e0bd30bd Mon Sep 17 00:00:00 2001 From: Pranay Pratyush Date: Sat, 29 Aug 2026 18:33:26 +0530 Subject: [PATCH 5/5] no-mistakes(document): Document remote lifecycle reply ingestion --- bin/fm-procevent-remote-reply.sh | 6 +++--- docs/architecture.md | 3 ++- docs/remote-secondmates.md | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/bin/fm-procevent-remote-reply.sh b/bin/fm-procevent-remote-reply.sh index 13db201e48c..beb4b8acaaa 100755 --- a/bin/fm-procevent-remote-reply.sh +++ b/bin/fm-procevent-remote-reply.sh @@ -17,9 +17,9 @@ # cursor-anchored source. A continuity break is escalated and not re-armed. # # Ingest accepts only bounded, printable status lines with an allowed lifecycle -# verb. Autonomous lifecycle reports need no correlation token, but only a -# corr=<16hex> report can resolve a matching pending parent request. Exact lines -# are appended at most once to the parent's state/.status. A data/*.md +# verb. Autonomous lifecycle reports need no correlation token, but only an +# explicit exact correlation token can resolve a matching pending parent request. +# Exact lines are appended at most once to the parent's state/.status. A data/*.md # pointer is fetched through the path-confined remote file reader and rewritten # to its local private copy before append. set -u diff --git a/docs/architecture.md b/docs/architecture.md index ac0acb7d91e..9cb9c8f7e76 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -225,7 +225,8 @@ Explicit backend-target sends and direct human typing stay unmarked, so captain After seeding a secondmate, `fm-backlog-handoff.sh` validates the fleet-specific handoff, then atomically delegates already-judged in-scope queued item moves to `tasks-axi mv` so the domain queue starts in the right place. Remote routes move that dependency-closed set into a non-dispatchable backlog-format outbox before transfer, then use an idempotent remote receive under the destination backlog's own lock. The outbox is the complete retry record, so no two-phase journal or transport-level retry is needed. -Remote replies travel in the other direction through a non-destructive cursor-anchored log reader and the existing process-event runner, with deduplicated correlated append into the primary status channel. +Remote lifecycle reports travel in the other direction through a non-destructive cursor-anchored log reader and the existing process-event runner. +[`remote-secondmates.md`](remote-secondmates.md) owns the status-ingestion and correlation-resolution contract. An unreachable remote host is unknown rather than dead, preserves its route and durable work, and is never failed over or relaunched locally. Idle secondmate panes are healthy; teardown is explicit and refuses while the secondmate home has in-flight work unless the captain has approved discard with `--force`. diff --git a/docs/remote-secondmates.md b/docs/remote-secondmates.md index a9f1b7183cc..1c45ecad889 100644 --- a/docs/remote-secondmates.md +++ b/docs/remote-secondmates.md @@ -188,7 +188,7 @@ The primary records its own durable marker and watcher wake for either verdict, Marked requests keep the existing correlation contract. The remote charter appends replies to `state/parent-replies.status` in the remote home. -A process-event source performs a non-destructive, cursor-anchored delta read, validates bounded lifecycle status lines, resolves marked parent requests only from their exact correlated reports, fetches only referenced `data/*.md` documents through the confined reader, and appends each accepted line at most once to the primary status channel. +A process-event source performs a non-destructive, cursor-anchored delta read, validates bounded lifecycle status lines, resolves marked parent requests only from status lines carrying their explicit exact correlation token, fetches only referenced `data/*.md` documents through the confined reader, and appends each accepted line at most once to the primary status channel. The source log is never truncated or consumed. A shortened or changed prefix stops the relay and surfaces a continuity failure instead of silently resetting the cursor.