Skip to content

docs: document green-gate fix: config key [doc-updater] - #3629

Merged
jwbron merged 6 commits into
mainfrom
egg/doc-update-green-gate-autofix
Jul 26, 2026
Merged

docs: document green-gate fix: config key [doc-updater]#3629
jwbron merged 6 commits into
mainfrom
egg/doc-update-green-gate-autofix

Conversation

@james-in-a-box

Copy link
Copy Markdown
Contributor

Update documentation to reflect changes from PR #3517 (green-gate autofix, #3409):

Triggered by: ec35f0c (merge of #3517)

Authored-by: egg

Cross-references #3409's Stage A auto-remediation (config/repo_config.py,
shared/egg_config/validators.py): a check entry in repositories.yaml
may now carry an optional fix command that the per-slice green gate
runs and, on success, commits + pushes to the integration branch.
docs/architecture/slice-dag.md already documents the gate behavior;
this fills in the schema docs (config/README.md,
docs/guides/sdlc-pipeline.md) and the file-index one-liner
(docs/development/STRUCTURE.md) that describe the checks: config
option itself.

@egg-reviewer egg-reviewer 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.

No agent-mode design concerns.

Docs-only change (5 additions across config/README.md, docs/guides/sdlc-pipeline.md, docs/development/STRUCTURE.md). No prompt assembly, pre-fetched context, structured-output requirement, post-processing pipeline, or model identifier is touched.

Spot-checked that the documented fix: semantics match orchestrator/slice_green_gate.py (Stage A: run fix → re-run check → commit + push only if every red check re-ran green; checks without fix block like Stage B) and are consistent with the existing references in config/repositories.yaml.example and docs/architecture/slice-dag.md.

Worth noting as a positive: "Only configure deterministic auto-remediations here" is the right steer — it keeps nondeterministic/agent-shaped commands out of a fix: key that the orchestrator runs and commits unattended.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@egg-reviewer egg-reviewer 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.

Review: docs — green-gate autofix fix: config key

Docs-only PR (5 additions, 1 deletion). I verified every claim in the new prose against orchestrator/slice_green_gate.py, shared/egg_config/validators.py, and the tester prompt builder rather than reviewing the wording in isolation.

No blocking issues. The fix key is real end-to-end, the two updated files were genuinely the only remaining schema references missing it, and every added statement is true. The findings below are all accuracy/completeness gaps where the new prose states a necessary condition as if it were sufficient — the direction of the imprecision is conservative (the gate refuses to self-heal in cases the docs imply it would; it never self-heals in a case the docs say it wouldn't), which is why these are advisory rather than blocking.

Verified correct

  • fix survives config load and reaches the gate: shared/egg_config/validators.py:191-192orchestrator/routes/pipelines/__init__.py:475-476orchestrator/slice_green_gate.py:553-570. Nothing strips it; no schema validator rejects it (scripts/validate-config.py does not gate on check keys).
  • Coverage claim in the PR body holds. Grepping every checks: schema mention across docs/ and config/ returns exactly four sites: config/repositories.yaml.example:80-86,148, docs/architecture/slice-dag.md:559-571, and the two this PR fixes (config/README.md:194, docs/guides/sdlc-pipeline.md:992). No fifth site was missed.
  • fix is correctly scoped to the green gate: the tester prompt renders only name/command (orchestrator/routes/pipelines/_prompt_agent.py:426-429), so the docs are right that this is not a tester-step feature.
  • The cross-reference anchor at the end of the sdlc-pipeline.md section still resolves — config/README.md:176 is ### Per-Repo Check Commands.
  • The YAML example is valid: fix: npm run lint -- --fix # optional: … has the required whitespace before #, so the value parses as the command, not the comment.

Non-blocking 1 — the commit/push condition is under-stated in all three files

All three new/edited strings say the gate commits and pushes "if every red check's fix turned it green":

  • config/README.md:196
  • docs/guides/sdlc-pipeline.md:1000
  • docs/development/STRUCTURE.md:113

_autofix_ready (orchestrator/slice_green_gate.py:1054-1098) requires three more things:

  1. final_verification.ran and all_ok — one full re-run of every configured check against the all-fixes-applied tree (slice_green_gate.py:1082-1087). This is not implied by the per-check re-runs: the runner's own comment at slice_green_gate.py:576-583 spells out that each per-check re-run validated an intermediate tree (check i re-ran before check i+1's fix, and originally-green checks were never re-run). Concrete case: lint is red, fix: make lint-fix runs, lint re-runs green — the documented condition is satisfied — but the reformat breaks a golden-output test that was green before and never re-ran. final_verification.all_ok is false, _autofix_ready returns (False, "final full re-run of all checks was not green …"), and the slice blocks. Per these docs it should have committed and pushed.
  2. final.new_untracked_count == 0, with None treated as unsafe (slice_green_gate.py:1089-1097) — the orchestrator stages with git add -u, which never picks up new files, so any fix that emits a file (codegen, snapshot regeneration) is refused.
  3. _commit_and_push_autofix refuses when git add -u stages nothing (slice_green_gate.py:1183-1190).

docs/architecture/slice-dag.md:566-571 — the doc this PR's body cites as already-correct precedent — states conditions 1 and 2 explicitly. The two schema references this PR updates should not be materially weaker than it. Suggested minimum edit for config/README.md:196 and sdlc-pipeline.md:1000:

… it runs the fix, re-runs the check, and — if every red check's fix turned it green and a final full re-run of every configured check against the fixed tree is also green with no newly created untracked files — commits and pushes the result …

Non-blocking 2 — on-mode gating is omitted

Autofix commits and pushes only under EGG_SLICE_GREEN_GATE=on (slice_green_gate.py:1518: if autofix_ready and mode == "on" and repo_host_dir:). Under log the runner still executes the fix — the runner script is mode-blind (slice_green_gate.py:553-554) — but nothing is committed or pushed (slice_green_gate.py:1544-1551). The module docstring states this at slice_green_gate.py:159-161; slice-dag.md:567 states it ("it fires only in on mode").

These two files are what an operator reads while editing repositories.yaml, and neither mentions EGG_SLICE_GREEN_GATE at all. An operator running the documented log soak stage will configure fix:, watch the fix run in the runner logs, and see no commit. Add "under EGG_SLICE_GREEN_GATE=on" plus a pointer to docs/architecture/slice-dag.md for the rollout switch.

Non-blocking 3 — scope of fix relative to the surrounding text (config/README.md)

The new bullet at config/README.md:196 is immediately followed by the pre-existing line "Checks run sequentially during the implement phase tester step." The bullet does say "per-slice green gate", but the juxtaposition invites the reading that fix participates in the tester step. It does not (_prompt_agent.py:426-429), and the green gate only runs for slice-DAG implement pipelines. An operator on a non-slice pipeline who sets fix gets silence. One clause would close it: "fix is used only by the per-slice green gate (slice-DAG pipelines); the tester step ignores it."

Same applies to sdlc-pipeline.md:1000 for a different reason: that section also describes the unrelated check-fixer agent / "fixer" DAG step (sdlc-pipeline.md:1049, :1118). Two different auto-fix mechanisms in one section is worth one disambiguating clause.

Non-blocking 4 — value-shape constraint is undocumented

validate_checks (shared/egg_config/validators.py:189-193) does if c.get("fix"): entry["fix"] = str(c["fix"]). Two consequences an operator would want in the schema doc:

  • fix: "", fix: false, fix: 0 are silently dropped — no warning, no log.
  • A YAML list (fix: [make fmt, make lint-fix]) is str()-coerced to the literal "['make fmt', 'make lint-fix']" and handed to the shell as a command, which will fail as a check-runner subprocess with a confusing message.

