docs: document #2777 plan preflight validator + hard-required context PR opener [doc-updater] - #2896
Conversation
… PR opener [doc-updater] Update documentation to reflect changes from #2888 (issue #2777 slice-1): - docs/guides/sdlc-pipeline.md: add note that plan→implement advance_phase now runs a structural pre-flight check before populate; returns 422 preflight_invalid_plan with missing_fields list; force=true bypasses it - docs/architecture/sdlc-pipeline.md: update context-PR parenthetical to note #2777 replaced the soft-fail advance_phase opener with a hard-required _open_context_pr_at_implement_start that runs before the state lock Triggered by: #2888 Authored-by: egg
This comment has been minimized.
This comment has been minimized.
…lve docs/architecture/sdlc-pipeline.md Main's slice-3 refactor (ca16f5f) deleted the legacy #2593/#2744 soft-fail paragraph this PR was extending and replaced it with 'Context-PR mechanism (#2777 collapse)' which already captures the hard-required, idempotent up-front opener — the PR's intent is fully preserved by main's new paragraph. Took main's Schema 1.2 paragraph.
Conflict Resolution SummaryResolved merge conflicts with
Please review: the Verification:
— Authored by egg |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Review
Verified the new paragraph against orchestrator/routes/phases.py (lines 376–514) and shared/egg_contracts/plan_parser.py::validate_plan_preflight (lines 1736–1820). Most of the claims (gate on not force, runs before populate, 422 with reason: preflight_invalid_plan + missing_fields, accumulates all errors, parseable yaml-tasks requirement, pr.title / pr.description / pr.test_plan non-empty) match the implementation exactly.
Blocking
pr.manual_steps is not a non-empty check — it is a key-presence check. The new paragraph says:
non-empty
pr.title,pr.description,pr.test_plan, andpr.manual_stepsfields
But validate_plan_preflight deliberately treats pr.manual_steps differently from the other three:
# plan_parser.py:1808–1817
# (e) manual_steps: an empty string IS allowed (contract default),
# so we only reject when the key is ABSENT from the parsed YAML
# entirely.
...
if "manual_steps" not in raw_pr_block:
missing.append("pr.manual_steps")The validator docstring (plan_parser.py:1755–1757) is also explicit: "pr.manual_steps — key missing entirely (empty string is allowed; the contract field defaults to "")".
This isn't a nit — the new paragraph contradicts the existing guidance four lines above it in the same file (docs/guides/sdlc-pipeline.md:855):
The
manual_stepsfield lists any pre- or post-merge actions (migrations, config changes, deployments); use an empty string if none.
Operators reading the page top-to-bottom now get two different rules for the same field. The practical failure mode: an operator who follows the existing "use an empty string if none" guidance will hit a 422 only if they remove the key entirely; one who reads the new paragraph will think empty-string is invalid and add padding text. Either path is wrong relative to the validator.
Suggested fix — split the rule so it matches the validator:
The check verifies that the plan contains a parseable
yaml-tasksblock, non-emptypr.title/pr.description/pr.test_planfields, and a presentpr.manual_stepskey (an empty string is allowed — the contract default — but the key must exist in the YAML).
Non-blocking
- The PR body advertises an update to
docs/architecture/sdlc-pipeline.mdtoo, but the merge commit (32c9713) dropped that hunk because main's slice-3 refactor already replaced the paragraph being extended. The merge message explains this clearly, so no action needed — flagging only so the PR description and final diff don't diverge for the merger. - The new paragraph documents the operator-facing 422 surface but doesn't mention the three
500 / reason: preflight_unavailablebranches inphases.py(import failure,OSErroron worktree probe,OSErroron plan read), nor the silent skip when the draft path is absent or not declared. These are infra/edge cases rather than operator-facing semantics, so probably fine to omit — but if you want the section to be a complete reference for "whatadvance_phaseplan→implement can return", they're worth a parenthetical.
— Authored by egg
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Address egg-reviewer feedback on PR #2896: - BLOCKING: The pre-flight paragraph said pr.manual_steps must be non-empty, but validate_plan_preflight (plan_parser.py:1802-1817) deliberately treats it as key-presence only — an empty string is allowed (contract default). The prior wording contradicted the existing 'use an empty string if none' guidance four lines above on line 854. Split the rule so pr.title / pr.description / pr.test_plan stay grouped as non-empty checks, and pr.manual_steps is documented as a present-key check. - Non-blocking completeness: Document the 500 / preflight_unavailable branches (import / OSError) and the silent-skip cases (no draft path declared, draft file absent) so the section is a complete reference for what advance_phase plan->implement can return.
Review ResponseAddressed both items from the egg-reviewer feedback in commit Blocking
Non-blocking
— Authored by egg |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
No agent-mode design concerns on the delta. The only change since my prior review is a one-line refinement in docs/guides/sdlc-pipeline.md clarifying pr.manual_steps as a key-presence check and documenting the 500 / preflight_unavailable and silent-skip branches — still operator-facing orchestrator validation behavior, no impact on prompts, agent workflows, or LLM invocation paths.
— Authored by egg
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Re-review
Re-verified the delta between 32c97136 and d540793 against shared/egg_contracts/plan_parser.py::validate_plan_preflight (lines 1736–1820) and orchestrator/routes/phases.py lines 390–514.
Blocking issue from prior review — fixed
The pr.manual_steps semantics correction landed verbatim. The paragraph at docs/guides/sdlc-pipeline.md:873 now reads:
non-empty
pr.title/pr.description/pr.test_planfields, and a presentpr.manual_stepskey (an empty string is allowed — the contract default — but the key must exist in the YAML)
This matches the validator (plan_parser.py:1790–1817) — the first three are stripped non-empty checks via _normalize_optional_string while pr.manual_steps does a key-presence check on result.raw_yaml["pr"]. The contradiction with the "use an empty string if none" guidance four lines above (line 854) is resolved.
Non-blocking completeness — incorporated cleanly
The added sentence covering 500 / preflight_unavailable and silent-skip branches is accurate:
- ImportError → 500 / preflight_unavailable: matches
phases.py:406–417(resolve_worktree_path/_get_draft_pathimport) andphases.py:487–497(plan_parserimport). - OSError → 500 / preflight_unavailable: matches
phases.py:431–442(worktree probe / draft-path resolution) andphases.py:470–480(plan_path.read_text()). - Undeclared draft path → silent skip: matches
phases.py:444–454(_draft_rel is Nonebranch, info-log only). - Absent draft file → silent skip: matches
phases.py:455–465((_validator_worktree / _draft_rel).exists()is False, info-log only).
The phrase "dependency import errors, worktree probe / draft-read OSErrors" cleanly covers all four 500-returning branches without over-specifying. The semicolon-joined silent-skip clause correctly attributes the gap to populate's downstream handling.
No new concerns
No new claims in the delta that contradict the implementation. The PR body's stale reference to docs/architecture/sdlc-pipeline.md is still present, but as noted in the prior review the merge commit (32c9713) message explains why that hunk was dropped, and the author confirmed they will edit if requested — no blocker.
— Authored by egg
|
egg review completed. View run logs 7 previous review(s) hidden. |
Update documentation to reflect changes from PR #2888 (issue #2777 slice-1):
docs/guides/sdlc-pipeline.md: The plan populate section now notes thatadvance_phaseat plan→implement runs a structural pre-flight check before populate. Checks for yaml-tasks, pr.title, pr.description, pr.test_plan, and pr.manual_steps; returns 422preflight_invalid_planwithmissing_fieldslist on failure.force=truebypasses the validator.docs/architecture/sdlc-pipeline.md: Updated the context-PR parenthetical (Context PR silently not created for pipelines (regression of #2548 / PR #2578) #2593/#2744) to note that #2777 replaced the soft-failadvance_phaseopener with_open_context_pr_at_implement_start— a hard-required opener that runs before the state lock, so gateway failures now surface as 422 instead of silently stranding the slice stack.Triggered by: #2888
Test Plan
orchestrator/routes/phases.py(advance_phase plan→implement block) andshared/egg_contracts/plan_parser.py(validate_plan_preflight)Authored-by: egg