Skip to content
Draft
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
42 changes: 41 additions & 1 deletion docs/howto/converge-a-stuck-red-canary-self-deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ doc_type: howto
status: active
related:
- ../reference/canary-gate-convergence.md
- ../reference/canary-unit-test-gate-hermetic-isolation.md
- ../reference/overseer-deploy-canary-diagnostics.md
- ../reference/self-deploy-api.md
- ../reference/overseer-tick-self-healing.md
Expand Down Expand Up @@ -67,7 +68,8 @@ Note the `failing_gate` value: `smoke`, `unit-test`, `gym-baseline`, or

| Symptom | Likely cause | Action |
| --- | --- | --- |
| `unit-test` fails reproducibly on merged `main` | **Genuine regression** | Fix the failing source/test at its origin so the canary goes green legitimately. Do **not** disable the gate. |
| `unit-test` fails reproducibly on merged `main` (assertion failures, exit `1`) | **Genuine regression** | Fix the failing source/test at its origin so the canary goes green legitimately. Do **not** disable the gate. |
| `unit-test` fails with `cargo test` exit **`101`** (test process aborts) every tick, but the same tests pass in a normal `cargo test` run | **Non-hermetic gate** — the gate's `cargo test` collides with the **live daemon** through the allow-listed `SIMARD_STATE_ROOT` | Already repaired by [unit-test gate hermetic isolation](../reference/canary-unit-test-gate-hermetic-isolation.md) (#4522): the gate runs against a private per-run state root. See step 4b. |
| `rpc-health` / `gym-baseline` fails with `connection refused`, a missing socket/endpoint, or an absent env var — but the same probe passes against the running binary | **Missing signal** in the ephemeral canary context | Supply the required signal through the `canary_env` allow-list (step 4). |
| Non-deterministic pass/fail | **Flaky gate** | Correct the gate's logic/threshold so it stops false-reddening **while still failing closed** on real regressions. |

Expand Down Expand Up @@ -95,6 +97,41 @@ Rules — these keep the gate a real authorization control, not a rubber stamp:
- **Absent name → still red.** If an allow-listed name is missing from the
environment, the gate proceeds without it and reddens — that is intended.

## 4b. `unit-test` gate crash-looping with exit `101` (hermetic-isolation case)

If `failing_gate=unit-test` and `failing_detail` reports `cargo test` exit
**`101`** (the test binary *aborted*, not an ordinary assertion `1`) on every
tick, the cause is almost always a **non-hermetic gate**, not a source
regression. The `unit-test` gate shells out to `cargo test`, and the Simard test
suite reads `SIMARD_STATE_ROOT`. Because that name is allow-listed for the
process-probe gates (step 4), the tests would otherwise inherit the **running
daemon's live state root** and race it — reading a half-written record or
colliding on a lock — aborting with exit `101`.

This is already repaired by
[unit-test gate hermetic isolation](../reference/canary-unit-test-gate-hermetic-isolation.md)
(#4522): `run_unit_test_gate` injects a **private, per-run state root** (mode
`0700`, auto-cleaned) into the gate's scrubbed env, overriding the live value for
that one gate. Confirm the fix is in the running binary and that the gate now
goes green:

```bash
# Reproduce the gate's env locally: scrubbed env + an isolated state root.
# The load-bearing override is SIMARD_STATE_ROOT — the same single override the
# fix applies. A healthy candidate must pass; if it does, the live-daemon
# collision was the cause. (TMPDIR is only added if a run shows the suite needs
# a private scratch dir; it is not part of the base fix — see the reference doc.)
env -i PATH="$PATH" HOME="$HOME" \
CARGO_HOME="$CARGO_HOME" RUSTUP_HOME="$RUSTUP_HOME" \
SIMARD_STATE_ROOT="$(mktemp -d)" \
cargo test --locked -p simard self_relaunch::gates
```

If exit `101` **persists** even against a private state root, it is a genuine
abort in the candidate's tests — treat it as the "genuine regression" row of the
step-3 table and fix the failing test at its origin. Never allow-list a way to
skip the gate.

## 5. Confirm convergence

Convergence has two halves — verify **both**:
Expand Down Expand Up @@ -147,6 +184,9 @@ simard status | grep -Ei 'deploy_drift|running_commit'

## Related reading

