Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions bin/fm-pending-reply-lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,27 @@ fm_pending_reply_extract_corr() { # <text>
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() { # <text>
local text=$1
printf '%s\n' "$text" \
| 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
}

# 0 if <text> carries the exact correlation token for <corr_id>.
fm_pending_reply_text_has_corr() { # <text> <corr_id>
local text=$1 corr=$2 token
token=$(fm_pending_reply_corr_token "$corr")
case "$text" in
*"$token"*) return 0 ;;
esac
local text=$1 corr=$2 candidate
printf '%s' "$corr" | grep -Eq '^[A-Fa-f0-9]{16}$' || return 1
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
}

Expand Down
26 changes: 19 additions & 7 deletions bin/fm-procevent-remote-reply.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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/<id>.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 an
# explicit exact correlation token can resolve a matching pending parent request.
# Exact lines are appended at most once to the parent's state/<id>.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)"
Expand Down Expand Up @@ -245,8 +247,14 @@ line_valid() { # <line>
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() { # <payload>
local line
while IFS= read -r line || [ -n "$line" ]; do
line_valid "$line" || return 1
done < "$1"
}

cmd_ingest() {
Expand Down Expand Up @@ -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
Expand All @@ -319,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"; }
Expand Down
3 changes: 2 additions & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Expand Down
2 changes: 1 addition & 1 deletion docs/remote-secondmates.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 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.

Expand Down
2 changes: 1 addition & 1 deletion docs/scripts.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
Loading
Loading