Skip to content
Merged
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
5 changes: 5 additions & 0 deletions config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,11 @@ repo_settings:
Each check has:
- `name`: Display label (e.g., "lint", "test", "integration")
- `command`: Shell command to execute
- `fix` (optional): Single shell command string that auto-remediates a failing check (e.g. `make lint-fix` for a `lint` check). It must be a *string*: a YAML list is silently coerced to its Python repr (`"['make fmt', 'make lint-fix']"`) and will never run, so chain steps with `&&` or point at a make target; an empty or omitted value (a bare `fix:`, `fix: ""`, `fix: false`, `fix: 0`) is dropped entirely, leaving the check with no fix and a block message that never mentions `fix`. When the per-slice green gate finds this check red at the slice tip, it runs the fix and re-runs the check. It commits and pushes the result to the slice integration branch only when **all** of the following hold (#3409): every *genuine* red check's fix turned it green (reds tagged with an infra signature are excluded before this decision under the default `EGG_SLICE_GREEN_GATE_INFRA_FAIL_OPEN=on`, #3417 — set that switch to `off` and every red, infra-tagged or not, must carry a fix that re-ran green); a final full re-run of every check the gate runs against the all-fixes-applied tree is also green; the runner's tree gained no new non-ignored untracked files, whether from the fix *or* from the checks themselves — the baseline is captured before the first check runs (the gate stages with `git add -u`, which would silently drop them; gitignored droppings don't count); at least one tracked file in that tree is modified, so there is something to commit; the runner reported a final-verification verdict at all and could determine that untracked count (a best-effort git failure in the runner is treated as unsafe); and the gate is running in `on` mode. Every refusal is logged as `autofix_block_reason` on the orchestrator's `Green gate red` line — a `log`-mode refusal is not one of them (that line reads "autofix available but not applied" and carries no such field) — and the reason reaches the slice failure message only when every genuine red's own re-run went green; otherwise it stays in the orchestrator's structured log. Checks without `fix`, or whose re-run stays red, route red verdicts back to the slice team unchanged. When *every* red is infra-tagged the gate fails open and returns **before** this decision, so even a fix that re-ran green is discarded with the worktree rather than committed.

**`on` is the default mode — no opt-in is required.** `EGG_SLICE_GREEN_GATE` is unset in the shipped configuration, and both an unset and an unrecognised value resolve to `on`, so autofix commits and pushes to your slice integration branch unless you turn it down. Set `EGG_SLICE_GREEN_GATE=log` to keep the fix running as a soak signal while committing and pushing nothing, or `off` to stop the gate before it spawns the runner at all — under `off` the fix never executes.

`fix` is used only by the per-slice green gate (slice-DAG implement pipelines); the tester step ignores it, so setting it on a non-slice pipeline has no effect. It is also a silent no-op on a check the gate skips: `EGG_SLICE_GREEN_GATE_SKIP_CHECKS` excludes checks by name before the gate runs, and defaults to `security`. See [slice-dag.md](../docs/architecture/slice-dag.md) for the `EGG_SLICE_GREEN_GATE` rollout switch.

Checks run sequentially during the implement phase tester step. If not configured, the tester falls back to auto-discovery (scanning for Makefile, package.json, pyproject.toml, etc.).

Expand Down
2 changes: 1 addition & 1 deletion config/repo_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,7 @@ def get_repo_checks(repo: str) -> list[dict[str, str]]:
"""Get configured check commands for a repository.

These are the commands to run during the SDLC pipeline implement phase
checker step. Each check has a "name" (display label) and "command"
tester step. Each check has a "name" (display label) and "command"
(shell command to execute). They run sequentially. A check may also
carry an optional "fix" command — an auto-remediation the per-slice
green gate runs when the check is red at the slice tip (#3409).
Expand Down
56 changes: 47 additions & 9 deletions config/repositories.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,53 @@ readable_repos:
# "bot" (default): Use GitHub App bot identity
# "user": Use personal access token with user identity
# - checks: List of check commands for the SDLC pipeline implement phase
# Each entry has "name" (display label) and "command" (shell command)
# These run sequentially during the checker step
# An entry may also set "fix" (shell command): when the per-slice green
# gate finds that check red at the slice tip, it runs the fix command,
# re-runs the check, and — if the fix turned it green — commits and
# pushes the result to the slice integration branch as the orchestrator
# (#3409). Only configure deterministic auto-remediations here, e.g.
# "make lint-fix" for a format/lint check. Checks without "fix" route
# red verdicts back to the slice team unchanged.
# Each entry has "name" (display label), "command" (shell command) and
# an optional "fix" (single shell command string — a YAML list is
# silently coerced to its Python repr and will never run, so chain
# steps with "&&" or point at a make target; an empty or omitted
# value — a bare "fix:", "", false, 0 — is dropped entirely,
# leaving the check with no fix and a block message that never
# mentions "fix")
# These run sequentially during the tester step
# "fix" auto-remediates that check at the per-slice green gate: when the
# gate finds the check red at the slice tip it runs the fix command and
# re-runs the check. It commits and pushes the result to the slice
# integration branch as the orchestrator only when ALL of the following
# hold (#3409): every *genuine* red check's fix turned it green (reds
# tagged with an infra signature are excluded first under the default
# EGG_SLICE_GREEN_GATE_INFRA_FAIL_OPEN=on, #3417 — set it "off" and
# every red must carry a fix that re-ran green); a final full re-run of
# every check the gate runs against the all-fixes-applied tree is also
# green; the runner's tree gained no new non-ignored untracked files,
# from the fix or from the checks themselves (the baseline is taken
# before the first check runs; the gate stages with "git add -u", which
# would silently drop them); at least one tracked file in that tree is
# modified, so there is something to commit; the runner reported a
# final-verification verdict at all and could determine that untracked
# count (a best-effort git failure in the runner is treated as unsafe);
# and the gate is running in "on" mode. Every refusal is logged as
# autofix_block_reason on the orchestrator's "Green gate red" line —
# a "log"-mode refusal is not one of them (that line reads "autofix
# available but not applied" and carries no such field) — and the
# reason reaches the slice failure message only when every genuine
# red's own re-run went green; otherwise it stays in the structured
# log. When EVERY red is infra-tagged the gate fails
# open and returns BEFORE this decision, so even a fix that re-ran
# green is discarded with the worktree rather than committed.
# NOTE: "on" is the DEFAULT mode — no opt-in is required. Nothing in
# the shipped configuration sets EGG_SLICE_GREEN_GATE, and both an
# unset and an unrecognised value resolve to "on", so autofix commits
# and pushes to your slice integration branch unless you turn it down.
# Set EGG_SLICE_GREEN_GATE=log to keep the fix running as a soak signal
# while committing and pushing nothing, or "off" to stop the gate
# before it spawns the runner at all — under "off" the fix never runs.
# Only configure deterministic auto-remediations here, e.g.
# "make lint-fix" for a format/lint check. Checks without "fix", or
# whose re-run stays red, route red verdicts back to the slice team
# unchanged. "fix" is green-gate-only (slice-DAG implement pipelines):
# the tester step ignores it, and it never runs on a check excluded by
# EGG_SLICE_GREEN_GATE_SKIP_CHECKS (default: "security").
# See docs/architecture/slice-dag.md for the rollout switch.
# - build_commands: Commands to run during Docker image build to install
# project-specific dependencies. Results are baked into the image so
# containers start with dependencies pre-installed (critical for private
Expand Down
70 changes: 55 additions & 15 deletions docs/architecture/slice-dag.md
Original file line number Diff line number Diff line change
Expand Up @@ -554,27 +554,67 @@ shape:
repo's configured checks at the integration-branch tip; staged
rollout via `EGG_SLICE_GREEN_GATE` — default `on` (a red verdict
withholds the slice PR), `log` runs the checks and logs the
verdict without blocking; fail-open on infra errors, including
infra-signature-tagged reds inside check execution, #3417.
**The gate can also write to the integration branch**: when every
genuine red carries an optional `fix:` command in
`repositories.yaml` (e.g. `lint: {fix: make lint-fix}`), the
runner applies the fixes in its worktree and the orchestrator
commits them as `egg-green-gate` and pushes to the integration
branch via the launcher-authed gateway push route, #3409. This is
the one place the orchestrator authors commits on a slice branch;
it fires only in `on` mode, and only when the runner proves the
exact tree `git add -u` will stage is green — one full re-run of
*every* configured check against the all-fixes-applied tree
(`final_verification.all_ok`) plus a no-new-untracked-files
check. Any failure to commit or push blocks the slice exactly
like an unfixed red) — calls
verdict without blocking, `off` returns before the runner Job is
spawned at all; fail-open on infra errors, including
infra-signature-tagged reds inside check execution, #3417; the
gate can also write to the integration branch — see
"Green-gate autofix" below) — calls
`GatewayClient.create_slice_pr` with `base` resolved from the
slice's DAG parent (root → latest completed chain tip, else the
pipeline branch (#3541); child → parent's
integration branch). On failure the worker calls
`scheduler.record_failure(slice_id)`, which arms the cascade
timer.

**Green-gate autofix (Stage A, #3409)** — the one place the
orchestrator authors commits on a slice branch. When every
genuine red carries an optional `fix:` command in
`repositories.yaml`, the runner applies the fixes in its
worktree and the orchestrator commits them as `egg-green-gate`
and pushes to the integration branch via the launcher-authed
gateway push route. `fix:` is a third key on a `checks:` list
entry — `- {name: lint, command: make lint, fix: make lint-fix}`
— not a name-keyed mapping; `validate_checks` drops any entry
missing `name`/`command` with no warning, and a repo whose
`checks:` all drop out gets no green gate at all, not merely no
autofix. Reds tagged with an infra signature are excluded from
"genuine" only under
the default `EGG_SLICE_GREEN_GATE_INFRA_FAIL_OPEN=on`; set it
`off` and every red must carry a fix that re-ran green. When
*every* red is infra-tagged the gate fails open and returns
**before** the autofix decision, so a fix that re-ran green is
discarded with the worktree rather than committed — deliberately,
since the run that proved it was already classified as
untrustworthy. In the mixed case the commit message names only
the genuine reds (`fixed_checks=genuine_failed`) even though the
committed tree also carries any infra-tagged check's fix.

Autofix fires only in `on` mode — which is the *default*, not an
opt-in: `EGG_SLICE_GREEN_GATE` is unset in the shipped
configuration and both an unset and an unrecognised value
resolve to `on`. It fires only when the runner proves the exact
tree `git add -u` will stage is green — one full re-run of
*every check the gate runs* against the all-fixes-applied tree
(`final_verification.all_ok`;
`EGG_SLICE_GREEN_GATE_SKIP_CHECKS` drops names before the
runner sees them, default `security`), plus a
no-new-*non-ignored*-untracked-files check
(`git ls-files --others --exclude-standard`, so gitignored
droppings don't count) over the runner's whole session rather
than the fix alone, plus at least one tracked modification for
`git add -u` to stage — the gate refuses when the re-runs went
green but staged nothing. `_autofix_ready` also refuses when the
runner reported no final-verification verdict at all
(`final_verification.ran` false) or could not determine the
untracked count (`new_untracked_count is None`, a best-effort
git failure); every refusal is logged as `autofix_block_reason`
on the orchestrator's `Green gate red` line — a `log`-mode
refusal is not one of them, it logs "autofix available but not
applied" with no such field — and the reason reaches the
operator-facing slice failure message only when every genuine
red's own re-run went green; otherwise it stays in the
structured log. Any failure to commit or push blocks the
slice exactly like an unfixed red.
4. After the wave completes, `scheduler.poll_cascades()` drains any
expired cascades and emits the orchestrator-side
`OVERSEER_ALERT` for each (see "Failure cascade").
Expand Down
2 changes: 1 addition & 1 deletion docs/development/STRUCTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ orchestrator/
├── slice_scheduler.py # Wave-based scheduler for the implement-phase slice DAG: computes execution waves, caps concurrency, two-tier max_cycles accounting, failure-cascade detection (#2137)
├── stacked_pr_reconciler.py # Stacked-PR rebase reconciler: detects child slice PRs whose base branch was deleted after a parent merge and retargets them via gateway rebase_onto (#2137)
├── cross_repo_merge_gate.py # Cross-repo merge-sequencing gate for multi-repo pipelines: auto-readies (or HITL-holds) a dependent slice's draft PR once its cross-repo upstream PR merges; rides the stacked-PR reconciler cadence (#3393 slice-5)
├── slice_green_gate.py # Per-slice green gate: sandboxed one-shot Job runs the repo's configured checks at the integration-branch tip; staged rollout via EGG_SLICE_GREEN_GATE (off/log/on, default on — on withholds the slice PR on a red verdict, log runs the checks and logs the verdict without blocking), fail-open on infra errors (#3398) and on infra-signature-tagged reds inside check execution (#3417)
├── slice_green_gate.py # Per-slice green gate: sandboxed one-shot Job runs the repo's configured checks at the integration-branch tip; staged rollout via EGG_SLICE_GREEN_GATE (off/log/on, default on — on withholds the slice PR on a red verdict, log runs the checks and logs the verdict without blocking), fail-open on infra errors (#3398) and on infra-signature-tagged reds inside check execution (#3417); Stage A (#3409) auto-remediates via each check's optional repositories.yaml `fix:` command and commits + pushes the fix to the integration branch instead of blocking, but only in `on` mode and only when every *genuine* red (infra-tagged reds are excluded first under the default EGG_SLICE_GREEN_GATE_INFRA_FAIL_OPEN=on, #3417) re-ran green, a final full re-run of every check the gate runs (EGG_SLICE_GREEN_GATE_SKIP_CHECKS excludes names, default `security`) against the fixed tree was green, the runner's tree gained no new non-ignored untracked files (from the fix or from the checks themselves — the baseline predates the first check), and at least one tracked file in it is modified so there is something to commit; when *every* red is infra-tagged the gate fails open and returns before the autofix decision, discarding a fix that re-ran green rather than committing it
├── action_guards.py # Formal BRC state machine action guards (preconditions for propose/ack/nack/confirm/withdraw)
├── approval_matrix.py # Per-reviewer ACK/NACK matrix for BRC consensus
├── attestation_schemas.py # Attestation payload validation for BRC proposals
Expand Down
7 changes: 7 additions & 0 deletions docs/guides/sdlc-pipeline.md
Original file line number Diff line number Diff line change
Expand Up @@ -992,10 +992,17 @@ repo_settings:
checks:
- name: lint
command: npm run lint
fix: npm run lint -- --fix # optional: green-gate auto-remediation (#3409)
- name: test
command: npm test
```

A check may optionally set `fix` to a single shell command string that auto-remediates it. It must be a *string*: a YAML list is silently coerced to its Python repr (`"['make fmt', 'make lint-fix']"`) and will never run, so chain steps with `&&` or point at a make target; an empty or omitted value (a bare `fix:`, `fix: ""`, `fix: false`, `fix: 0`) is dropped entirely, leaving the check with no fix and a block message that never mentions `fix`. When the per-slice green gate finds that check red at the slice tip, it runs the fix and re-runs the check. It commits and pushes the result to the slice integration branch only when **all** of the following hold (#3409): every *genuine* red check's fix turned it green (reds tagged with an infra signature are excluded before this decision under the default `EGG_SLICE_GREEN_GATE_INFRA_FAIL_OPEN=on`, #3417 — set that switch to `off` and every red, infra-tagged or not, must carry a fix that re-ran green); a final full re-run of every check the gate runs against the all-fixes-applied tree is also green; the runner's tree gained no new non-ignored untracked files, whether from the fix *or* from the checks themselves — the baseline is captured before the first check runs (the gate stages with `git add -u`, which would silently drop them; gitignored droppings don't count); at least one tracked file in that tree is modified, so there is something to commit; the runner reported a final-verification verdict at all and could determine that untracked count (a best-effort git failure in the runner is treated as unsafe); and the gate is running in `on` mode. Every refusal is logged as `autofix_block_reason` on the orchestrator's `Green gate red` line — a `log`-mode refusal is not one of them (that line reads "autofix available but not applied" and carries no such field) — and the reason reaches the slice failure message only when every genuine red's own re-run went green; otherwise it stays in the orchestrator's structured log. When *every* red is infra-tagged the gate fails open and returns **before** this decision, so even a fix that re-ran green is discarded with the worktree rather than committed. Only configure deterministic auto-remediations here; checks without `fix`, or whose re-run stays red, route red verdicts back to the slice team unchanged.

**`on` is the default mode — no opt-in is required.** `EGG_SLICE_GREEN_GATE` is unset in the shipped configuration, and both an unset and an unrecognised value resolve to `on`, so autofix commits and pushes to your slice integration branch unless you turn it down. Set `EGG_SLICE_GREEN_GATE=log` to keep the fix running as a soak signal while committing and pushing nothing, or `off` to stop the gate before it spawns the runner at all — under `off` the fix never executes. See [slice-dag.md](../architecture/slice-dag.md) for the rollout switch.

`fix` belongs to the per-slice green gate (slice-DAG implement pipelines) and is unrelated to the `check-fixer` agent ([Built-in Checks](#built-in-checks)) or the `fixer` DAG step ([Check DAG Configuration](#check-dag-configuration)). The tester step ignores `fix`, so setting it on a non-slice pipeline has no effect. `fix` is also a silent no-op on a check the gate skips: `EGG_SLICE_GREEN_GATE_SKIP_CHECKS` excludes checks by name before the gate runs, and defaults to `security`.

When configured, the tester runs these commands sequentially instead of auto-discovering test/lint commands. This is useful when:
- Auto-discovery doesn't find the right commands
- You want to run checks in a specific order
Expand Down
Loading