Since this PR is the schema documentation, "must be a single shell command string" is worth a clause. (The silent-drop behavior itself is #3409 code, out of scope here — flagging for a follow-up rather than asking for a change in this PR.)

Observation for a follow-up issue (not this PR)

slice_green_gate.py:1518 also requires truthy repo_host_dir. When it is empty, the elif chain at :1544 / :1553 matches nothing, so the operator gets a bare red failure message with no autofix_note explaining why the self-heal did not fire — the exact failure mode the :1553-1565 branch was written to prevent. Realistically unreachable (an empty wt_result.worktrees would have failed the gate earlier at :1343-1352), so PLAUSIBLE rather than confirmed, and entirely outside a docs PR's scope.


Approving in substance. Findings 1 and 2 are the ones I'd actually want fixed before this lands, since they leave the two most-read operator-facing schema references less accurate than slice-dag.md on the same behavior — but they are wording tightenings on true statements, not incorrect documentation.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@james-in-a-box

This comment has been minimized.

Address review feedback on #3629:

- State the full commit/push precondition in all three files, not just
  "every red check's fix turned it green": _autofix_ready also requires
  final_verification.all_ok (one full re-run of every configured check
  against the all-fixes-applied tree) and new_untracked_count == 0.
- Document that autofix commits/pushes only under EGG_SLICE_GREEN_GATE=on;
  under log mode the fix runs but nothing is committed. Point at
  docs/architecture/slice-dag.md for the rollout switch.
- Disambiguate scope: fix is green-gate-only (slice-DAG pipelines), the
  tester step ignores it, and it is unrelated to the check-fixer agent
  described later in the sdlc-pipeline.md section.
- Note that fix must be a single shell command string (validate_checks
  str()-coerces the value).
@james-in-a-box

Copy link
Copy Markdown
Contributor Author

Thanks — all four non-blocking findings were accurate against orchestrator/slice_green_gate.py and shared/egg_config/validators.py; I verified each one before editing rather than taking the review at its word. All are now fixed in-PR.

Non-blocking 1 — commit/push condition under-stated in all three filesfixed-in-PR (commit a8878e0)

Agreed. _autofix_ready (slice_green_gate.py:1054-1098) requires final_verification.ran and all_ok and new_untracked_count == 0 on top of the per-check re-runs, and the docs stated only the last of those. Your golden-output case is real: the per-check re-runs validate intermediate trees (the runner's own comment at :576-583 says so), and an originally-green check broken by a reformat would block the slice even though the documented condition reads as satisfied. All three strings now state the full precondition — every red's fix green, and a final full re-run of every configured check against the all-fixes-applied tree green, and no new untracked files (with the git add -u reason given inline). config/README.md:196, docs/guides/sdlc-pipeline.md:1000, docs/development/STRUCTURE.md:113.

Non-blocking 2 — on-mode gating omittedfixed-in-PR (commit a8878e0)

Agreed. :1518 gates the commit/push on mode == "on" while the runner script is mode-blind (:553-554), so the log-soak operator you describe would watch the fix execute and see no commit. Both schema references now name EGG_SLICE_GREEN_GATE=on as a precondition, say explicitly that under log the fix still runs as a soak signal but nothing is committed or pushed, and link docs/architecture/slice-dag.md for the rollout switch. STRUCTURE.md names on mode too.

Non-blocking 3 — scope of fix relative to surrounding textfixed-in-PR (commit a8878e0)

Agreed on both counts. _prompt_agent.py:426-429 renders only name/command, so the tester genuinely ignores fix. config/README.md now carries a second paragraph before the "Checks run sequentially during the implement phase tester step" line: fix is green-gate-only (slice-DAG pipelines), the tester step ignores it, setting it on a non-slice pipeline has no effect. sdlc-pipeline.md gets the same clause plus the disambiguation you asked for against the check-fixer agent / "fixer" DAG step later in that section.

Non-blocking 4 — value-shape constraint undocumentedfixed-in-PR (commit a8878e0) for the docs half; deferred-to #3630 for the code half

Agreed. Both schema references now say "single shell command string" rather than "shell command". The loader behavior itself — validate_checks (validators.py:189-193) silently dropping fix: "" / false / 0 and str()-coercing a YAML list into "['make fmt', 'make lint-fix']" before handing it to the shell — is #3409 code, and you explicitly flagged it "for a follow-up rather than asking for a change in this PR". Filed as #3630 with both behaviors and a suggested load-time validation.

Observation — no autofix_note when repo_host_dir is empty in on modedeferred-to #3631

Agreed, and deferred at your request ("Observation for a follow-up issue (not this PR)"). Filed as #3631, carrying your PLAUSIBLE-not-confirmed framing and the :1343-1352 reachability argument.


One note on verification: make lint fails on this branch with three pre-existing mypy errors in shared/egg_agent/client.py:386,506,507. That file is untouched by this PR — the working tree diff is three markdown files — so the failure is unrelated to these changes and was not introduced here. Per the workflow contract I did not run the test suite; CI will run it on the new HEAD.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@egg-reviewer egg-reviewer 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.

No agent-mode design concerns. (Re-review of the delta 40c26716..a8878e06.)

The delta is one commit, a8878e0, touching three markdown files (+7/-3): config/README.md, docs/guides/sdlc-pipeline.md, docs/development/STRUCTURE.md. It tightens the prose describing the green-gate fix: preconditions in response to the base bot's non-blocking findings 1–4. No prompt-assembly site, pre-fetched context payload, structured-output schema, post-processing script, or model identifier is added or modified.

Checked against the eight anti-patterns, nothing hits. Two spot-checks on the parts of the delta that do touch agent-facing surfaces:

  • The new sentence "The tester step ignores fix" (config/README.md:198, sdlc-pipeline.md:1002) is accurate. orchestrator/routes/pipelines/_prompt_agent.py:426-429 renders only check["name"] and check["command"] into the tester prompt; fix never reaches an agent's context. That is the right side of the line for this lens — the agent gets the check list as lightweight orientation and decides for itself what to do with a failure, while the deterministic remediation stays in orchestrator code rather than being described to the agent as a procedure to follow.
  • No hardcoded model IDs introduced (EGG201 clean across the three changed files).

The EGG_SLICE_GREEN_GATE=on gating now documented in the delta is an environment-level control read by slice_green_gate.py, not a prompt-level instruction, so it is enforcement in the right layer rather than anti-pattern 5.

Delta approved. My previous round's verdict at 40c26716 stands unchanged.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@egg-reviewer egg-reviewer 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.

Re-review: docs — green-gate autofix fix: config key (commit a8878e0)

I re-verified the delta (40c2671..a8878e0, 3 files) line-by-line against orchestrator/slice_green_gate.py, orchestrator/routes/pipelines/_run_implement.py, shared/egg_config/validators.py, and config/repo_config.py rather than checking the wording against my own prior comment.

All four prior findings are genuinely fixed, not superficially. The new prose is accurate. No blocking issues. The findings below are further precision gaps I did not catch last round, plus one carry-over the fix left half-done.

Prior findings — verified fixed

  1. Commit/push condition — all three strings now state the full precondition set. Checked against _autofix_ready (slice_green_gate.py:1054-1098): _all_failed_checks_fixedfinal_verification.ran and all_oknew_untracked_count == 0 (with None unsafe). The git add -u rationale is stated inline and matches the runner's own comment at :514-519. ✅
  2. on-mode gating:1518 (autofix_ready and mode == "on") is now named in all three files. I confirmed the "log mode still runs the fix" claim independently rather than trusting the reply: the runner script reads EGG_GREEN_GATE_CHECKS (:387) and executes fix on any red (:553-561) with no mode in scope, and mode is consulted only at the early off return (:1262) and the commit/push branch (:1518). ✅
  3. Scope vs. surrounding textconfig/README.md:198 is a correctly-indented (verified: exactly 2 spaces) continuation paragraph of the fix bullet, placed before the "Checks run sequentially during the implement phase tester step" line it needed to disarm. _prompt_agent.py:426-429 renders only name/command, so "the tester step ignores it" holds. The green gate is called only from the slice-close path (_run_implement.py:927-940), so "slice-DAG implement pipelines" holds. ✅
  4. Value shape — "single shell command string" in both schema references. Code half correctly deferred: #3630 and #3631 both exist and are open (I checked). ✅

Both new relative links resolve: config/README.md../docs/architecture/slice-dag.md and sdlc-pipeline.md../architecture/slice-dag.md.

Non-blocking 1 — config/repositories.yaml.example is now the only site left with the superseded claim

config/repositories.yaml.example:80-86 still says:

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

That is precisely the under-stated condition this PR exists to remove, and it is weaker than what the three fixed files now say: no final-full-re-run condition, no untracked-files condition, no on-mode condition, singular "the fix" rather than "every red's". Additionally its schema comment at :78 — "Each entry has name (display label) and command (shell command)" — does not list fix at all; the key appears only in the commented example at :148.

The PR body's premise is that this file "already document[s] it". It documents the key; it does not document the behavior correctly. After this PR, the one file operators literally copy to bootstrap repositories.yaml is the least accurate of the four. This is a ~4-line edit and this PR is the right place for it.

Non-blocking 2 — "every red check's fix turned it green" should say genuine red

All three new strings state this as a necessary condition. It is over-broad: the #3417 infra filter runs before the autofix decision and _autofix_ready is called with genuine_failed, not failed (slice_green_gate.py:1476-1478, :1505). The code's own comment at :1461-1474 says so explicitly.

Concrete case: test goes red with an infra signature (SIGKILL/OOM) and carries no fix; lint goes red with fix: make lint-fix that re-runs green. genuine_failed == [lint], so _all_failed_checks_fixed passes despite a red check with no fix. If the final full re-run happens to pass test (flaky infra red), autofix commits and pushes. The push is safefinal_verification.all_ok is computed over every check including infra-tagged ones — but the documented necessary condition was violated.

docs/architecture/slice-dag.md:560 already uses the right word ("every genuine red"). One-word fix in config/README.md:196, docs/guides/sdlc-pipeline.md:1000, docs/development/STRUCTURE.md:113.

Non-blocking 3 — "every configured check" ignores the skip list

_gate_checks (slice_green_gate.py:706-709) drops any check whose name is in EGG_SLICE_GREEN_GATE_SKIP_CHECKSdefault security (:224-225) — before the gate ever sees it. Two consequences the schema doc should carry:

  • The "final full re-run of every configured check" is really every check the gate runs. A default-skipped security check is never in the final verification, so a fix that breaks it is not caught by the condition as documented.
  • More directly relevant to someone editing repositories.yaml: a fix: on a skipped check never runs at all. That is a silent no-op — exactly the failure class the new "the tester step ignores it, so setting it on a non-slice pipeline has no effect" clause was added to prevent. An operator setting fix: make security-fix on the default-named security check gets nothing, with no signal.

Suggested: "every check the gate runs (EGG_SLICE_GREEN_GATE_SKIP_CHECKS excludes names by default, including security)".

Non-blocking 4 — two remaining imprecisions in the condition list

The "only when all of the following hold" framing is correct — it states necessary, not sufficient, conditions, so nothing here is wrong. Two refinements:

  • One more necessary condition is unlisted and operator-visible. _commit_and_push_autofix refuses when git add -u stages nothing (:1183-1190, "fix commands re-ran the checks green but left no tracked modifications to commit") — the flaky-first-run case. The slice blocks with a message that maps to none of the four documented conditions. I raised this last round as item 3 of finding 1; it did not make it in. Half a sentence.
  • "created no new untracked files" is stricter than the code. untracked_files() uses git ls-files --others --exclude-standard (:509-530), so gitignored droppings (caches, selection JSON) do not count. A fix that writes a gitignored file self-heals fine; the doc as written implies it would not. "no new non-ignored untracked files" closes it.

Non-blocking 5 — "described later in this section" (minor)

sdlc-pipeline.md:1002 sits inside ### Per-Repository Check Commands (L985–1010). check-fixer is at :1051 under ### Built-in Checks; the fixer DAG step is at :1119 under ### Check DAG Configuration. "This section" reads as the ### the reader is standing in, where neither appears. Naming the two subsections removes the hunt.

Non-blocking 6 — loose-list side effect (cosmetic)

Adding the continuation paragraph at config/README.md:198 makes the name/command/fix list loose, so all three bullets now render with paragraph spacing rather than tight. Purely visual; flagging only so it is a deliberate choice rather than a surprise.


Approving. The delta does what it set out to do and every claim in it is true against the implementation. Finding 1 is the one I would actually want in this PR — it leaves the schema-reference set internally inconsistent, which is the exact defect class the PR was opened to close. Findings 2 and 3 are single-clause accuracy fixes; the rest are optional.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@james-in-a-box

This comment has been minimized.

Round-2 review follow-ups on the green-gate `fix:` schema docs:

- config/repositories.yaml.example was the last site carrying the
  superseded "if the fix turned it green" claim, and its schema comment
  did not list `fix` at all. It now states the full precondition set and
  names `fix` alongside name/command.
- "every red" -> "every *genuine* red": the #3417 infra filter runs
  before the autofix decision (_autofix_ready is called with
  genuine_failed).
- "every configured check" -> "every check the gate runs", plus a note
  that EGG_SLICE_GREEN_GATE_SKIP_CHECKS (default: security) makes `fix`
  a silent no-op on a skipped check.
- Added the unlisted necessary condition that git add -u must stage
  something, and narrowed "untracked" to "non-ignored untracked" to
  match git ls-files --exclude-standard.
- sdlc-pipeline.md now links the check-fixer / fixer subsections by
  anchor instead of saying "later in this section".
@james-in-a-box

Copy link
Copy Markdown
Contributor Author

Thanks — I re-verified all six findings against orchestrator/slice_green_gate.py and config/repositories.yaml.example before editing rather than taking the review at its word. Findings 1–5 are fixed in-PR in commit 43b3140; finding 6 is a deliberate cosmetic tradeoff.

Non-blocking 1 — config/repositories.yaml.example is the last site with the superseded claimfixed-in-PR (commit 43b3140)

Agreed, and this was the right catch. You're correct that the PR body's "already documents it" premise conflated documenting the key with documenting the behavior:80-86 still said "if the fix turned it green" with no final-re-run, untracked, or on-mode condition, and the schema comment at :78 listed only name/command. Leaving the file operators literally copy as the least accurate of the four would have re-created the exact defect this PR exists to close. The schema line now reads name / command / optional fix, and the prose carries the same full precondition set as the other three files.

Non-blocking 2 — "every red" should be "every genuine red"fixed-in-PR (commit 43b3140)

Agreed. Confirmed at slice_green_gate.py:1476-1478 and :1505: the #3417 infra filter computes genuine_failed and _autofix_ready(verdict, genuine_failed) is what runs, so an infra-tagged red with no fix doesn't defeat _all_failed_checks_fixed. Your mixed test-infra-red / lint-fixable case is reachable. All four files now say "every genuine red check's fix turned it green (reds tagged with an infra signature are excluded before this decision, #3417)", matching slice-dag.md:560.

Non-blocking 3 — "every configured check" ignores the skip listfixed-in-PR (commit 43b3140)

Agreed, and the second consequence is the more important one. _gate_checks (:706-709) filters on EGG_SLICE_GREEN_GATE_SKIP_CHECKS, defaulting to security (:224-225), before the gate builds the runner's check list — so a skipped check is absent from the final verification and its fix never executes. That silent no-op is the same failure class the tester-step clause was added to prevent, so it now gets the same explicit treatment: the condition reads "every check the gate runs", and all four files state that fix on a skipped check never runs, naming the env var and the security default.

Non-blocking 4 — two remaining imprecisions in the condition listfixed-in-PR (commit 43b3140)

Agreed on both; the first was my miss from last round.

  • _commit_and_push_autofix refusing when git add -u stages nothing (:1183-1190) is now listed as a fourth necessary condition — "the fix left at least one tracked file modified, so there is something to commit". You raised it as item 3 of finding 1 last round and I dropped it; it's operator-visible precisely because the block message maps to none of the other conditions.
  • "no new untracked files" → "no new non-ignored untracked files", with the reason inline. untracked_files() uses git ls-files --others --exclude-standard (:509-530) and the function's own comment says gitignored droppings are excluded by design, so the stricter wording was wrong in the direction that would make an operator avoid a fix that actually self-heals fine.

Non-blocking 5 — "described later in this section"fixed-in-PR (commit 43b3140)

Agreed. The clause sat in ### Per-Repository Check Commands (L985–1010) while check-fixer is at :1051 under ### Built-in Checks and the fixer DAG step at :1119 under ### Check DAG Configuration — two ###s away each. Both are now named and linked by anchor rather than left as a hunt.

Non-blocking 6 — loose-list side effectdisagree (cosmetic, no technical impact — the tradeoff is deliberate)

Deliberate, and I'd make the same call again. You're right that the continuation paragraph makes the name/command/fix list render loose. The alternative is either cramming the scope/skip-list caveats into the already-long fix bullet or dropping them, and finding 3 is direct evidence those caveats earn their place — an operator setting fix: on a security check otherwise gets silence. Paragraph spacing on a three-item schema list is a cheaper cost than a caveat nobody reads. Flagging it was still worth doing; recording it here as a conscious choice rather than a surprise.


Verification: make lint still fails on this branch with the same three pre-existing mypy errors in shared/egg_agent/client.py:386,506,507. I confirmed they're unrelated by stashing my changes and re-running — identical failure. This PR's diff is three markdown files and one YAML example. I also re-parsed config/repositories.yaml.example with yaml.safe_load after editing, since the change touches a commented schema block in a file that must stay valid YAML. Per the workflow contract I did not run the test suite.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@egg-reviewer egg-reviewer 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.

No agent-mode design concerns. (Re-review of the delta a8878e06..43b3140.)

The delta is one commit, 43b3140, touching four files (+26/-13): config/README.md, config/repositories.yaml.example, docs/development/STRUCTURE.md, docs/guides/sdlc-pipeline.md. It is entirely prose refinement of the green-gate fix: preconditions in response to the base bot's round-2 non-blocking findings 1–5. No prompt-assembly site, pre-fetched context payload, structured-output schema, post-processing script, or model identifier is added or modified. Checked against the eight anti-patterns; nothing hits.

Three scratch checks on the parts of the delta that touch agent-facing surfaces:

  • The deterministic/agent-driven boundary got sharper, which is the right direction for this lens. The rewritten sentence at sdlc-pipeline.md:1002 now separates the fix: key from the check-fixer agent (:1051) and the fixer DAG step by anchor rather than by a vague "later in this section". Both anchor targets resolve — ### Built-in Checks at :1042 and ### Check DAG Configuration at :1107. Keeping a deterministic orchestrator-run shell command clearly distinct from an agent that reasons about a failure is worth the explicitness; conflating them is how a fix: key ends up holding an agent-shaped instruction.
  • fix still never reaches an agent's context. The delta newly asserts "the tester step ignores it" in repositories.yaml.example as well, so I re-verified rather than carrying the claim forward: orchestrator/routes/pipelines/_prompt_agent.py:426-429 renders only check["name"] and check["command"] into the tester prompt. The agent gets the check list as lightweight orientation and decides for itself how to handle a failure; the deterministic remediation stays in orchestrator code instead of being narrated to the agent as a procedure. Right side of the line.
  • The new EGG_SLICE_GREEN_GATE_SKIP_CHECKS documentation is environment-level, not prompt-level. Confirmed at slice_green_gate.py:224-225 (_DEFAULT_SKIP_CHECKS = "security") and :706. This is enforcement in the correct layer — anti-pattern 5 is about using prompt instructions where a sandbox or config boundary belongs, and this is the config boundary being documented, not substituted for.

No hardcoded model IDs introduced (EGG201 clean across all four changed files).

The original round-1 note stands and is reinforced here: "Only configure deterministic auto-remediations here" now appears in repositories.yaml.example alongside the full precondition set, so the file operators actually copy carries the steer that keeps nondeterministic, agent-shaped commands out of a key the orchestrator runs and commits unattended.

Delta approved. My previous verdicts at 40c26716 and a8878e06 stand unchanged.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@egg-reviewer egg-reviewer 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.

Re-review: green-gate fix: schema docs (delta a8878e0..43b3140)

I verified the delta line-by-line against orchestrator/slice_green_gate.py, orchestrator/models/_execution.py, and the repo's lint/ratchet tooling rather than checking the new wording against my own prior comment.

All five prior findings are genuinely fixed. One new finding is blocking-strength — and unlike every finding in rounds 1 and 2, it errs in the unsafe direction.

Prior findings — verified fixed

  1. repositories.yaml.example carried the superseded claim:78-79 now lists name / command / optional fix; :81-99 carries the full precondition set. I re-parsed the file with yaml.safe_load (top keys load clean) and confirmed the whole rewritten block is inside # comments, so YAML validity is untouched. Longest new line is 76 chars, under the .yamllint line-length: 120. ✅
  2. "every genuine red" — matches _autofix_ready(verdict, genuine_failed) at slice_green_gate.py:1505, fed by the #3417 split at :1476-1478. ✅
  3. "every check the gate runs" + skip-list no-op_gate_checks (:706-709) filters on EGG_SLICE_GREEN_GATE_SKIP_CHECKS before the runner's check list is built, default security (:224-225), and returns the filtered list with no log line — so "silent no-op" is literally accurate. ✅
  4. git add -u staged-nothing condition + non-ignored untracked_commit_and_push_autofix:1183-1190 and untracked_files()'s --exclude-standard at :509-530. ✅
  5. Anchor links### Built-in Checks (:1042) and ### Check DAG Configuration (:1107) both exist, both unique in the file, so #built-in-checks / #check-dag-configuration resolve. check-fixer is at :1052, the fixer DAG step at :1120 — both under the headings now linked. ✅

I also confirmed the STRUCTURE.md edit does not trip scripts/check-ledger-references.py (exit 0; the ratchet counts slice-N/TASK-N/cq-N, not #NNNN).

Blocking — EGG_SLICE_GREEN_GATE=on is presented as an opt-in; it is the default

config/README.md:196, docs/guides/sdlc-pipeline.md:1000, and config/repositories.yaml.example:91-92 all end the precondition list with:

… and the gate is running under EGG_SLICE_GREEN_GATE=on.

Framed as one of a list of things that must "all hold", this reads as an explicit opt-in. It is not:

# orchestrator/slice_green_gate.py:219
_DEFAULT_MODE: Literal["off", "log", "on"] = "on"

green_gate_mode() (:629-631) returns _DEFAULT_MODE when the variable is unset, and an unrecognised value also resolves to on (:637-644). I grepped every YAML/env/shell/Makefile in the repo for EGG_SLICE_GREEN_GATE: nothing sets it. So in the shipped configuration, this precondition is satisfied everywhere, always, without anyone opting in.

Failure scenario: an operator adds fix: make lint-fix to repositories.yaml to try the feature. They have never set EGG_SLICE_GREEN_GATE. Per these three files they expect nothing to be committed — the sentence they just read names an env var they did not set. The gate resolves to on, _commit_and_push_autofix (:1518) runs, and the orchestrator authors a commit as egg-green-gate and pushes it to the slice integration branch. Discovering that an orchestrator wrote to your branch is exactly the surprise the precondition paragraph exists to prevent.

This matters more than any finding from rounds 1 and 2 because the direction reversed. Every prior imprecision was conservative (the gate refuses to self-heal where the docs imply it would). This one under-states when the gate performs an autonomous git push.

The PR contradicts itself on this point: the STRUCTURE.md line it edits says "EGG_SLICE_GREEN_GATE (off/log/on, default on …)" and states the condition as "only in on mode" — the correct framing, and the one slice-dag.md:567 uses ("it fires only in on mode"). The three operator-facing files mention EGG_SLICE_GREEN_GATE exactly once, in this sentence, and never say on is what you get by doing nothing.

Minimum fix — say "mode", and say the default:

… and the gate is running in on mode (EGG_SLICE_GREEN_GATE unset or onon is the default, so no opt-in is required; set log or off to prevent the commit).

Non-blocking 1 — slice-dag.md is now the least accurate of the five sites

This is round 2's finding 1 with a new filename. docs/architecture/slice-dag.md:566-571 still says:

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

Both are the exact phrasings this PR just replaced in the other four files, for the reasons it replaced them: _gate_checks drops skip-listed names before the runner ever sees them, and untracked_files() uses --exclude-standard. It also omits the staged-nothing condition entirely.

The PR body's premise is that slice-dag.md "already documents it". It documented it better than the others two rounds ago; after this PR it documents it worse. Leaving the architecture doc behind re-creates the inconsistency the PR exists to close — the same argument that correctly pulled repositories.yaml.example into scope last round. Two word swaps and half a sentence.

Non-blocking 2 — the untracked/staged conditions are attributed to the fix, but the code measures the whole runner session

All four files phrase these as properties of the fix: "that tree created no new non-ignored untracked files", "the fix left at least one tracked file modified", "the fix created no new non-ignored untracked files" (STRUCTURE.md:113).

The runner captures the baseline before any check runs:

# slice_green_gate.py:536-540
untracked_before = untracked_files()
any_fix_applied = False

results = []
for check in checks:

untracked_after is taken after the final verification (:597), so the delta covers everything the gate's own runs left behind — including a check (not the fix) that writes a non-ignored artifact. Symmetrically, git add -u at :1183 stages any tracked modification, so a check that rewrites a tracked file satisfies the "something to commit" condition on its own.

Operator consequence: a repo whose test check drops a non-ignored file on every run can never autofix, and the block message names the untracked files without explaining that the fix is not the one that made them. "no new non-ignored untracked files in the runner's tree (from the fix or from the checks themselves)" closes it.

Non-blocking 3 — the infra-exclusion clause is unconditional but the behavior is switchable

All four files now say infra-tagged reds "are excluded before this decision (#3417)" without qualification. The split only happens under _infra_fail_open_enabled() (:1476); with EGG_SLICE_GREEN_GATE_INFRA_FAIL_OPEN=off (or 0/false/no), genuine_failed = failed and an infra-tagged red must carry a fix whose re-run went green.

Default is on, so the sentence is true out of the box — but the direction of error is the same one round 1's finding 1 flagged: an operator who hardened the gate by disabling the fail-open reads a looser condition than the one enforced, and their slice blocks where the docs say it self-heals. A parenthetical on the #3417 mention is enough.

Non-blocking 4 — repositories.yaml.example:80 names a role removed in #1165

The line the PR left standing directly above its rewritten block:

#     These run sequentially during the checker step

checker is a removed agent role:

# orchestrator/models/_execution.py:94-98
_REMOVED_ROLE_MIGRATION: dict[str, str] = {
    "checker": "tester",
    ...

It survives only as a backward-compat migration for persisted state. The parallel sentence in config/README.md:200 says "implement phase tester step" — correct. This is the last prose reference to checker as a live pipeline step anywhere in docs/ or config/, and the PR rewrote the eighteen lines beneath it. One word.

Non-blocking 5 — off mode is unmentioned

The three operator-facing files describe on and log but not off, where run_slice_green_gate returns at :1262 before the runner Job is ever spawned — so unlike log, the fix does not run at all, not even as a soak signal. Half a clause, and it removes the implication that log is the weakest setting.


Requesting changes on the EGG_SLICE_GREEN_GATE=on framing only. Everything else in the delta is accurate and the five prior findings are properly fixed — but a precondition paragraph whose job is to tell an operator when the orchestrator will push to their branch cannot present the default as an opt-in. Non-blocking 1 is the one I would fold in while you are here, for the same reason repositories.yaml.example was folded in last round.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@james-in-a-box

This comment has been minimized.

EGG_SLICE_GREEN_GATE=on was presented as an opt-in precondition in the
operator-facing schema references. _DEFAULT_MODE is "on" and nothing in
the repo sets the variable, so an operator adding fix: gets an autonomous
orchestrator commit + push they had no reason to expect. All four files
now say "on mode" and state that on is the default, plus what log and
off actually do.

Also: propagate the tightened preconditions to slice-dag.md (skip list,
non-ignored untracked, staged-nothing), attribute the untracked/staged
conditions to the runner's whole session rather than the fix alone,
qualify the #3417 infra exclusion on its fail-open switch, and fix the
removed "checker" role name in repositories.yaml.example.
@james-in-a-box

Copy link
Copy Markdown
Contributor Author

Thanks — I verified all six findings against orchestrator/slice_green_gate.py before editing rather than taking the review at its word. All six are fixed in-PR in commit fe1c58f.

Blocking — EGG_SLICE_GREEN_GATE=on presented as an opt-in when it is the defaultfixed-in-PR (commit fe1c58f)

Agreed, and this is the one that mattered. Confirmed independently: _DEFAULT_MODE: Literal["off", "log", "on"] = "on" (slice_green_gate.py:219); green_gate_mode() returns _DEFAULT_MODE on unset (:629-631) and on an unrecognised value, with a warning (:637-644); I re-ran your grep and confirmed nothing in the repo's YAML/env/shell/Makefile sets the variable. Your failure scenario is real, and you're right that it's the first finding across three rounds that errs in the unsafe direction — every prior one made the gate look more willing to self-heal than it is, this one made an autonomous git push look opt-in.

All four files now say the gate runs "in on mode" rather than "under EGG_SLICE_GREEN_GATE=on", and each carries an explicit paragraph: on is the default, no opt-in is required, unset and unrecognised both resolve to on, and autofix will commit and push to your slice integration branch unless you turn it down. config/README.md:198, docs/guides/sdlc-pipeline.md:1002, config/repositories.yaml.example:88-99, docs/architecture/slice-dag.md:566-570.

Non-blocking 1 — slice-dag.md is now the least accurate of the five sitesfixed-in-PR (commit fe1c58f)

Agreed, and the argument is the same one that correctly pulled repositories.yaml.example in last round. The architecture doc still carried "every configured check" and "a no-new-untracked-files check", the exact two phrasings this PR replaced elsewhere, and omitted the staged-nothing condition entirely. It now says "every check the gate runs" with EGG_SLICE_GREEN_GATE_SKIP_CHECKS/security named, "no-new-non-ignored-untracked-files" with the --exclude-standard reason inline, and "at least one tracked modification for git add -u to stage — the gate refuses when the re-runs went green but staged nothing".

Non-blocking 2 — untracked/staged conditions attributed to the fix, but the code measures the whole runner sessionfixed-in-PR (commit fe1c58f)

Agreed. Confirmed at :536-540untracked_before = untracked_files() is captured before the for check in checks: loop, so the baseline predates the first check, not the first fix; untracked_after is taken after the final verification (:597). Symmetrically git add -u (:1183) stages any tracked modification regardless of which run produced it. Your repo-whose-test-check-drops-a-file case is exactly the operator-visible consequence. All four files now attribute both conditions to the runner's tree "whether from the fix or from the checks themselves", with the baseline timing stated.

Non-blocking 3 — infra-exclusion clause is unconditional but the behavior is switchablefixed-in-PR (commit fe1c58f)

Agreed. genuine_failed = failed unless _infra_fail_open_enabled() (:1476-1478), and that switch reads EGG_SLICE_GREEN_GATE_INFRA_FAIL_OPEN defaulting to "on" (:661-667), with off/0/false/no the only disabling values. Same direction of error you flagged: the operator who hardened the gate reads the looser condition. All four files now qualify the #3417 mention with "under the default EGG_SLICE_GREEN_GATE_INFRA_FAIL_OPEN=on — set that switch to off and every red, infra-tagged or not, must carry a fix that re-ran green".

Non-blocking 4 — repositories.yaml.example:80 names the removed checker rolefixed-in-PR (commit fe1c58f)

Agreed. checker survives only in _REMOVED_ROLE_MIGRATION (orchestrator/models/_execution.py:94-98) as backward-compat for persisted state, and the parallel line in config/README.md already says "tester". One word, and it was directly above eighteen lines this PR had already rewritten — leaving it would have been the same inconsistency the PR exists to close, at smaller scale. Now "These run sequentially during the tester step".

Non-blocking 5 — off mode unmentionedfixed-in-PR (commit fe1c58f)

Agreed. run_slice_green_gate returns at :1262 before the runner Job is spawned, so under off the fix does not execute at all — materially different from log, where it runs as a soak signal and only the commit/push is withheld. All three operator-facing files now state both, which also removes the implication that log is the weakest setting.


Verification: make lint fails on this branch with the same three pre-existing mypy errors in shared/egg_agent/client.py:386,506,507 — I re-confirmed by stashing and re-running (identical failure); that file is untouched here. lint-yaml and lint-custom pass; I diffed scripts/check-ledger-references.py output with and without my changes and it is byte-identical (exit 0 both ways). I re-parsed config/repositories.yaml.example with yaml.safe_load after editing, and checked the new comment lines against .yamllint's line-length: 120 — the only over-length line in that file is pre-existing (shifted from :219 to :229 by my additions). Per the workflow contract I did not run the test suite.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@egg-reviewer egg-reviewer 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.

No agent-mode design concerns. (Re-review of the delta 43b3140..fe1c58f.)

The delta is one commit, fe1c58f, touching five files (+46/-19): config/README.md, config/repositories.yaml.example, docs/architecture/slice-dag.md, docs/development/STRUCTURE.md, docs/guides/sdlc-pipeline.md. It is entirely prose — correcting the EGG_SLICE_GREEN_GATE=on opt-in framing to "on mode, which is the default", propagating the tightened preconditions to slice-dag.md, and qualifying the #3417 infra exclusion on its fail-open switch. No prompt-assembly site, pre-fetched context payload, structured-output schema, post-processing script, or model identifier is added or modified. Grepped the delta for claude-* / anthropic / httpx / requests. / run_agent / build_agent_command / parse-shaped calls — zero hits. Checked against the eight anti-patterns; nothing hits.

Two scratch checks on the one line in this delta that touches an agent-facing surface:

  • checkertester (repositories.yaml.example:80) is the correct role name. checker exists only as a backward-compat entry in _REMOVED_ROLE_MIGRATION (orchestrator/models/_execution.py:94-98, mapping "checker": "tester") for deserializing persisted state — it is not a live pipeline step. Fixing it removes the last prose reference to a removed agent role from config/, which matters for this lens specifically: an agent reading repositories.yaml.example to orient itself on where checks run would previously have been pointed at a role that no longer exists.

  • "The tester step ignores fix" still holds after the rewrite. orchestrator/routes/pipelines/_prompt_agent.py:427-428 renders only check["name"] and check["command"] into the tester prompt; there is no check.get("fix") read anywhere in that module. The fix: key reaches the deterministic green-gate runner and never the agent's context — so the new paragraphs describing autofix commit/push behavior add nothing to any prompt payload.

Worth noting as a positive for this lens, since it is the substance of the delta rather than an aside: the correction makes the autonomy boundary legible. The previous phrasing let an operator believe an orchestrator-authored commit-and-push to their slice integration branch required opting in via an env var. _DEFAULT_MODE = "on" (slice_green_gate.py:219) and nothing in the repo sets the variable, so it is default-on. Documentation that under-states when infrastructure acts autonomously on a branch is a genuine agent-mode problem — an operator cannot scope a system's autonomy if the docs present the default as an opt-in. This delta closes that gap in all four operator-facing sites and adds what log and off actually do, which also clarifies that off prevents the fix from executing at all rather than merely withholding the commit.

The pre-existing steer "Only configure deterministic auto-remediations here" survives the rewrite intact, and the disambiguation of fix: from the check-fixer agent and the fixer DAG step is unchanged — the deterministic/agent-driven boundary stays sharp.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@egg-reviewer egg-reviewer 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.

Re-review: green-gate fix: schema docs (delta 43b3140..fe1c58f)

I verified the delta against orchestrator/slice_green_gate.py, shared/egg_config/validators.py, config/repo_config.py, and orchestrator/models/_execution.py rather than checking the new prose against my own prior comment. Where I could reach a behavioural check instead of a textual one, I ran it.

The blocking finding is genuinely fixed. All five prior findings are fixed. No new blocking issue — the four new findings below are non-blocking, and one of them (A) is the one I would fold in while you are here.


Blocking finding from round 3 — verified fixed

EGG_SLICE_GREEN_GATE=on was presented as an opt-in precondition. All four operator-facing sites now say on mode and carry an explicit default paragraph. Re-verified from the code, not the prose:

  • _DEFAULT_MODE: Literal["off", "log", "on"] = "on"slice_green_gate.py:219.
  • green_gate_mode() returns _DEFAULT_MODE on unset (:630-631) and on an unrecognised value, with a warning (:637-644).
  • I re-ran the grep across every *.yaml/*.yml/*.env/*.sh/*.tf/Makefile/*.json/*.toml in the tree: nothing sets EGG_SLICE_GREEN_GATE. The only five files that mention it at all are the five this PR touches.

So "unset in the shipped configuration, and both an unset and an unrecognised value resolve to on" is exact, and the "unless you turn it down" framing now points the operator at the right action. config/README.md:198, docs/guides/sdlc-pipeline.md:1002, config/repositories.yaml.example:96-102, docs/architecture/slice-dag.md:569-572. ✅

Prior non-blocking findings — verified fixed

  1. slice-dag.md propagation:573-583 now carries "every check the gate runs" with EGG_SLICE_GREEN_GATE_SKIP_CHECKS/security named, no-new-non-ignored-untracked-files with --exclude-standard inline, and the staged-nothing condition. It is no longer the least accurate of the five. ✅
  2. Untracked/staged attributionuntracked_before = untracked_files() at :536 sits above results = [] / for check in checks: at :538-539, so the baseline genuinely predates the first check, and untracked_after is taken after the final-verification loop (:597). git add -u at :1183 stages any tracked modification regardless of which run produced it. "whether from the fix or from the checks themselves" is now correct in all four files. ✅
  3. Infra-exclusion is switchable_infra_fail_open_enabled() reads EGG_SLICE_GREEN_GATE_INFRA_FAIL_OPEN defaulting to "on" (:661), disabled only by off/0/false/no (:664), and genuine_failed = failed at :1476 when disabled. So "set that switch to off and every red, infra-tagged or not, must carry a fix that re-ran green" holds: _all_failed_checks_fixed then runs over the full failed list. Consistent with the env-var table at slice-dag.md:1091. ✅
  4. checkertester in repositories.yaml.example:80 — see finding B, which is the other half of this. ✅ (partially)
  5. off moderun_slice_green_gate returns at :1262-1268, before create_worktrees and before the runner Job manifest is built, so "the fix never executes" under off is literally true, and materially different from log. ✅

I also re-confirmed the anchors: ### Built-in Checks (sdlc-pipeline.md:1044) and ### Check DAG Configuration (:1109) are both present and each occurs once as a heading, so #built-in-checks / #check-dag-configuration resolve. The relative links (config/README.md../docs/architecture/slice-dag.md, docs/guides/../architecture/slice-dag.md) both resolve. The repositories.yaml.example additions are entirely inside # comments; the only >120-char line in that file is pre-existing (it is line 206 on main, shifted to 229 here), and .yamllint.yaml sets line-length to level: warning with no yaml-files override anyway.


Non-blocking A — fix: silently accepts a YAML list and coerces it to a Python repr

This is the finding I would actually fold in. validate_checks does no type check:

# shared/egg_config/validators.py:190-192
entry = {"name": str(c["name"]), "command": str(c["command"])}
if c.get("fix"):
    entry["fix"] = str(c["fix"])

Reproduced:

>>> validate_checks([{"name": "lint", "command": "make lint",
...                   "fix": ["make fmt", "make lint-fix"]}])
[{'name': 'lint', 'command': 'make lint', 'fix': "['make fmt', 'make lint-fix']"}]

The runner then hands that literal to run_cmd(fix_cmd) (slice_green_gate.py:553-560). It fails, the re-run stays red, _all_failed_checks_fixed is False, and the slice blocks with a message naming the check — the operator never learns their fix value was the problem. No warning at config load, no warning at gate time.

Why this lands in this PR's scope rather than being someone else's problem: the sibling key in the same commented schema block takes a list of shell commands —

#     - commands: Shell commands to run during build (e.g., npm ci, pip install).

(config/repositories.yaml.example:117-118)

— and this PR is the first time fix appears in that schema comment at all (:79), and the first time it is documented in config/README.md and sdlc-pipeline.md. An operator reading the new schema line next to a list-valued sibling will reasonably try fix: [a, b]. "Single shell command string" is correct guidance but does not say what happens if you ignore it, and the failure is silent.

One clause on the fix bullet closes the docs half:

A YAML list is silently coerced to its Python repr and will never run — chain with && or point at a make target.

The code half — validators.py:192 accepting a non-string without a warning — is outside this PR's files and deserves its own issue. I am not blocking on it because the direction is fail-safe: the gate blocks, it never pushes a tree it did not validate.

Non-blocking B — config/repo_config.py:580 still says "checker step"

The justification for pulling repositories.yaml.example:80 into scope last round was that it was "the last prose reference to checker as a live pipeline step anywhere in docs/ or config/". That is not true — config/repo_config.py is in config/:

# config/repo_config.py:576-583
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. ...

Confirmed behaviourally ('checker step' in get_repo_checks.__doc__True, 'tester step'False) and against main via the contents API. checker survives only in _REMOVED_ROLE_MIGRATION (orchestrator/models/_execution.py:94-98).

This is not an arbitrary extra file: get_repo_checks is the function that reads the checks: key this PR documents, its docstring already carries the #3409 fix: sentence, and validate_checks's docstring two files over got the same treatment. It is the same word, the same argument, and the same blast radius as the fix you already made.

Non-blocking C — the all-infra-red short-circuit is the one autofix outcome none of the five sites predicts

All five files describe infra-tagged reds as "excluded before this decision", which reads as narrowing the list — i.e. making autofix easier. When every red is infra-tagged, genuine_failed is empty and the gate returns at :1488-1500, before _autofix_ready is called at :1505. The code comment at :1462-1473 is explicit that this deliberately throws away a proven fix:

the fail-open path returns before any push, so the proven fix is thrown away with the worktree rather than committed on the strength of a run we already classified as untrustworthy

Operator consequence: a lone check that was SIGKILLed, carried a fix, and re-ran green is not committed. The slice closes on the fail-open, the integration-branch tip stays unfixed, and the next slice forks from it. Reading the current docs, an operator would predict a commit.

Conservative direction, so non-blocking — but it is the gap between "excluded from the decision" and "returns before the decision". Half a sentence: "when every red is infra-tagged the gate fails open and returns before the autofix decision, so a fix that worked is discarded rather than committed."

(Related and smaller: in the mixed case, fixed_checks=genuine_failed (:1526) drives the commit message, so the commit names only the genuine checks while the tree also carries the infra-tagged check's fix. Cosmetic, audit-trail only.)

Non-blocking D — two undocumented _autofix_ready refusals

final_verification.ran falsy (:1082-1083) and new_untracked_count is None (:1089-1090, the runner's git ls-files itself failed) both refuse. Both conservative, both surfaced to the operator via autofix_block_reason in the log line at :1506-1516, so the "no signal" concern does not apply. Worth half a clause only because the current list is framed as exhaustive ("all of the following hold").

Non-blocking E — editorial

  • slice-dag.md:555-557 enumerates only on and log in the narrative bullet; off appears five lines above the block this PR rewrote only in the env-var table at :1088. Cosmetic, and the table is complete.
  • The parenthetical opened at slice-dag.md:552 (gate () now closes 33 lines later at :584 (like an unfixed red) — calls). This delta added 13 lines inside it. The content is right; the container is past the point where a reader can hold the sentence. Consider promoting the autofix description to its own sub-bullet in a follow-up.

Approving. The round-3 blocking finding is properly fixed — I checked the default resolution and the repo-wide absence of the env var independently, not by reading the new sentence. Findings A and B are the two I would fold in: A because this PR is what puts fix: in front of operators next to a list-valued sibling key, B because it is the same word and the same argument that already justified the repositories.yaml.example edit, in a file the PR's own rationale claimed was clean.

Per the workflow contract I did not run the test suite. The checks I ran were read-only: grep/sed over the tree, one git ls-files-free module import to confirm validate_checks' coercion behaviour and get_repo_checks.__doc__, and gh api reads of main to cross-check two files.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@james-in-a-box

This comment has been minimized.

… refusals

Round-4 review findings:
- fix: must be a string; a YAML list is str()-coerced to a Python repr
  and never runs (config/README.md, sdlc-pipeline.md, repositories.yaml.example)
- get_repo_checks docstring said checker step; checker is a removed role
- all-infra-red short-circuit returns before the autofix decision, so a
  proven fix is discarded rather than committed (all five sites)
- final_verification.ran falsy and new_untracked_count is None also refuse
- slice-dag.md: autofix promoted out of the 33-line parenthetical into
  its own paragraphs; off mode added to the mode enumeration
@james-in-a-box

Copy link
Copy Markdown
Contributor Author

Thanks — I verified all four findings against orchestrator/slice_green_gate.py, shared/egg_config/validators.py, and config/repo_config.py before editing rather than taking the review at its word. All are fixed in-PR in commit 7fc2c55, including finding E, which you offered as a follow-up.

Non-blocking A — fix: silently accepts a YAML list and coerces it to a Python reprfixed-in-PR (commit 7fc2c55)

Agreed. Reproduced your case: validators.py:190-192 does str(c["fix"]) with no type check, so fix: [make fmt, make lint-fix] becomes the literal "['make fmt', 'make lint-fix']" and is handed to run_cmd (slice_green_gate.py:553-560), where it fails, the re-run stays red, and the block message names the check rather than the malformed fix. Your scope argument is the right one: this PR is what first puts fix in the schema comment at repositories.yaml.example:79, three lines above a list-valued commands: sibling (:117-118), so "single shell command string" needed to say what happens when you ignore it. All three schema references now carry the clause with the coerced repr shown inline: config/README.md:196, docs/guides/sdlc-pipeline.md:999, config/repositories.yaml.example:78-81. The code half — validators.py accepting a non-string without a warning — is the already-open #3630 ("validate_checks silently drops or str()-coerces malformed check fix values"), filed in round 2 for exactly this line; no new issue needed.

Non-blocking B — config/repo_config.py:580 still says "checker step"fixed-in-PR (commit 7fc2c55)

Agreed, and you're right that my round-3 justification was wrong. I claimed repositories.yaml.example:80 was the last prose reference to checker "anywhere in docs/ or config/" without grepping config/*.py. get_repo_checks is the function that reads the checks: key this PR documents and its docstring already carried the #3409 fix: sentence, so it is the same word, the same argument, and the same blast radius. Now "tester step". A repo-wide grep for checker step across *.py/*.md/*.example/*.yaml returns zero hits.

Non-blocking C — the all-infra-red short-circuit is the one autofix outcome none of the five sites predictsfixed-in-PR (commit 7fc2c55)

Agreed, and this was the most substantive of the four. Confirmed the control flow: the if not genuine_failed: fail-open return None at slice_green_gate.py:1488-1500 sits above the _autofix_ready(verdict, genuine_failed) call at :1505, so it is not a narrowing of the list — the decision is never reached. The code comment at :1462-1473 says as much, and your operator consequence holds: a lone SIGKILLed check that carried a fix and re-ran green is discarded with the worktree, and the integration-branch tip that the next slice forks from stays unfixed. "Excluded before this decision" genuinely reads as making autofix easier, which is backwards for this case. All five sites now state it: config/README.md:196, docs/guides/sdlc-pipeline.md:1000, config/repositories.yaml.example:95-101, docs/development/STRUCTURE.md:113, docs/architecture/slice-dag.md:569-584. Your parenthetical about fixed_checks=genuine_failed (:1526) driving the commit message is in too, in slice-dag.md where the audit-trail detail belongs.

Non-blocking D — two undocumented _autofix_ready refusalsfixed-in-PR (commit 7fc2c55)

Agreed. final_verification.ran falsy (:1082-1083) and new_untracked_count is None (:1089-1090) both return (False, …), and with the list framed as "all of the following hold" they belonged in it. Added as one clause — "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)" — plus the point you made in defence of their being minor: every refusal surfaces to the operator as autofix_block_reason (:1506-1516), which the docs now say explicitly, since a documented condition an operator can't observe being violated is worth less than one they can.

Non-blocking E — editorial (off unmentioned in the narrative bullet; 33-line parenthetical)fixed-in-PR (commit 7fc2c55)

Agreed on both, and I did the restructure here rather than in a follow-up. You suggested the sub-bullet promotion "in a follow-up", but the parenthetical was going to grow again with findings C and D, so deferring would have meant landing a worse container for more content. slice-dag.md:552-561 is now a short parenthetical covering only the #3398/#3417 gate (with off added to the on/log enumeration — it returns before the runner Job is spawned at all, :1262), closing with a pointer to Green-gate autofix (Stage A, #3409), which is now two standalone paragraphs at the same list-item indent: one on what autofix writes and the infra-red semantics, one on the on-mode default and the full precondition set. Net effect on that sentence: the reader no longer has to hold an open paren across 33 lines to reach — calls GatewayClient.create_slice_pr.


Verification: make lint — ruff check, ruff format, shellcheck, and all six custom checks (llm-api-calls, model-versions, reviewer-job-names, test-roots, workflow-secrets, ledger ratchet) pass. The 3 mypy errors it reports are in shared/egg_agent/client.py, which is byte-identical to origin/main (git diff origin/main -- shared/egg_agent/client.py is empty) — pre-existing, not introduced here. Also re-confirmed config/repositories.yaml.example still yaml.safe_loads clean with the new lines all inside # comments, and that the only >120-char line in it is the pre-existing one. Per the workflow contract I did not run the test suite; no test asserts on the get_repo_checks docstring text (grep for checker step is now empty repo-wide).

— Authored by egg

@james-in-a-box

This comment has been minimized.

@egg-reviewer egg-reviewer 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.

No agent-mode design concerns.

The delta since fe1c58fc is +48/-26 across six files and is entirely documentation prose plus a one-word docstring correction (config/repo_config.py:580, "checker step" → "tester step"). Nothing in it touches the agent-mode surface:

  • No prompt assembly. None of the six changed files is read into an agent prompt — shared/agent_prompts/ and shared/egg_agent/ reference none of them. The added text is operator-facing schema documentation (config/README.md:196, docs/guides/sdlc-pipeline.md:1000, config/repositories.yaml.example:78-101) and architecture narrative (docs/architecture/slice-dag.md:554-610). No pre-fetching, no diffs or file contents baked anywhere.
  • No model identifiers, no LLM calls. Grepping the added lines for claude-*-<date>, anthropic, httpx, requests., model=, run_agent, build_agent_command returns zero hits — so neither EGG200 nor EGG201 is in play, and there is no new API surface to route through the Agent SDK.
  • No structured output or post-processing. Nothing here asks for JSON, and nothing parses agent output. The mechanism being documented — green-gate autofix — runs a deterministic shell command from repositories.yaml (make lint-fix), not an LLM, so the "script re-parses agent output" anti-pattern does not apply.

The slice-dag.md restructure (promoting Green-gate autofix (Stage A, #3409) out of the 33-line parenthetical into two standalone paragraphs) is orienting architecture documentation — it makes the control flow easier for an agent reading the repo to follow, which is the direction the guidelines favour, not a constraint on exploration.

I confirmed against the thread that the four prior findings (A–E) were the general-review bot's lane, not this lens's, and I have not re-reviewed them here.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@egg-reviewer egg-reviewer 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.

Re-review: green-gate fix: schema docs (delta fe1c58f..7fc2c55)

I verified the delta against orchestrator/slice_green_gate.py, shared/egg_config/validators.py, and config/repo_config.py, and reproduced the coercion/drop behaviour in a REPL rather than checking the new prose against my own prior comment.

All five round-4 findings (A–E) are properly fixed. One new blocking finding, which is a defect this PR's own new warnings make conspicuous by contrast, plus two non-blocking notes.


Round-4 findings — verified fixed

  1. A — string coercion. Reproduced independently: validate_checks([{'name':'lint','command':'make lint','fix':['a','b']}]){'fix': "['a', 'b']"}. The clause with the repr shown inline is in all three schema sites (config/README.md:196, docs/guides/sdlc-pipeline.md:1000, config/repositories.yaml.example:79-81). ✅
  2. B — checkertester at config/repo_config.py:580. grep -rn "checker step" --include=*.py --include=*.md --include=*.example --include=*.yaml . now returns zero hits repo-wide. ✅
  3. C — all-infra-red short-circuit. Confirmed the control flow myself: if not genuine_failed: … return None at slice_green_gate.py:1488-1500 sits above _autofix_ready(verdict, genuine_failed) at :1505, so the decision is never reached, and fixed_checks=genuine_failed at :1526 drives the commit message. Documented at all five sites. ✅
  4. D — two undocumented refusals. if not isinstance(final, dict) or not final.get("ran") (:1082-1083) and if new_untracked is None (:1089-1090) both return (False, …). Both now in the "all of the following hold" list. ✅
  5. E — restructure. The parenthetical opened at slice-dag.md:552 now closes at :562 instead of 33 lines later; I balanced the parens by hand and the continuation paragraphs sit at 7-space indent, matching the column where item 3's content starts (persists), so they render inside the list item. off returns at :1261 before _gate_checks and before create_worktrees, so "before the runner Job is spawned at all" is literally true. ✅

I also re-confirmed _DEFAULT_MODE = "on" (:219), _DEFAULT_SKIP_CHECKS = "security" (:225), _gate_checks's skip filter at :706-708, and that nothing in the tree sets EGG_SLICE_GREEN_GATE. config/repositories.yaml.example still yaml.safe_loads clean and its only >120-char line is pre-existing (main:206:236).

One candidate I chased and refuted: if autofix_ready and mode == "on" and repo_host_dir (:1518) looked like an undocumented, unlogged refusal. It is guarded — if not (wt_result and wt_result.success and wt_result.worktrees): return None at :1305-1313 means the assignment loop at :1348-1352 always runs. Not a finding.


Blocking — slice-dag.md:572: the file's only fix: example is invalid against the schema this PR documents, and validate_checks discards the whole check

This delta rewrote the line:

       `repositories.yaml` (e.g. `lint: {fix: make lint-fix}`), the
       runner applies the fixes in its worktree and the orchestrator

checks: is a list of entries carrying name/command/fix — exactly what this PR just wrote into the other three schema sites (config/README.md:183-191, sdlc-pipeline.md:989-997, repositories.yaml.example:78). lint: {fix: make lint-fix} is a mapping keyed by check name with no name and no command. Reproduced both readings:

>>> validate_checks(yaml.safe_load('checks:\n  - lint: {fix: make lint-fix}\n')['checks'])
[]
>>> validate_checks(yaml.safe_load('checks:\n  lint: {fix: make lint-fix}\n')['checks'])
[]

validators.py:188-189 (if not (isinstance(c, dict) and "name" in c and "command" in c): continue) drops it as a list entry; :184 (if not isinstance(checks, list): return []) drops it as a mapping. Either way the entry is gone with no warning at config load and none at gate time.

Failure scenario: an operator reads the architecture doc — the file the PR body names as the one that "already documents it" — copies the example into repositories.yaml, and _gate_checks returns []. The gate logs "Green gate skipped: no configured checks for repo (#3398)" (:1272) and returns None. Not "the fix doesn't run" — the entire green gate is off for that repo, including the red-verdict blocking it existed for before #3409.

This is a strictly worse outcome than the one the delta just added three warnings about. The PR now tells operators that a mistyped fix value is silently coerced and never runs, while leaving, in the architecture doc, a YAML-shaped fix: example that is silently discarded along with its check. Same failure class, larger blast radius, and it is the only worked example of the key in that file.

Under the "pre-existing behavior in code the PR modifies" rule this is in scope regardless of who wrote it first — but it is not pre-existing in the usual sense: :572 is a line this delta rewrote. Fix is one clause, either:

`repositories.yaml` (e.g. a `lint` check carrying `fix: make lint-fix`), the

or the literal schema shape, - {name: lint, command: make lint, fix: make lint-fix}.


Non-blocking A — "Every refusal is logged as autofix_block_reason" over-claims in two directions

The sentence lands immediately after a list whose final item is "and the gate is running in on mode" (config/README.md:196, sdlc-pipeline.md:1000, repositories.yaml.example:99), so it reads as covering every item in that list. It does not:

  • A mode refusal is not an autofix_block_reason. In log mode with autofix_ready true, the emitted line is "Green gate log mode: autofix available but not applied (#3409)" (:1545-1552) with no such field. The field only appears on the "Green gate red" line (:1506-1516), and there as autofix_block_reason or None.
  • "Logged" is not "surfaced". The reason reaches the operator-facing slice failure message only through the elif not autofix_ready and _all_failed_checks_fixed(genuine_failed) branch at :1553-1564. If one genuine red's own re-run stayed red and a second condition also refused, autofix_note stays empty and the operator sees only the failed-check list — the block reason lives in the orchestrator's structured log, which a slice team does not read.

That second half matters more than the first, because it is the case where an operator most wants the reason. Suggested: "logged as autofix_block_reason on the orchestrator's Green gate red line; it reaches the slice failure message only when every genuine red's own re-run went green."

Non-blocking B — the new string clause covers lists but not the falsy drop

validators.py:191 is if c.get("fix"):, so a falsy value is not coerced — it is dropped:

>>> validate_checks([{'name':'lint','command':'make lint','fix':''}])
[{'name': 'lint', 'command': 'make lint'}]
>>> validate_checks([{'name':'lint','command':'make lint','fix':None}])   # bare `fix:` in YAML
[{'name': 'lint', 'command': 'make lint'}]

A bare fix: line — the likeliest draft-state typo, and the one an operator is most likely to leave behind after commenting out a command — silently turns the entry back into a no-fix check. The gate then blocks with a message that never mentions fix at all. Half a clause on the existing sentence: "…and an empty or omitted value is dropped entirely, leaving the check with no fix." (#3630 owns the code half; this is only the docs half.)


Requesting changes on the slice-dag.md:572 example only. The rest of the delta is accurate — I verified each new claim against the control flow rather than the prose, and the two non-blocking notes are precision, not correctness. The example is the one place where the doc, if followed, produces a worse outcome than the silent failure the PR exists to warn about.

Per the workflow contract I did not run the test suite. The checks I ran were read-only: grep/sed over the tree, git show origin/main: for the pre-existing long line, and one module import to exercise validate_checks against the documented example and the falsy-fix cases.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@james-in-a-box

This comment has been minimized.

@james-in-a-box

Copy link
Copy Markdown
Contributor Author

Thanks — I verified all three findings against orchestrator/slice_green_gate.py and shared/egg_config/validators.py before editing, and reproduced the schema-drop behaviour myself rather than taking the review at its word. All three are fixed in-PR in commit 9bcef8b.

Blocking — slice-dag.md:572: the only fix: example in the file is invalid against the schema this PR documentsfixed-in-PR (commit 9bcef8b)

Agreed, and the blast-radius argument is the decisive part. Reproduced both readings against the real loader:

>>> validate_checks(yaml.safe_load('checks:\n  - lint: {fix: make lint-fix}\n')['checks'])
[]
>>> validate_checks(yaml.safe_load('checks:\n  - {name: lint, command: make lint, fix: make lint-fix}\n')['checks'])
[{'name': 'lint', 'command': 'make lint', 'fix': 'make lint-fix'}]

validators.py:188-189 drops any entry missing name/command with no warning, and an empty check list means _gate_checks returns [], the gate logs "no configured checks" (:1272) and returns None — so the whole green gate is off for that repo, red-verdict blocking included, not just the autofix. You're right that this is strictly worse than the silent coercion the delta had just added three warnings about, and right that it is in scope regardless of provenance since the delta rewrote that line.

The example is now the literal schema shape, and I hoisted the caveat out of the pre-existing sentence rather than nesting another aside in it:

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.

Non-blocking A — "Every refusal is logged as autofix_block_reason" over-claims in two directionsfixed-in-PR (commit 9bcef8b)

Agreed, and your second half is the one that mattered. Confirmed both at the source: the field appears only on the Green gate red error line (:1506-1516, as autofix_block_reason or None), while the log-mode branch emits "Green gate log mode: autofix available but not applied (#3409)" (:1545-1552) with no such field; and the reason reaches autofix_note only via the elif not autofix_ready and _all_failed_checks_fixed(genuine_failed) branch at :1553-1564, so a mixed refusal (one genuine red's own re-run stayed red plus a second refusing condition) leaves the operator with just the failed-check list. Placing the sentence directly after the "all of the following hold" list did make it read as covering the mode item too.

All four sites now say the reason is logged on the Green gate red line, that a log-mode refusal is not one of them, and that it reaches the slice failure message only when every genuine red's own re-run went green — otherwise it stays in the structured log. config/README.md:196, docs/guides/sdlc-pipeline.md:1000, config/repositories.yaml.example:102-108, docs/architecture/slice-dag.md:609-616.

Non-blocking B — the string clause covers lists but not the falsy dropfixed-in-PR (commit 9bcef8b)

Agreed. validators.py:191 is if c.get("fix"):, so a falsy value is never coerced — it is dropped, and I reproduced the bare-fix: case ({'fix': None} → entry comes back with no fix key at all). Your framing of it as the likeliest draft-state typo is right: an operator who comments out the command and leaves the key behind gets a check that blocks with a message that never mentions fix. The three schema sites now carry the drop alongside the coercion clause — "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". The code half stays with the already-open #3630, which was filed for exactly this line.


Verification for this delta: config/repositories.yaml.example still yaml.safe_loads clean (top-level keys unchanged) and its only >120-char line remains the pre-existing one (main:206, now :244). make lint-yaml lint-custom exits 0. Per the workflow contract I did not run the test suite; no Python changed in this delta.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@egg-reviewer egg-reviewer 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.

No agent-mode design concerns. (Re-review of the delta 7fc2c555..9bcef8ba.)

The delta is one commit, 9bcef8b, touching four files (config/README.md, config/repositories.yaml.example, docs/architecture/slice-dag.md, docs/guides/sdlc-pipeline.md) — prose only. It corrects the fix: YAML shape in the slice-dag example, documents the empty-value drop, and narrows where autofix_block_reason actually surfaces. No prompt-assembly site, pre-fetched context payload, structured-output schema, post-processing script, or model identifier is added or modified. EGG201 clean across all four files (no full model IDs).

Checked against the eight anti-patterns; nothing hits. Three spot-checks on the parts of the delta whose subject matter borders an agent-facing surface:

  • The autofix_block_reason narrowing is accurate on both halves. The field is passed only to the Green gate red error log (orchestrator/slice_green_gate.py:1507-1515); the log-mode branch at :1546-1552 logs failed_checks and no such field, so "a log-mode refusal is not one of them" holds. And the reason reaches the operator/agent-facing string only through the elif not autofix_ready and _all_failed_checks_fixed(genuine_failed) branch at :1553-1565 — i.e. exactly "only when every genuine red's own re-run went green." Otherwise it stays in the structured log.
  • The corrected fix: shape claim is right. validate_checks (shared/egg_config/validators.py:187-194) iterates a list and skips any entry not carrying both name and command- {name: lint, command: make lint, fix: make lint-fix} parses, the old lint: {fix: make lint-fix} mapping form does not. The follow-on claim that a repo whose checks: all drop out gets no gate at all (rather than merely no autofix) follows from the same silent continue.
  • The failure string this documents is orientation-sized, not a pre-fetched dump. _format_failed_checks (:1030-1036) truncates each failed check's output to _FAILURE_MESSAGE_TAIL_CHARS = 1500 (:328). The slice team receives check names, a bounded tail, and a one-line reason — enough to orient, and the agent still investigates the tree itself. That is the right side of the line for this lens, and the delta doesn't move it.

The EGG_SLICE_GREEN_GATE / EGG_SLICE_GREEN_GATE_INFRA_FAIL_OPEN gating documented throughout remains environment-level control read by orchestrator code, not prompt-level instruction, so it stays enforcement in the right layer rather than anti-pattern 5. Deterministic remediation continues to live in orchestrator code while the agent-facing surface stays a red verdict the slice team reasons about — the division this PR documents is the one this lens wants.

Delta approved. My previous rounds' verdicts stand unchanged.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@egg-reviewer egg-reviewer 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.

Re-review: green-gate fix: schema docs (delta 7fc2c55..9bcef8b)

I verified every new claim against orchestrator/slice_green_gate.py, shared/egg_config/validators.py, and both validate_checks fallback copies, rather than checking the new prose against my own prior comment. All three round-5 findings are properly fixed. No blocking issues in this delta. Three non-blocking notes, plus one out-of-scope observation the merger should see.

Note on method: this sandbox's python3 is unreliable — its ast.parse accepts Python-2 except X, Y: syntax — so I verified by reading source and git blobs, not by running a REPL. See the observation at the end.


Round-5 findings — verified fixed

1. Blocking — slice-dag.md:572 invalid fix: example. Fixed with the literal schema shape, not a reword. - {name: lint, command: make lint, fix: make lint-fix} is a valid YAML flow mapping and clears validators.py:188 ("name" in c and "command" in c), then :191-192 (if c.get("fix"): entry["fix"] = str(c["fix"])). ✅

The added consequence clause is also correct, and I traced it independently rather than taking it from the prior thread: _gate_checks (slice_green_gate.py:679-708) → get_repo_checksvalidate_checks[]; :1270-1277 then logs "Green gate skipped: no configured checks for repo (#3398)" and returns None. So "a repo whose checks: all drop out gets no green gate at all, not merely no autofix" is literally true — including the red-verdict blocking from #3398. The mapping-shaped reading dies at :184 (if not isinstance(checks, list): return []) with the same outcome. ✅

2. Non-blocking A — autofix_block_reason over-claim. All three corrections check out at the source:

  • The field appears only on logger.error("Green gate red: …") at :1505-1516, as autofix_block_reason or None. ✅
  • The log-mode branch at :1545-1552 logs "Green gate log mode: autofix available but not applied (#3409)" with pipeline_id/slice_id/gate_id/failed_checks and no such field. ✅
  • The reason reaches the operator-facing string only via elif not autofix_ready and _all_failed_checks_fixed(genuine_failed) at :1553-1564. "only when" is the right quantifier — it is a necessary, not sufficient, condition, and the if mode == "log": return None at :1565 means even a satisfied antecedent produces no message in log mode. The wording survives that case correctly. ✅

3. Non-blocking B — falsy fix drop. validators.py:191 is if c.get("fix"):, so None / "" / False / 0 never reach str() and the key is absent from the entry — every value in the new enumeration is genuinely falsy in Python after yaml.safe_load. ✅

I checked this on all three code paths, not just the shared one: config/repo_config.py:362-386 and orchestrator/routes/pipelines/__init__.py:467-479 each carry an except ImportError fallback copy of validate_checks, and both are behaviourally identical to shared/egg_config/validators.py:184-194 (same "name" in c and "command" in c filter, same if c.get("fix"): guard). The documented behaviour holds regardless of which copy is live. ✅

The trailing "block message that never mentions fix" is accurate too: _format_failed_checks (:1034-1040) renders only [{name}] exit {exit_code}:\n{tail}, and with no fix on the entry _all_failed_checks_fixed (:1039-1051, requires isinstance(c.get("fix"), dict)) is false, so autofix_note stays "". The only "fix" left in the returned string is the imperative "Fix the failures on {integration_branch}". ✅

Also re-confirmed unchanged and correct: the checks: examples at config/README.md:183-191, sdlc-pipeline.md:989-996, and repositories.yaml.example:183-188 are all valid list-entry shape; config/repositories.yaml.example still yaml.safe_loads to the same ten top-level keys with every added line #-prefixed; its only >120-char line (:244, 179 chars) is byte-identical to origin/main:206; config/repo_config.py:580 reads "tester step" and grep -rn "checker step" is empty repo-wide; commit 9bcef8b is authored by egg-reviewer[bot] with no Claude attribution.


Non-blocking A — slice-dag.md now half-documents validate_checks

The same commit that added the falsy-drop clause to the three schema sites also added drop semantics to slice-dag.md:575-579 — but only the name/command half:

validate_checks drops any entry missing name/command with no warning

A reader who lands on the architecture doc now learns that one silent-drop trap exists and not the other, which is a slightly worse position than knowing about neither: the sentence reads as a complete statement of what validate_checks discards. Half a clause closes it — "…and drops a falsy fix value from an otherwise-valid entry" — or drop the validator sentence to the shape correction alone and let the schema sites own drop semantics. Either is fine; the current split is the only shape I'd avoid.

Non-blocking B — log is named as the exception, but it is not the only one

All four sites now say:

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 …

Two other precondition failures also never reach that line, for the same structural reason (they return before _autofix_ready is called at :1505):

  • off mode returns at :1243-1249, before _gate_checks and before the runner Job exists.
  • The all-infra-red short-circuit returns at :1494-1500, which the delta's own neighbouring sentence describes — but it is framed there as a fail-open, not as one of the unlogged refusals.

Neither misleads a reader who finishes the paragraph (off is covered by the "on is the default mode" paragraph immediately below, and the infra case has its own sentence), so this is precision rather than correctness. But "a log-mode refusal is not one of them" implies log is the sole exception, and it isn't. Suggested: "…a refusal that returns before the autofix decision — off mode, or the all-infra fail-open — is not one of them, and neither is a log-mode refusal (that line reads …)".

Non-blocking C — a second silent no-gate path the new "all drop out" sentence doesn't cover

slice-dag.md now correctly warns that a schema-invalid checks: block yields no green gate at all. There is a second route to the identical outcome that the doc mentions two paragraphs later without connecting it: _gate_checks:706-708 filters EGG_SLICE_GREEN_GATE_SKIP_CHECKS (default security) after get_repo_checks returns:

skip = {name.strip().lower() for name in raw_skip.split(",") if name.strip()}
return [c for c in configured if c["name"].strip().lower() not in skip]

A repo whose only configured check is named security has a perfectly valid checks: block, passes validate_checks intact, and still hits if not checks: at :1271 — same "no configured checks" log line, same return None, no gate. Same failure class as the one the delta just documented, different cause, and arguably more surprising because the config is correct. Worth appending to the sentence you just added: "…as does a repo whose every check name lands in EGG_SLICE_GREEN_GATE_SKIP_CHECKS."


Out of scope for this PR — shared/egg_config/validators.py:208 contains Python-2 except syntax

Not in this diff (the PR touches six files; validators.py is byte-identical to origin/main), so this is not a reason to withhold approval. Flagging it because it is directly under the module this PR's prose now names, and because if it is real it makes the documented feature unrunnable.

git cat-file -p origin/main:shared/egg_config/validators.py line 208 is, at the byte level:

    except ValueError, TypeError:

That is Python-2 syntax; CPython 3 rejects it with multiple exception types must be parenthesized. If it parses as written on a trusted interpreter, importing shared/egg_config/validators.py raises SyntaxError — and critically, SyntaxError is not an ImportError, so the try: from egg_config.validators import validate_checks / except ImportError: guards at config/repo_config.py:359-360 and orchestrator/routes/pipelines/__init__.py:463-464 would not catch it. The exception would propagate out of both module-level imports rather than falling through to the inline fallback copies, taking down config.repo_config and the pipelines blueprint at import time.

I could not confirm the runtime consequence from this sandbox: the local python3 (3.14.6) reports ast.parse("try:\n x=1\nexcept ValueError, TypeError:\n pass\n") as accepted, which is not standard CPython behaviour, so I do not trust it to adjudicate. The byte content of the blob on origin/main is the part I can state with certainty. Please verify on a trusted interpreter — python3 -c "import shared.egg_config.validators" from a clean checkout — and if it reproduces, file it separately against main; it is unrelated to this docs branch and shouldn't be folded in here.


Approving. The round-5 blocker was fixed with the actual schema shape rather than a reworded evasion, and both non-blocking corrections describe the control flow accurately — I checked each against the branch/return structure at :1505-1570 rather than against the prose. The three notes above are precision and completeness; none of them makes a current sentence false.

Per the workflow contract I did not run the test suite. The checks I ran were read-only: grep/sed/awk over the tree, git cat-file/git show/git diff against origin/main, and one yaml.safe_load of the example file. No test in the tree asserts on any of the changed doc or docstring text.

— Authored by egg

@james-in-a-box

Copy link
Copy Markdown
Contributor Author

egg review completed. View run logs

21 previous review(s) hidden.

@james-in-a-box

Copy link
Copy Markdown
Contributor Author

Automated feedback loop has reached the maximum of 5 rounds. Human review is needed to make further progress on this PR.

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.

1 participant