- [Canary unit-test gate hermetic isolation](../reference/canary-unit-test-gate-hermetic-isolation.md) —
the #4522 fix for the `unit-test` exit-`101` crash-loop: a private per-run
state root so the gate stops colliding with the live daemon.
- [Canary gate isolation and self-deploy convergence](../reference/canary-gate-convergence.md) —
the full design: per-gate spans, `canary_env`, `scrub_gate_env`, and the
preserved fail-closed invariants.
Expand Down
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ Bare `simard` prints the unified help text instead of attempting a hidden enviro
- [Overseer tick self-healing reference](./reference/overseer-tick-self-healing.md) - The transient-failure self-healing rung on the `overseer` meta-thread's per-tick health: a `cycle_failed` tick whose `run_cycle()` error is a transient upstream blip (5xx, timeout, connection reset, rate-limit) routes to a self-clearing `"backoff"` for one cadence instead of `"erroring"`. Covers the additive `transient_cycle_failure` field, the fail-closed `is_transient` classifier, the bounded consecutive-transient escalation ceiling, and the safety invariants (#893).
- [Overseer deploy red-canary diagnostics reference](./reference/overseer-deploy-canary-diagnostics.md) - The additive diagnostics that make a refused self-deploy diagnosable: the reddening canary gate name and detail surfaced into the `overseer::deploy` WARN and OTel attributes (`failing_gate` / `failing_detail`), the `CanaryResult.failing_gate` / `failing_detail` fields, `CanaryResult::refusal_reason`, the enriched `deploy_refused` notification and `Capability` detail, and the fail-closed `is_transient` `deploy_gate` / `target_canary` guard that stops a red canary being retried as a transient blip (#4420).
- [Canary gate isolation and self-deploy convergence reference](./reference/canary-gate-convergence.md) - The #4440 root-cause repair that acts on those diagnostics so a healthy candidate self-deploys and `DeployDrift` returns to 0: per-gate `self_relaunch::gate` tracing/OTel spans in `verify_canary`, the additive `RelaunchConfig.canary_env` narrow allow-list plus `scrub_gate_env` that supplies a gate's legitimately-missing signal without weakening fail-closed semantics, and the self-deploy loop advancing past the stuck target SHA. See the [convergence runbook](./howto/converge-a-stuck-red-canary-self-deploy.md).
- [Canary unit-test gate hermetic isolation reference](./reference/canary-unit-test-gate-hermetic-isolation.md) - The #4522 follow-on that stops the `unit-test` canary gate crash-looping with `cargo test` exit `101`: `run_unit_test_gate` runs `cargo test` under a private, per-run `SIMARD_STATE_ROOT` (mode `0700`, auto-cleaned) injected into the scrubbed gate env, so the gate no longer collides with the live daemon's state through the #4440 allow-listed root — deny-by-default floor and deny-over-allow hijack guard preserved, other gates still dialing the live daemon.
- [How to review the Overseer's workstream gaps](./howto/review-overseer-workstream-gaps.md) - Read, act on, and tune the Overseer's recurring "what workstreams are we missing?" gap-scan — the uncovered high-priority goals, high-signal issues, and unaddressed anomalies it flags each tick, where the deduped notification appears, and the `SIMARD_OVERSEER_GAP_SCAN` knobs (#2630).
- [Overseer workstream gap-scan reference](./reference/overseer-workstream-gap-scan.md) - The additive Observe→Orient→Act gap-scan: the `Signal::WorkstreamGap`/`GapItem`/`ProblemKind::WorkstreamCoverage` model, the coverage-set detection contract, the deduped NotifyOperator act path, the `SIMARD_OVERSEER_GAP_SCAN` configuration, and the additive `OverseerTickReport.workstream_gaps_detected` counter (#2630).
- [Overseer recipe-launch idempotency reference](./reference/overseer-recipe-launch-idempotency.md) - The launcher-level rail that makes `AmplihackRecipeRunner::spawn` idempotent per task signature: the pure `recipe_signature` normalization (`target_repo` + `task_description`, trim/lowercase/whitespace-collapse, `\u{1F}` separator), the reap-then-dedup order, the fail-visible `overseer::recipe` suppressed-launch warning, the shared-handle `probe` semantics, and the injectable `ChildSpawner`/`SpawnedChild` test seam — so a still-blocked signature no longer spawns a byte-identical `smart-orchestrator` every tick (#4125).
Expand Down
15 changes: 15 additions & 0 deletions docs/reference/canary-gate-convergence.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ owner: simard
doc_type: reference
status: implemented
related:
- ./canary-unit-test-gate-hermetic-isolation.md
- ./overseer-deploy-canary-diagnostics.md
- ./self-deploy-api.md
- ./self-deploy-source-prep.md
Expand Down Expand Up @@ -202,6 +203,17 @@ below); confirm it against a real gate run before narrowing it.
- **Fail closed on absence.** A missing allow-listed name is skipped, so the gate
proceeds with the missing signal and reddens if that signal is required.

> **Follow-on: the `unit-test` gate needs its own hermetic state root (#4522).**
> Allow-listing `SIMARD_STATE_ROOT` (above) is correct for the process-probe
> gates, which must dial the **running daemon's** state. But the `unit-test` gate
> shells out to `cargo test`, and the test suite reads `SIMARD_STATE_ROOT` — so
> inheriting the live root made that gate collide with the running daemon and
> crash-loop with `cargo test` exit `101`. The repair injects a **private,
> per-run state root** into the `unit-test` gate's scrubbed env (overriding the
> allow-listed value for that one gate). The allow-list and deny-by-default floor
> are unchanged; see
> [Canary unit-test gate hermetic isolation](./canary-unit-test-gate-hermetic-isolation.md).

## Behavior

### Per-gate tracing spans
Expand Down Expand Up @@ -366,6 +378,9 @@ green."

## See also

- [Canary unit-test gate hermetic isolation](./canary-unit-test-gate-hermetic-isolation.md) —
the #4522 follow-on that gives the `unit-test` gate a private state root so it
stops crash-looping (`cargo test` exit `101`) against the live daemon.
- [Overseer deploy red-canary diagnostics](./overseer-deploy-canary-diagnostics.md) —
the #4420 observability this repair acts on (`failing_gate` / `failing_detail`,
`refusal_reason`, the `overseer::deploy` WARN, the `is_transient` guard).
Expand Down
Loading
Loading