Skip to content

ci: bound the test step, correct the macos-x86 accel claim, and ground #79's www mechanism - #108

Merged
mobileskyfi merged 2 commits into
mainfrom
ci/76-timeout-and-accel
Jul 30, 2026
Merged

ci: bound the test step, correct the macos-x86 accel claim, and ground #79's www mechanism#108
mobileskyfi merged 2 commits into
mainfrom
ci/76-timeout-and-accel

Conversation

@mobileskyfi

@mobileskyfi mobileskyfi commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Picks up items 1 and 4 from the #79/#76 triage, plus the two things the last session left as judgement calls: recording the mechanism in the durable docs, and attempting the local repro. Items 2 and 3 (the probe fix and the #106 envelope retune) are deliberately not here.

1. Step-level timeout on the test step (#76)

The test step had no cap of its own — only the job budget. When a job timeout fires, GitHub tears the runner down and if: always() never runs, so the reap, metrics, summary and artifact-upload steps are all skipped and the leg produces no output at all. That is the reason macos-x86 has resisted diagnosis for weeks.

The step now caps 10 min under the job budget (steptimeout = timeout - 10), which kills only the step and leaves the job alive to report.

Honest limit: this does not rescue a runner that loses communication with the server, which is how both macos-x86 legs actually died at ~60 min. Nothing in-job can. It converts the hang case into an honest failure with a report; #76's per-file cap is still the follow-up for the starvation case.

2. macos-x86 accel misclassification (#76)

macos-15-intel is a bare-metal Intel runner where detectAccel() returns hvf (timeoutFactor 1.5, boots 30–46 s at cpu-load: 0–1%). The platform table hardcoded tcg, and the "Accel hint" summary line printed that hardcoded value one row below the detectAccel line contradicting it.

  • The table now carries accel (a claim about the runner) separately from budget (the measured run-time class). Entangling them is what produced the wrong value.
  • The summary reports the detected accelerator and raises a ::warning:: on drift, so the table cannot go stale silently again.
  • macos-x86 keeps the extended budget: its full suite has never finished inside 60 min. That is an open question (CI: macos-x86 (TCG) full suite hangs silently mid provisioning.test.ts #76), not a property of its accelerator, so it is recorded as a budget fact rather than an accel one.

Verified by running the plan script locally across dispatch shapes (all / gating / single platform, with and without tcg-smoke and test-filter): every leg's timeout and smoke eligibility is unchanged.

3. #79's mechanism, reproduced locally (test/lab/www-abort-damage/)

#79's root-cause comment inferred "aborted probe → wedged www" from one socket table. Reproduced on RouterOS 7.21.5, Intel Mac, x86 under HVF and arm64 under cross-arch TCG:

experiment result
one aborted request, probed 0–3000 ms later no effect200 throughout
2–5 aborts in a row, gaps 0–2000 ms, nothing completing between no effect
abort, then a completed request, repeated www resets incoming connections; a surviving one gets the ABORTED request's response

Concretely — probe as a non-existent user, abort mid-flight, then probe as admin: with its valid empty password, and get 401: the status belonging to the aborted request. Two runs of the discriminating experiment were byte-identical.

Health is checked with curl from a separate process, so this is guest-side rather than Bun node:http socket reuse. And it reproduces on x86 as well as arm64, which is the load-bearing part of the claim.

waitForBoot() emits exactly that pattern whenever a probe overruns its 3 s deadline and the next one succeeds. Our probe damages the service it is waiting for — and the damage is a wrong answer, not merely a lost one (the #69 surface).

What did NOT reproduce, stated plainly

  • The permanent wedge. www recovers within ~6 s here. "After five aborts www stops accepting SYNs forever" remains an inference from CI's socket table.
  • The trigger for CI's first abort. Every CI probe timed out starting with the first at 3001 ms. Locally a failed login answers in ~125 ms even at cpu-load: 100, and the full clean() → relaunch → waitForBoot() scenario on arm64/TCG resolved in 51 s. The entry point to the CI failure is still unexplained.
  • Also confirmed: clean() leaves state.user naming the deleted user (defect A) — but that alone is survivable, since waitForBoot() accepts 401 as proof REST responded. Defect B is the one that matters, as CI: post-clean() relaunch never becomes REST-ready on Linux hosts (long-term / 7.21.5) #79 already argued.

Recorded in DESIGN.md (design decision 7) and qemu.instructions.md, both including the negative results.

Not in this PR

Verification

  • 826 unit tests pass (808 pass / 18 skip / 0 fail).
  • Lab test 5/5 on x86/HVF (twice) and arm64/TCG.
  • markdownlint, cspell, biome clean; all six workflows parse; actionlint finding count unchanged from main (2 pre-existing SC2016 infos).
  • Plan-matrix output diffed across dispatch shapes.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation

    • Added guidance on avoiding RouterOS REST service issues caused by interrupted boot probes.
    • Documented recommended probe timing, backoff behavior, and limitations of REST responses following timeouts.
    • Added lab documentation and findings for reproducing and understanding service damage scenarios.
  • CI Improvements

    • Refined platform accelerator and runtime-budget handling.
    • Improved integration-test timeout controls, artifact preservation, and accelerator mismatch warnings.
  • Tests

    • Added coverage for REST service recovery and abort-related failure scenarios across supported environments.

…s www mechanism

Three things, all in service of #76 and #79.

**Step-level timeout (#76).** The integration test step had no cap of its own,
only the job budget. When a job timeout fires GitHub tears the runner down and
`if: always()` never runs — so the reap, metrics, summary and artifact-upload
steps are all skipped and the leg produces literally no output. That is why
macos-x86 has resisted diagnosis for weeks. The step now caps at 10 minutes
under the job budget, which kills only the step and leaves the job alive to
report. It does not rescue a runner that loses communication with the server;
nothing in-job can.

**Accel misclassification (#76).** macos-15-intel is a bare-metal Intel runner
where detectAccel() returns hvf (timeoutFactor 1.5, boots 30-46 s at cpu-load
0-1 %), but the platform table hardcoded tcg — and the "Accel hint" summary line
printed that hardcoded value one row below the detectAccel line contradicting
it. The table now records the expected accelerator separately from the empirical
run-time budget, so a claim about the runner is no longer entangled with a
measurement of how long its suite takes. The summary reports the *detected*
accelerator and raises a ::warning:: on drift, so the table cannot go stale
silently again. macos-x86 keeps the extended budget: its full suite has never
finished inside 60 min, which is an open question, not an accel fact.

Verified by running the plan script locally across dispatch shapes (all /
gating / single-platform, with and without tcg-smoke and test-filter): every
leg's timeout and smoke eligibility is unchanged.

**#79's mechanism, grounded (test/lab/www-abort-damage/).** The root-cause
comment on #79 inferred "aborted probe -> wedged www" from one socket table.
Reproduced locally on RouterOS 7.21.5, Intel Mac, x86 under HVF and arm64 under
cross-arch TCG:

- A lone aborted request is harmless at any probe delay from 0-3000 ms.
- 2-5 consecutive aborts with 0-2000 ms gaps are harmless.
- An aborted request FOLLOWED BY A COMPLETED ONE, repeated, is the trigger.
  www then resets incoming connections, and a surviving connection is answered
  with the status computed for the *aborted* request — an admin: probe carrying
  a valid empty password comes back 401. Two runs byte-identical.

Health is checked with curl from a separate process, so this is guest-side, not
Bun node:http socket reuse; and it reproduces on x86 as well as arm64, so it is
not an arm64 quirk. waitForBoot() emits exactly that pattern whenever a probe
overruns its 3 s deadline and the next one succeeds — our probe damages the
service it is waiting for, and the damage is a WRONG answer, not just a lost
one (the #69 surface).

Recorded honestly in DESIGN.md and qemu.instructions.md, including what did NOT
reproduce: www recovers within ~6 s here, so the permanent wedge remains an
inference, and nothing local explains what made CI's first probe exceed 3 s (a
failed login answers in ~125 ms even at cpu-load 100). Also confirmed locally
that clean() leaves state.user naming the deleted user — but that alone is
survivable, since waitForBoot() accepts 401 as proof REST responded.

No fix to the probe itself here; that is #79's own change.

Refs #76, #79, #69, #106

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 29, 2026 22:21
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: af1c81bb-d1d3-470a-8b03-50c3aecf4cec

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds a RouterOS REST abort-damage lab and related design documentation, then updates integration CI to separate accelerator from runtime budget, calculate step timeouts, detect accelerator drift, and document the changes.

Changes

RouterOS REST abort-damage investigation

Layer / File(s) Summary
Abort-damage lab test
test/lab/www-abort-damage/www-abort-damage.test.ts
Adds five conditional integration scenarios covering baseline health, failed authentication, isolated aborts, repeated abort-then-complete requests, and recovery.
Abort-damage findings and constraints
test/lab/www-abort-damage/REPORT.md, DESIGN.md, .github/instructions/qemu.instructions.md, test/lab/README.md
Documents the reproduced RouterOS www behavior, req.destroy() deadline handling, timing/backoff rules, evidence limitations, and lab registration.

Integration workflow runtime planning

Layer / File(s) Summary
Accelerator and budget matrix planning
.github/workflows/integration.yml
Separates expected accelerator from runtime budget, updates smoke eligibility, and recalculates extended-leg timeout caps.
Runtime detection and timeout wiring
.github/workflows/integration.yml
Reports detected versus expected accelerators, warns on drift, logs kern.hv_support, and applies the computed step timeout.
CI documentation and vocabulary
CHANGELOG.md, project-words.txt
Records the CI timeout and accelerator-reporting changes and adds steptimeout to the project dictionary.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AbortLab
  participant RouterOS_www
  participant curlProbe
  AbortLab->>RouterOS_www: start REST request
  AbortLab->>RouterOS_www: destroy request socket
  AbortLab->>RouterOS_www: send completed REST request
  RouterOS_www-->>curlProbe: return health status
  curlProbe-->>AbortLab: report result
Loading

Possibly related PRs

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main changes: CI test-step bounding, macOS x86 accel correction, and documentation of the www mechanism.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci/76-timeout-and-accel

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves integration CI diagnostics (especially for macos-x86) by adding a step-level timeout below the job timeout, corrects/grounds accelerator reporting for macos-x86, and adds a lab + durable documentation capturing the reproduced RouterOS www corruption mechanism related to aborted REST probes (issue #79 context).

Changes:

  • CI: add a per-step timeout for the integration test step (10 minutes under the job timeout) so if: always() teardown/metrics/artifacts still run when tests hang.
  • CI: correct macos-x86 accel classification/reporting by separating “expected accel” from “budget class” and reporting detected accel with drift warnings.
  • Labs/docs: add a new lab (test/lab/www-abort-damage/) and record the reproduced “abort then complete” www corruption mechanism in durable docs.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
.github/workflows/integration.yml Adds step-level timeout, separates accel vs budget in the plan matrix, and improves platform/accel reporting for diagnosis.
test/lab/www-abort-damage/www-abort-damage.test.ts New lab test formalizing the reproduced www corruption pattern with node:http + curl health checks.
test/lab/www-abort-damage/REPORT.md Lab report documenting experiments, positive/negative results, and bounds of the reproduction.
test/lab/README.md Registers the new lab in the lab index.
.github/instructions/qemu.instructions.md Records the “aborting a probe damages www” finding and resulting operational rules.
DESIGN.md Adds design decision capturing the mechanism and constraints/bounds.
CHANGELOG.md Notes CI timeout and macos-x86 accel reporting changes.
project-words.txt Adds steptimeout to the cSpell project dictionary.

Comment thread .github/workflows/integration.yml
Comment thread test/lab/www-abort-damage/www-abort-damage.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Around line 50-56: Update the changelog entry to remove the inaccurate claim
that no leg’s smoke eligibility changed: document that macos-arm64, now
classified as tcg|standard, no longer qualifies for tcg-smoke, or narrow the
claim specifically to macos-x86.

In `@test/lab/www-abort-damage/www-abort-damage.test.ts`:
- Around line 36-37: Update the lab gating and port initialization near SKIP and
PORT: parse QUICKCHR_INTEGRATION using explicit enabled values so “0” does not
enable the test, and validate QUICKCHR_LAB_PORT as an integer within the valid
1–65535 port range before allowing execution.
- Around line 83-84: Update the settle helper in www-abort-damage.test.ts to
poll for www recovery until a bounded deadline instead of sleeping a fixed six
seconds. Return or record the actual elapsed recovery time, and ensure the
report uses that measured duration while preserving independent settling between
patterns.
- Around line 111-126: Update the assertions in the pairs/flat validation to
verify each completed admin request is tied to its corresponding bogus request,
requiring the documented tuple pattern including ["ABORT", "401"] rather than
only checking that flat contains "401". Enforce the exact reset positions and
sequence documented for this scenario, while preserving the existing four-pair
setup and connection ordering.
- Around line 49-80: Update connect() and curlProbe() to distinguish
guest-induced resets from unrelated failures instead of mapping every error or
nonzero curl exit to "RST". Handle response close/aborted events separately,
preserve normal completion, and add a bounded timeout for requests that never
complete; classify connection refusal, timeout, missing curl, and other failures
with distinct non-RST outcomes so reset assertions cannot pass spuriously.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8059c325-65f3-41d8-b237-4f825e02fc39

📥 Commits

Reviewing files that changed from the base of the PR and between 74fb82e and 6510cda.

📒 Files selected for processing (8)
  • .github/instructions/qemu.instructions.md
  • .github/workflows/integration.yml
  • CHANGELOG.md
  • DESIGN.md
  • project-words.txt
  • test/lab/README.md
  • test/lab/www-abort-damage/REPORT.md
  • test/lab/www-abort-damage/www-abort-damage.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: copilot-pull-request-reviewer
  • GitHub Check: Unit Tests (windows-latest)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

In Bun-based TypeScript code, use Bun.spawn(), Bun.write(), Bun.sleep(), bun:test, and ESM imports with .ts extensions.

Files:

  • test/lab/www-abort-damage/www-abort-damage.test.ts
test/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Do not turn a red integration test green by broadening timeouts, skipping it, or platform-gating it before reproducing and root-causing the failure.

Files:

  • test/lab/www-abort-damage/www-abort-damage.test.ts
**/*.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.ts: Use Bun APIs and tooling rather than Node.js equivalents: Bun.spawn(), Bun.write(), Bun.sleep(), bun test, and bun:test. Use ESM with .ts extensions in imports; do not use CommonJS.
For ARM64 virt machines, never use if=virtio for drives; use an explicit -device virtio-blk-pci,drive=drive0 configuration.
When using HVF acceleration, use -cpu host, not cortex-a710.
For arm64 guests on macOS, automatically select TCG with -cpu cortex-a710; HVF cannot run the CHR image's 32-bit ARM userspace on Apple Silicon. --accel and QUICKCHR_ACCEL must override this selection for testing.
UEFI pflash code and vars units must be identical in size.
QGA is x86-only; do not assume the guest agent starts for arm64 CHR.
Use tabs for indentation.
Do not add unnecessary comments to obvious code.
Errors must be thrown as QuickCHRError(code, message, installHint?).
Preserve the documented public API types and behavior: QuickCHR.start(opts) returns ChrInstance; ChrInstance provides stop(), remove(), rest(), monitor(), serial(), and qga(); and MachineState represents persisted machine.json state.

Files:

  • test/lab/www-abort-damage/www-abort-damage.test.ts
🧠 Learnings (1)
📚 Learning: 2026-07-28T00:12:59.340Z
Learnt from: mobileskyfi
Repo: tikoci/quickchr PR: 101
File: .github/workflows/integration.yml:342-342
Timestamp: 2026-07-28T00:12:59.340Z
Learning: For tikoci/quickchr, review workflow changes to ensure GitHub Actions are pinned to immutable commit SHAs as a repository-wide policy. If a PR introduces SHA pinning for only part of a workflow (or only some workflows) without extending the same policy across the relevant workflow files, flag it. When adding/changing SHA pinning, also verify Dependabot is configured to update the `github-actions` ecosystem so action version bumps are managed consistently (e.g., in the repo’s Dependabot configuration), rather than doing an isolated pinning change in an unrelated PR.

Applied to files:

  • .github/workflows/integration.yml
🪛 LanguageTool
.github/instructions/qemu.instructions.md

[style] ~151-~151: Consider using “the surrounding budget”.
Context: ... deadline with the accel factor**, like the budget around it. Under TCG the honest fix for a slow an...

(NOUN_AROUND_IT)

🪛 zizmor (1.28.0)
.github/workflows/integration.yml

[warning] 350-350: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[warning] 371-371: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[warning] 373-373: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[warning] 397-397: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

🔇 Additional comments (8)
.github/workflows/integration.yml (4)

23-57: LGTM!

Also applies to: 87-87


157-194: LGTM!

Also applies to: 223-253


346-398: LGTM!


481-486: LGTM!

project-words.txt (1)

374-374: LGTM!

.github/instructions/qemu.instructions.md (1)

130-160: LGTM!

test/lab/README.md (1)

19-19: LGTM!

test/lab/www-abort-damage/www-abort-damage.test.ts (1)

55-59: 🗄️ Data Integrity & Integration

No action needed. restGet() uses node:http with agent: false and Connection: close, matching the probe’s connection semantics.

Comment thread CHANGELOG.md
Comment thread test/lab/www-abort-damage/www-abort-damage.test.ts Outdated
Comment thread test/lab/www-abort-damage/www-abort-damage.test.ts Outdated
Comment thread test/lab/www-abort-damage/www-abort-damage.test.ts
Comment thread test/lab/www-abort-damage/www-abort-damage.test.ts Outdated
…signal

Addresses the PR #108 review.

**The lab's assertions were too weak for the claim they support.** CodeRabbit
was right that `flat.toContain("401")` does not demonstrate wrong-response
delivery — a `401` in the *abort* slot is just the bogus request answering
normally. The assertion now works on the pair: some pair must be
`["ABORT", "401"]`, i.e. the request we let COMPLETE while sending `admin:`
with its valid empty password came back 401. Likewise the reset count now
looks only at the completed slot of each pair.

**Failures are no longer collapsed into "RST".** `connect()` mapped every
error and `curlProbe()` every non-zero curl exit to `RST`, so a refusal, a
stall, or a missing curl could have satisfied the guest-reset assertions —
in a lab whose entire point is that *the guest* resets the connection.
Both now classify: ECONNRESET/56 → RST, ECONNREFUSED/7 → REFUSED, 28 →
TIMEOUT, 127 → NO-CURL, everything else labelled with its code. A completed
request also gets a 20 s stall guard so it cannot hang the suite.

**Recovery is polled, not slept.** A fixed 6 s wait was fragile under TCG
scheduling variance and, worse, was reported as the recovery time. It is
polled with a bounded deadline and logs the real figure: measured at 1344 /
2301 / 2309 / 2376 ms across four runs. Every "~6 s" claim in DESIGN.md,
qemu.instructions.md, REPORT.md and the test header is corrected to the
measured 1.3-2.4 s range — the 6 s was my settle interval, not a measurement.

**The accel-drift warning no longer fires on an expected fallback.** Copilot
was right: a Linux leg dropping kvm->tcg because the runner has no writable
/dev/kvm is a runner condition, not a stale table, and "update the platform
table" is the wrong advice. That case is now a ::notice::; ::warning:: is
reserved for genuine drift, so it stays meaningful on macos-x86/windows-x86.

**Also hardened:** QUICKCHR_LAB_PORT is validated as a real port, so the file
cannot silently "pass" against nothing. The QUICKCHR_INTEGRATION gate is left
matching every other test file in the repo.

Not changed: CodeRabbit's CHANGELOG finding claimed macos-arm64 loses
tcg-smoke eligibility. It does not — eligibility now follows `budget`, and
macos-arm64 was ineligible before (tcg=false) and after (budget=standard).
Verified by running the plan script from HEAD~1 and HEAD with tcg-smoke=true:
byte-identical smoke/timeout for all five legs.

Verified: lab 5/5 on x86/HVF and arm64 cross-arch TCG with the stricter
assertions; biome, markdownlint, cspell clean; actionlint finding count
unchanged from main.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

.github/workflows/integration.yml:22

  • Spelling: “comma-separate” should be “comma-separated”.
# Platform ids (comma-separate to combine, or use a group alias):

@mobileskyfi
mobileskyfi merged commit 9ce1932 into main Jul 30, 2026
13 checks passed
@mobileskyfi
mobileskyfi deleted the ci/76-timeout-and-accel branch July 30, 2026 02:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants