* fix(coverage): box the pairing-attribution journey future — stack overflow under llvm-cov Since the #6520 merge commit, main's Coverage (default) and Coverage (all-features) jobs die in reborn_integration_extension_delivery: unbound_telegram_actor_pairs_via_web_minted_code_then_turns_attribute_to_ the_paired_user's ~400-line journey future overflows the 2 MiB test-thread stack once llvm-cov instrumentation inflates its frames (SIGABRT, 'has overflowed its stack'). Box the future via the _impl extraction the sibling telegram_update_becomes_a_turn_and_a_coordinated_reply already uses. Red first: cargo llvm-cov --no-report -p ironclaw_reborn_integration_tests --test reborn_integration_extension_delivery reproduced the exact CI crash locally; green after this change (18 passed instrumented and uninstrumented; the 2 postgres cases are filtered locally — no service). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YGtnyQSPh8ouwypXioXWTc * test(e2e): send the #6520 client_action_id install gesture in private-tool-installs test_private_tool_installs_full_path has failed on main's E2E Coverage lane since #6520 merged: the install contract now requires the client gesture id (parse_client_action_id, product_surface_inbound.rs) and this scenario's _install helper still posted only package_ref, dying with {"field":"client_action_id","validation_code":"missing_field"}. Send one id per install gesture via the reborn_webui_harness helper — the same reconciliation #6603 applied to the extensions-api specs. Verified locally through the real serve binary: 1 passed (was the standing red on main). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YGtnyQSPh8ouwypXioXWTc * ci(coverage): drive int-tier suite discovery from the Cargo.toml [[test]] registration The selector's find -maxdepth 1 walk could not see domain-folder bins, so the six tests/integration/auth/ suites (oauth_connect, oauth_popup_journeys, oauth_refresh, auth_gate, auth_failure, reopen_resume_through_gate) ran in NO PR or merge-queue lane — their only executor was the push-to-main coverage workflow, itself crashed since #6520. Discovery now selects every workspace [[test]] whose path sits under tests/integration/, so a suite cannot be registered without also being selected, whatever directory shape it uses; a registered-but-deleted file fails the lane loudly. Also drops the bash-4 mapfile so the guardrail runs on macOS dev machines (the old selector could not execute under /bin/bash 3.2 — all 12 section-D harness assertions failed locally at base), and de-stales the lane-runner's hardcoded 34/27/7 suite counts. Regression coverage: test-reborn-coverage.sh D6 pins domain-folder-bin selection and unregistered-sibling exclusion; D1-D5 pass unchanged. All six auth suites verified green locally (71 passed / 0 failed) before wiring them into the lanes; new selector output cross-checked name-by-name against Cargo.toml (56 suites, was 50). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YGtnyQSPh8ouwypXioXWTc * test(reborn): re-arm the remove-propagation guard and pin existing-member install reconciliation Two integration-tier weakenings from #6520: 1. scenario_remove_then_absent_cross_thread's phase-4 guard asserted the absence of installation_phase:setup_needed — but the same PR seeds a credential in phase 1, so the pre-remove state is active and a stale search projection would read active and pass the guard. Assert the wire contract instead: the installation_phase key is omitted entirely for a caller with no visible installation, so ANY surviving phase now fails. (Non-vacuity is anchored by scenario_install_then_active positively pinning the key for installed entries.) 2. The retired Activate action's structural successor — an existing member's idempotent install retry reconciling setup_needed -> active (extension_lifecycle.rs's Some(existing) same-caller arm) — had no integration coverage, and setup_needed was never positively observed at this tier. New scenario drives it on the shared store as a DISTINCT actor (with_actor_id): install parks the credential gate, deny leaves the membership at setup_needed (observed cross-thread), a seeded credential completes setup, and the same member's re-install reconciles to active (observed cross-thread) with no remove in between. The distinct actor kills scenario-order coupling both ways. Verified: cargo test --test reborn_group_extensions — 15 passed / 0 failed including both changes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YGtnyQSPh8ouwypXioXWTc * docs(canary): retire the forced-refresh flow from auth-canary config docs AUTH_LIVE_FORCE_GOOGLE_REFRESH and the deliberate-expiry note documented the expire_secret_in_db flow #6520 removed (the canary no longer reaches into persistence); the flag is read nowhere. Point the scopes example at the full-URL form matching run_live_canary.py's GOOGLE_SCOPE_DEFAULT, and note that a product-side refresh-under-expiry proof remains a tracked follow-up. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YGtnyQSPh8ouwypXioXWTc * ci(coverage): parse [[test]] stanzas order-independently Review finding (ironloopai blocking + CodeRabbit major, PR #6609): the selector emitted a name only when it appeared BEFORE the stanza's path line, so a valid Cargo registration written path-first would be silently skipped — recreating the blind-spot class this selector exists to prevent. Cargo treats TOML key order as irrelevant; the parser now buffers each [[test]] stanza and emits at the stanza boundary (next table header or EOF). Red first: harness case D6 extended with a path-before-name stanza failed against the old parser, passes now; all 14 section-D assertions green and the real-repo output is byte-identical (56 suites). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YGtnyQSPh8ouwypXioXWTc * ci(coverage): parse the [[test]] registration with tomllib, not line regexes Second formatting-dependence finding on the selector (CodeRabbit, PR #6609): the awk accepted only 'key = "value"' spacing, so a legal compact 'name="..."' stanza would be silently dropped. Rather than harden the regex one format at a time, parse Cargo.toml with Python's stdlib tomllib — the lane already hard-depends on python3 (scripts/ci/lib/reborn_coverage_lcov.py) and ubuntu-latest + macOS both ship >=3.11 — so the selector accepts exactly what Cargo accepts, closing the whole class (key order, spacing, comments). Red first: D6 extended with a compact trailing-comment stanza failed against the regex parser, passes now; all 14 section-D assertions green; real-repo output byte-identical (56 suites); shellcheck clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YGtnyQSPh8ouwypXioXWTc --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Live Auth Canary
This runner starts the shipping Reborn ironclaw binary in a clean home and
verifies provider-backed auth through:
Use scripts/live-canary/run.sh
as the top-level entrypoint for scheduled and manual lane dispatch. This file
documents the underlying executor for the auth-live-seeded lane.
/v1/responses- the browser gateway UI via Playwright
It uses the existing mock LLM from tests/e2e/mock_llm.py only for deterministic
tool selection. The thing under test is the real provider auth/runtime path,
not model behavior.
What It Proves
- a brand-new machine can build and start the shipping Reborn binary
- manifest-declared tenant configuration is saved through the operator API
- install derives
setup_neededwhen personal auth is missing - completing the declared setup recipe derives
activeand publishes tools - the Responses API can execute provider-backed tools
- the browser UI can execute provider-backed tools
- no public activation endpoint or legacy auth booleans are required
Current Provider Cases
gmailConfiguresvendor.google, then completes the declared Google OAuth recipe Runs through Responses API and browsergoogle_calendarInstalls packagegoogle-calendarand completes the declared Google OAuth recipe Runs through Responses APIgithubSubmits the PAT under the opaque manual-token requirement named by the manifest Runs through Responses API only (PAT-only — not browser-OAuth; the GitHub manifest declaresmanual_token)notionRuns only in browser mode because its manifest declares OAuth/DCR. The generic setup API intentionally does not accept a pre-seeded OAuth token.
Setup
See the canonical live-canary account and credential guide in scripts/live-canary/ACCOUNTS.md.
Copy the example config and fill in the real test credentials:
cd scripts/auth_live_canary
cp config.example.env config.env
set -a && source config.env && set +a
For seeded Google verification provide:
AUTH_LIVE_GOOGLE_ACCESS_TOKEN
along with:
GOOGLE_OAUTH_CLIENT_IDGOOGLE_OAUTH_CLIENT_SECRET
The runner saves the client values through the generic vendor.google admin
configuration group. It then installs each package, starts the descriptor's
OAuth flow, and completes the Reborn callback against the deterministic token
endpoint. If AUTH_LIVE_GOOGLE_REFRESH_TOKEN is also set, the resulting account
includes it; no canary reaches into persistence or mutates an internal state
field.
Browser-consent Google challenge bypass
When running --mode browser against Google, Google's risk engine will often
interrupt the Playwright login with a "Verify it's you" challenge that
handle_google_popup cannot solve. Bootstrap a storage_state.json once, and
the canary will skip the login (and the challenge) on subsequent runs:
python3 scripts/auth_live_canary/bootstrap_google_storage_state.py
# log into the dedicated test Google account in the window that opens,
# solve any challenges, then press Enter
export AUTH_BROWSER_GOOGLE_STORAGE_STATE_PATH=~/.ironclaw/auth-canary/google_storage_state.json
unset AUTH_BROWSER_GOOGLE_USERNAME AUTH_BROWSER_GOOGLE_PASSWORD
Re-run the bootstrap if browser-mode failures suggest the session has decayed.
Usage
From the repo root:
python3 scripts/auth_live_canary/run_live_canary.py --mode seeded
Run only selected providers:
python3 scripts/auth_live_canary/run_live_canary.py \
--mode seeded \
--case gmail \
--case github
CI-style fresh-machine install:
python3 scripts/auth_live_canary/run_live_canary.py \
--mode seeded \
--playwright-install with-deps
Reuse an existing venv and binary:
python3 scripts/auth_live_canary/run_live_canary.py \
--mode seeded \
--skip-python-bootstrap \
--skip-build
List the currently configured cases:
python3 scripts/auth_live_canary/run_live_canary.py --mode seeded --list-cases
Artifacts
The runner writes JSON results to:
artifacts/auth-live-canary/<mode>/results.json
Browser failures also write screenshots into the same output directory.
Important Boundary
This is the practical high-frequency live canary.
Seeded mode does not automate the provider login UI. It still crosses the same generic Reborn install/setup/callback contracts as browser mode; its mock token exchange returns the configured live Google test tokens. This catches:
- broken manifest/admin/setup plumbing
- regressions in derived lifecycle state or tool publication
- bad redirect/client config shipped with the runtime
- provider-side token validation changes
- tool execution regressions
Browser mode is the lower-frequency full provider-consent pass.