Files
ironclaw/scripts/auth_live_canary
Benjamin Kurrek 6d0da53a85 fix(test-infra): repair the #6520 audit fallout — coverage-lane crash, blind auth suites, weakened guards (#6609)
* 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>
2026-07-23 23:42:21 -04:00
..

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_needed when personal auth is missing
  • completing the declared setup recipe derives active and 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

  • gmail Configures vendor.google, then completes the declared Google OAuth recipe Runs through Responses API and browser
  • google_calendar Installs package google-calendar and completes the declared Google OAuth recipe Runs through Responses API
  • github Submits 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 declares manual_token)
  • notion Runs 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_ID
  • GOOGLE_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.

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.