From 916ad55350490471c27592d25b18a292c479b550 Mon Sep 17 00:00:00 2001 From: keenvc Date: Fri, 2 Oct 2026 04:56:04 +0000 Subject: [PATCH 1/2] docs: correct the portable hint-table coverage claims for this fork The hint tables' five measured CI runs are upstream's, and they cover the lane sizes upstream held when they ran. This fork's serial lane carries additional fork-only members no upstream run measured, so state that they pack on the PORTABLE_SERIAL_DEFAULT_WEIGHT_MS default until the fork refreshes its own hints, and point at bin/fm-test-run.sh --check-coverage for the live lane size and unmeasured share instead of a count copied here. Generalize the refresh command's -R owner placeholder to match. --- docs/fm-test-portable-shards.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/fm-test-portable-shards.md b/docs/fm-test-portable-shards.md index bbe07dfbd4b..71f65aab06a 100644 --- a/docs/fm-test-portable-shards.md +++ b/docs/fm-test-portable-shards.md @@ -12,7 +12,8 @@ Local timings are not interchangeable with CI timings: platform and machine load Both hint tables were refreshed on 2026-09-30 from five Ubuntu CI runs: [36583881812](https://github.com/kunchenguid/firstmate/actions/runs/36583881812), [36658498535](https://github.com/kunchenguid/firstmate/actions/runs/36658498535), [36663947738](https://github.com/kunchenguid/firstmate/actions/runs/36663947738), [36664663190](https://github.com/kunchenguid/firstmate/actions/runs/36664663190), and [36669175457](https://github.com/kunchenguid/firstmate/actions/runs/36669175457). Use the slowest successful `duration_ms` per script across their uploaded portable timing artifacts and completed `FM_TEST_END` log markers, with the two version/platform exceptions below. All artifact records were cross-checked against the corresponding job's markers. -This covers all 24 parallel and 201 serial members; an existing live-capability skip is a portable-runner measurement, not a timing claim for the unavailable live integration. +Those runs are upstream's, and their records cover all 24 parallel members and the 201 serial members the lane held upstream; an existing live-capability skip is a portable-runner measurement, not a timing claim for the unavailable live integration. +This fork's serial lane carries additional fork-only members that no upstream run measured, so each of them packs on the `PORTABLE_SERIAL_DEFAULT_WEIGHT_MS` default until the fork refreshes its own hints from its own green CI runs; read the current lane size and unmeasured share from `bin/fm-test-run.sh --check-coverage` rather than from a count copied here. Observed maxima provide conservative packing weights, not an upper bound on future durations. Two serial-5 jobs were cancelled at their 30-minute cap and uploaded no artifact. @@ -78,7 +79,7 @@ Refresh the CI-derived hints by downloading the per-shard timing artifacts from ```sh for run in ; do - gh-axi run download "$run" -R kunchenguid/firstmate --dir "/tmp/fm-serial/$run" + gh-axi run download "$run" -R /firstmate --dir "/tmp/fm-serial/$run" done jq -r '.scripts[] | select(.exit == 0) | [.path, .duration_ms] | @tsv' /tmp/fm-serial/*/fm-test-timing-portable-serial-*/*.json \ | awk -F'\t' '$2 > m[$1] { m[$1] = $2 } END { for (p in m) print p, m[p] }' \ From 9fdece0fa6e8497fe596fbebe4aa89a3408cca42 Mon Sep 17 00:00:00 2001 From: keenvc Date: Fri, 2 Oct 2026 05:30:28 +0000 Subject: [PATCH 2/2] no-mistakes(test): test: accept subreaper reparenting and ruby-less YAML parsing --- tests/fm-session-lock-ancestry.test.sh | 14 ++++++++++---- tests/fm-test-run.test.sh | 24 ++++++++++++++++++++---- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/tests/fm-session-lock-ancestry.test.sh b/tests/fm-session-lock-ancestry.test.sh index 6076057bb17..87c6cbe725a 100755 --- a/tests/fm-session-lock-ancestry.test.sh +++ b/tests/fm-session-lock-ancestry.test.sh @@ -765,15 +765,21 @@ test_e2e_background_session_keeps_its_lock_across_a_recycled_chain() { grep -qx "$frontend" "$dir/state/phase-1/ancestry" || fail "the healthy chain did not reach the front-end" expect_phase_owned "$dir" 1 2 "$frontend" "healthy chain" - # Recycle the bridge: the daemon ends, the pty-host is reparented to init, and - # the front-end that holds the lock stays alive. + # Recycle the bridge: the daemon ends, the pty-host is orphaned to whatever + # reaper the host uses (init, or a user-session subreaper such as systemd + # --user), and the front-end that holds the lock stays alive. kill -TERM "$daemon" i=0 - while [ "$i" -lt 200 ] && { kill -0 "$daemon" 2>/dev/null || [ "$(ps -o ppid= -p "$ptyhost" 2>/dev/null | tr -d ' ')" != 1 ]; }; do + ptyhost_ppid=$(ps -o ppid= -p "$ptyhost" 2>/dev/null | tr -d ' ') + while [ "$i" -lt 200 ] && [ "$ptyhost_ppid" = "$daemon" ]; do sleep 0.05 i=$((i + 1)) + ptyhost_ppid=$(ps -o ppid= -p "$ptyhost" 2>/dev/null | tr -d ' ') done - [ "$(ps -o ppid= -p "$ptyhost" 2>/dev/null | tr -d ' ')" = 1 ] || fail "the pty-host was not reparented to init after the daemon ended" + kill -0 "$ptyhost" 2>/dev/null \ + || fail "the pty-host died with the daemon, so the recycled case cannot be exercised" + [ "$ptyhost_ppid" != "$daemon" ] \ + || fail "the pty-host is still parented by the daemon after it ended" kill -0 "$frontend" 2>/dev/null || fail "the front-end died with the daemon, so the recycled case cannot be exercised" # Phase 2: the same session id over the broken chain - the reported drift. diff --git a/tests/fm-test-run.test.sh b/tests/fm-test-run.test.sh index 924342c9f06..6a97b5709c3 100755 --- a/tests/fm-test-run.test.sh +++ b/tests/fm-test-run.test.sh @@ -1754,10 +1754,11 @@ test_herdr_ci_family_run_has_a_step_timeout() { # The required Herdr lane's hang tripwire is the family-run *step* bound, not # the 75-minute job cap. Parse the workflow as YAML so nested `with.name` # artifact keys cannot masquerade as the step contract. - command -v ruby >/dev/null 2>&1 \ - || fail "ruby is required to parse .github/workflows/ci.yml as YAML" local json job_timeout step_timeout - json=$(ruby -ryaml -rjson -e ' + # Ruby's YAML is the CI runner's parser; fall back to PyYAML on hosts that + # ship python3 without ruby so the contract still runs there. + if command -v ruby >/dev/null 2>&1; then + json=$(ruby -ryaml -rjson -e ' doc = YAML.load_file(ARGV[0]) job = doc.fetch("jobs").fetch("tests-herdr") step = job.fetch("steps").find { |s| @@ -1770,7 +1771,22 @@ puts JSON.generate( "step_timeout" => step.fetch("timeout-minutes") ) ' "$ROOT/.github/workflows/ci.yml") \ - || fail "could not parse tests-herdr timeouts from ci.yml" + || fail "could not parse tests-herdr timeouts from ci.yml" + elif python3 -c 'import yaml' >/dev/null 2>&1; then + json=$(python3 - "$ROOT/.github/workflows/ci.yml" <<'PY' +import json, sys, yaml +doc = yaml.safe_load(open(sys.argv[1], encoding="utf-8")) +job = doc["jobs"]["tests-herdr"] +step = next(s for s in job["steps"] + if isinstance(s, dict) + and s.get("name") == "Run real-Herdr family (serial, required)") +print(json.dumps({"job_timeout": job["timeout-minutes"], + "step_timeout": step["timeout-minutes"]})) +PY +) || fail "could not parse tests-herdr timeouts from ci.yml" + else + fail "ruby or python3 with PyYAML is required to parse .github/workflows/ci.yml as YAML" + fi job_timeout=$(python3 -c 'import json,sys; print(json.load(sys.stdin)["job_timeout"])' <<<"$json") \ || fail "could not read job timeout from parsed workflow" step_timeout=$(python3 -c 'import json,sys; print(json.load(sys.stdin)["step_timeout"])' <<<"$json") \