diff --git a/.egg-state/agent-outputs/coder/brc-memory-issue-3393.md b/.egg-state/agent-outputs/coder/brc-memory-issue-3393.md
index 5c6c6300a7..1d7b2ab2ea 100644
--- a/.egg-state/agent-outputs/coder/brc-memory-issue-3393.md
+++ b/.egg-state/agent-outputs/coder/brc-memory-issue-3393.md
@@ -1,5 +1,89 @@
# Coder BRC memory — issue-3393 (multi-repo pipelines)
+## Slice-6 — per-repo test-gate + reviewer-diff scoping + per-repo conventions
+
+**Branch:** `egg/issue-3393-slice-6-coder/work`
+**Task:** task-6-1 — implemented. **File:** `orchestrator/routes/pipelines.py` only.
+
+### Change model (what landed)
+All wiring is in `_run_concurrent_phase` (the per-slice team spawn driver,
+receives `slice_id`, `pipeline`, `worktree_repo_path`, `repos`,
+`repo_volumes`). The implement-phase test gate and reviewer diff both run
+INSIDE the agent containers (tester's configured checks; reviewers'
+`git diff origin/...HEAD`) — there is NO orchestrator-side
+`make test`/`make lint` subprocess in pipelines.py (grep confirmed: only
+prompt/doc strings). So scoping = threading the slice's repo / worktree /
+base-branch into the prompt builder + spawn, not moving a subprocess.
+
+- **New helper `_resolve_slice_worktree_path(pipeline, slice_repo, fallback)`**
+ (beside `_resolve_pipeline_worktree_path`): returns
+ `WORKTREE_BASE_DIR/pipeline.id/` if it exists, else
+ fallback (pipeline-primary worktree). Mirrors the existing per-repo
+ worktree layout (slice-3 owner/repo keying).
+- **Slice-repo scoping block** in `_run_concurrent_phase`, GATED on
+ `slice_id and len(pipeline.repos) > 1` (so N=1 skips it entirely — no
+ extra contract read, byte-identical): loads the contract, finds the
+ slice, `resolved = resolve_slice_repo(slice_obj, pipeline)`. Only when
+ `resolved != pipeline.repo` does it diverge — sets `slice_repo`,
+ `slice_repo_path` (via the new helper), `slice_base_branch` (from the
+ matching `RepoSpec.base_branch`), and `slice_repos = [resolved, *others]`.
+ Contract-load failure soft-degrades to the primary (logged, non-blocking).
+- **Base-branch resolution** now prefers `slice_base_branch or
+ pipeline.base_branch`, then auto-detects in `slice_repo_path`.
+- **`_build_agent_prompt`** now gets `repo=slice_repo`,
+ `repo_path=str(slice_repo_path)` (was `pipeline.repo` /
+ `worktree_repo_path`). This is the lever for AC "check/lint from the
+ slice repo's conventions": `get_repo_checks(repo)` (tester prompt
+ ~15963) + file-boundary `get_agent_pattern_for_repo(repo)` both key off
+ `repo`; the reviewer diff base flows via `base_branch`.
+- **`create_concurrent_spawn_fn`** now gets `repos=slice_repos`. Verified
+ `_spawn.py:460` derives `primary_repo = next(iter(repos))` →
+ `repo_path`/`EGG_REPO_PATH` (agent cwd) from the first repo, so
+ slice-first ordering sets cwd to the slice's repo worktree. `repo_volumes`
+ (full owner/repo map, slice-3) is passed unchanged.
+
+### Converged with tester task-6-2 (commit 3db72777e)
+Tester pinned a required coder-owned accessor via a task-6-1 gap:
+`routes.pipelines._resolve_slice_gate_repo(slice, pipeline) -> str | None`
+== `resolve_slice_repo(slice, pipeline)` (their `TestSliceGateRepoAccessor`
+skips until it exists, then activates). Added it as the single gate-repo
+source of truth and use it inside the scoping block. Their
+`TestPerRepoWorktreeSelection` validates the existing
+`routes.resolve_worktree_path` selects the slice's per-repo subdir — my
+`_resolve_slice_worktree_path` is the functionally-equivalent
+WORKTREE_BASE_DIR/pid/ lookup used at the call site.
+
+### N=1 byte-equivalence
+`len(pipeline.repos) <= 1` ⇒ scoping block skipped ⇒ `slice_repo ==
+pipeline.repo`, `slice_repo_path == worktree_repo_path`, `slice_repos ==
+repos`, `slice_base_branch == None` ⇒ base resolution + prompt + spawn
+identical to pre-change. The legacy slice-1 validator synthesises a
+one-element `repos` for any legacy pipeline, so the guard holds.
+
+### Documented boundary (preempt reviewer questions)
+- **cwd via repos-reorder, not a new spawner param:** task-6-1 files=only
+ pipelines.py, and `_spawn.py` already derives cwd from `repos[0]`;
+ reordering is the minimal, in-scope lever. For a secondary-repo slice
+ `EGG_PIPELINE_REPO` becomes the slice repo — correct under slice↔repo 1:1
+ (authorship + naming should follow the slice's repo). `EGG_PIPELINE_REPOS`
+ (full map) still exported from `repo_volumes`, unchanged.
+- **`_build_slice_diff_summary` (PR-body "What's in this PR") NOT rescoped:**
+ it's PR rendering (best-effort, soft-fails), not the reviewer's audit
+ diff or the test gate, and its parent/integration branches are already
+ per-slice. Left on the pipeline worktree; multi-repo PR-body diff is
+ gated on task-7-1 (secondary worktrees) anyway.
+- **Runtime multi-repo end-to-end depends on task-7-1** (secondary-repo
+ worktree/branch materialisation → populates `repo_volumes` with the
+ secondary + creates the on-disk worktree the helper resolves). This slice
+ is structurally complete + forward-compatible: once task-7-1 lands, a
+ secondary slice's team spawns in its own repo worktree with NO further
+ change here.
+
+### Validation (no venv — deps cert-blocked, same as slices 2/3/4)
+- `py_compile` clean; `ruff check` "All checks passed!" on pipelines.py.
+- Full pytest deferred to the tester (task-6-2/6-3): env can't `pip install`
+ (charset-normalizer fetch fails on UnknownIssuer cert).
+
## Slice-4 — slice-PR routing to slice.repo + lazy per-repo context PR
**Branch:** `egg/issue-3393-slice-4-coder/work` (base = slice-3 tip aa567fa67)
diff --git a/.egg-state/brc-history/3393-implement-slice-6.json b/.egg-state/brc-history/3393-implement-slice-6.json
new file mode 100644
index 0000000000..3956ced32d
--- /dev/null
+++ b/.egg-state/brc-history/3393-implement-slice-6.json
@@ -0,0 +1,1475 @@
+[
+ {
+ "id": "c175a84e-8064-4a",
+ "pipeline_id": "issue-3393",
+ "from_role": "coder",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "one-shot event handler action=propose (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:33:13.315982+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "ea4810bc-83e4-4a",
+ "pipeline_id": "issue-3393",
+ "from_role": "tester",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "one-shot event handler action=propose (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:33:13.327523+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "27697764-8377-4d",
+ "pipeline_id": "issue-3393",
+ "from_role": "documenter",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "one-shot event handler action=propose (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:33:14.340676+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "9bc04f98-098f-43",
+ "pipeline_id": "issue-3393",
+ "from_role": "coder",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "in-tool-loop liveness (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:33:19.846799+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "cee975b0-ba6f-41",
+ "pipeline_id": "issue-3393",
+ "from_role": "tester",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "in-tool-loop liveness (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:33:21.810990+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "49f90c8d-2be5-49",
+ "pipeline_id": "issue-3393",
+ "from_role": "documenter",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "in-tool-loop liveness (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:33:22.417679+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "bfacfcf8-5e10-40",
+ "pipeline_id": "issue-3393",
+ "from_role": "documenter",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "in-tool-loop liveness (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:35:30.185494+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "51369776-e23f-48",
+ "pipeline_id": "issue-3393",
+ "from_role": "tester",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "in-tool-loop liveness (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:35:35.964271+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "fc842ce0-e5d1-4f",
+ "pipeline_id": "issue-3393",
+ "from_role": "coder",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "in-tool-loop liveness (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:35:39.101490+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "3d2a4331-115a-4d",
+ "pipeline_id": "issue-3393",
+ "from_role": "documenter",
+ "to_role": "all",
+ "message_type": "CONSENSUS_PROPOSE",
+ "subject": "Proposal from documenter",
+ "body": "Documented the shipped multi-repo pipeline model across the two current-state docs (task-6-3). docs/guides/sdlc-pipeline.md gains a \"Multi-Repo Pipelines\" section: list-shaped submission via submit_task / POST /api/v1/pipelines with per-repo base_branch (RepoSpec), the primary-repo concept (Pipeline.primary_repo, resolve_slice_repo), and the uniform-visibility + uniform-auth submission-time rules (get_repo_visibility fail-closed, get_auth_mode; same-name-different-owner allowed). docs/architecture/slice-dag.md gains a Slice.repo row in the New Slice fields table plus a \"Per-slice repo (multi-repo pipelines)\" section covering: the 1:1 slice\u2194repo rule + runtime resolver + schemaVersion 1.4 additive stamp; owner/repo-keyed worktree map (EGG_PIPELINE_REPOS); lazy per-repo work branch + context PR; per-slice PR routing with sibling cross-references; cross-repo ordering via slice dependencies (B\u2192A cross-repo iff resolve_slice_repo differs); the two-tier cq-1 merge-sequencing hold (Tier A automated draft\u2192ready on mergedAt not head-SHA, mark_pr_ready, CLOSED-unmerged + bounded-timeout HITL terminals; Tier B beyond-merge-state HITL opt-in); and per-repo test-gate/reviewer-diff/convention scoping. Current-state snapshot, no slice-number or pipeline-process narration; bidirectional cross-links between the two docs.",
+ "metadata": {
+ "payload": {
+ "summary": "Documented the shipped multi-repo pipeline model across the two current-state docs (task-6-3). docs/guides/sdlc-pipeline.md gains a \"Multi-Repo Pipelines\" section: list-shaped submission via submit_task / POST /api/v1/pipelines with per-repo base_branch (RepoSpec), the primary-repo concept (Pipeline.primary_repo, resolve_slice_repo), and the uniform-visibility + uniform-auth submission-time rules (get_repo_visibility fail-closed, get_auth_mode; same-name-different-owner allowed). docs/architecture/slice-dag.md gains a Slice.repo row in the New Slice fields table plus a \"Per-slice repo (multi-repo pipelines)\" section covering: the 1:1 slice\u2194repo rule + runtime resolver + schemaVersion 1.4 additive stamp; owner/repo-keyed worktree map (EGG_PIPELINE_REPOS); lazy per-repo work branch + context PR; per-slice PR routing with sibling cross-references; cross-repo ordering via slice dependencies (B\u2192A cross-repo iff resolve_slice_repo differs); the two-tier cq-1 merge-sequencing hold (Tier A automated draft\u2192ready on mergedAt not head-SHA, mark_pr_ready, CLOSED-unmerged + bounded-timeout HITL terminals; Tier B beyond-merge-state HITL opt-in); and per-repo test-gate/reviewer-diff/convention scoping. Current-state snapshot, no slice-number or pipeline-process narration; bidirectional cross-links between the two docs.",
+ "attestation": {},
+ "artifacts": [
+ "docs/guides/sdlc-pipeline.md",
+ "docs/architecture/slice-dag.md"
+ ],
+ "risk_considered": "Docs-only change (documenter file boundary: docs/ + **/*.md). No code/behavior change. Verified every documented symbol against the landed implementation (Slice.repo + schemaVersion 1.4 in shared/egg_contracts/models.py; RepoSpec/Pipeline.repos/primary_repo/resolve_slice_repo in orchestrator/models.py; EGG_PIPELINE_REPOS in kubernetes_spawner/_spawn.py; mark_pr_ready in gateway_client/_pr.py; cross_repo_merge_gate.py two-tier hold). Anchors verified consistent across both docs.",
+ "commit_sha": "6afc6dfb75b3581d056cf128068e3dbf1fe19846",
+ "files_changed": [
+ "docs/guides/sdlc-pipeline.md",
+ "docs/architecture/slice-dag.md"
+ ],
+ "tests_run": [],
+ "tasks_satisfied": [
+ "task-6-3"
+ ],
+ "no_changes_needed": false,
+ "no_changes_reason": ""
+ },
+ "version": 1,
+ "commit_sha": "6afc6dfb75b3581d056cf128068e3dbf1fe19846",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:36:40.871413+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "c609056b-8ef4-43",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_code",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "one-shot event handler action=ack (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:36:49.197953+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "737acca0-73ac-49",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_contract",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "one-shot event handler action=ack (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:36:49.668745+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "48575b44-0ba9-4c",
+ "pipeline_id": "issue-3393",
+ "from_role": "documenter",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "context-measure occ=135475 util=0.14 cache_hit=0.99 decision=no_warm_session",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:36:55.190603+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "bbd8e37f-62ed-4f",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_code",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "in-tool-loop liveness (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:36:58.205757+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "ec23e71f-8c65-40",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_contract",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "in-tool-loop liveness (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:36:58.722634+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "46b6eeec-6ea1-4d",
+ "pipeline_id": "issue-3393",
+ "from_role": "tester",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "in-tool-loop liveness (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:37:42.693803+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "da7dfca4-bbff-48",
+ "pipeline_id": "issue-3393",
+ "from_role": "coder",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "in-tool-loop liveness (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:37:49.128733+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "a5fe14a2-d55c-4e",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_code",
+ "to_role": "documenter",
+ "message_type": "CONSENSUS_ACK",
+ "subject": "ACK from reviewer_code for documenter",
+ "body": "Verified every concrete anchor in the docs diff against live code \u2014 all accurate and non-misleading. resolve_slice_repo (models.py:1484 returns slice.repo or pipeline.primary_repo), Pipeline.repos:list[RepoSpec] + primary_repo property, Slice.repo optional field with schema 1.4 additive _migrate_schema_version_to_1_4, cross_repo_merge_gate.py (draft-PR Tier-A hold; merge detection via mergedAt/merged NOT head-SHA per _is_merged; CLOSED-not-merged + attempt-bound HITL terminals), mark_pr_ready gateway verb, create_slice_pr repo-parameterized, gateway/repo_visibility.py:get_repo_visibility, and EGG_PIPELINE_REPOS owner/repo-keyed worktree map with naming env falling back to primary. Cross-file doc anchors resolve. No blocking issues.",
+ "metadata": {
+ "payload": {
+ "artifact_references": [
+ "docs/guides/sdlc-pipeline.md",
+ "docs/architecture/slice-dag.md"
+ ],
+ "reason": "Verified every concrete anchor in the docs diff against live code \u2014 all accurate and non-misleading. resolve_slice_repo (models.py:1484 returns slice.repo or pipeline.primary_repo), Pipeline.repos:list[RepoSpec] + primary_repo property, Slice.repo optional field with schema 1.4 additive _migrate_schema_version_to_1_4, cross_repo_merge_gate.py (draft-PR Tier-A hold; merge detection via mergedAt/merged NOT head-SHA per _is_merged; CLOSED-not-merged + attempt-bound HITL terminals), mark_pr_ready gateway verb, create_slice_pr repo-parameterized, gateway/repo_visibility.py:get_repo_visibility, and EGG_PIPELINE_REPOS owner/repo-keyed worktree map with naming env falling back to primary. Cross-file doc anchors resolve. No blocking issues.",
+ "ack_version": 1
+ },
+ "version": 1,
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:37:57.646633+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "3f67c7c0-eed1-42",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_code",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "context-measure occ=66003 util=0.07 cache_hit=0.96 decision=no_warm_session",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:38:06.146246+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "38c2ced7-c301-4d",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_contract",
+ "to_role": "documenter",
+ "message_type": "CONSENSUS_ACK",
+ "subject": "ACK from reviewer_contract for documenter",
+ "body": "ACK task-6-3 (docs snapshot of shipped multi-repo model). Both acceptance criteria met: (1) sdlc-pipeline.md documents list-shaped submission (MCP submit_task + POST /api/v1/pipelines, per-repo base_branch), the uniform-visibility + uniform-auth rules, and the primary-repo concept; (2) slice-dag.md documents Slice.repo (1:1 rule), per-repo work-branch/context-PR, cross-repo ordering via slice dependencies, the two-tier merge-sequencing hold, and per-repo gate/diff/convention scoping \u2014 as a current-state snapshot. Verified every code claim resolves to shipped symbols (resolve_slice_repo, Pipeline.primary_repo, RepoSpec/Pipeline.repos, _migrate_schema_version_to_1_4\u2192schemaVersion 1.4, EGG_PIPELINE_REPOS + back-compat EGG_PIPELINE_REPO/EGG_REPO_PATH, cross_repo_merge_gate.py/mark_pr_ready, create_slice_pr, _assert_repo_set_uniform + get_repo_visibility/get_auth_mode). New sections contain no prohibited slice-N/task-N narration (only standard #3393 issue cites). Snapshot is faithful.",
+ "metadata": {
+ "payload": {
+ "artifact_references": [
+ "docs/guides/sdlc-pipeline.md",
+ "docs/architecture/slice-dag.md"
+ ],
+ "reason": "ACK task-6-3 (docs snapshot of shipped multi-repo model). Both acceptance criteria met: (1) sdlc-pipeline.md documents list-shaped submission (MCP submit_task + POST /api/v1/pipelines, per-repo base_branch), the uniform-visibility + uniform-auth rules, and the primary-repo concept; (2) slice-dag.md documents Slice.repo (1:1 rule), per-repo work-branch/context-PR, cross-repo ordering via slice dependencies, the two-tier merge-sequencing hold, and per-repo gate/diff/convention scoping \u2014 as a current-state snapshot. Verified every code claim resolves to shipped symbols (resolve_slice_repo, Pipeline.primary_repo, RepoSpec/Pipeline.repos, _migrate_schema_version_to_1_4\u2192schemaVersion 1.4, EGG_PIPELINE_REPOS + back-compat EGG_PIPELINE_REPO/EGG_REPO_PATH, cross_repo_merge_gate.py/mark_pr_ready, create_slice_pr, _assert_repo_set_uniform + get_repo_visibility/get_auth_mode). New sections contain no prohibited slice-N/task-N narration (only standard #3393 issue cites). Snapshot is faithful.",
+ "ack_version": 1,
+ "attestation": {
+ "tasks_verified": [
+ "task-6-3"
+ ],
+ "snapshot_accuracy": "verified against shipped code (resolve_slice_repo, primary_repo, RepoSpec, schemaVersion 1.4 additive after-stamp, EGG_PIPELINE_REPOS + back-compat env, cross_repo_merge_gate/mark_pr_ready, create_slice_pr, uniform visibility+auth validation)",
+ "no_slice_task_narration": true
+ }
+ },
+ "version": 1,
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:38:47.125620+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "a5679d28-302d-44",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_contract",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "context-measure occ=72801 util=0.07 cache_hit=0.99 decision=no_warm_session",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:38:56.376867+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "cc8e41bf-aa24-42",
+ "pipeline_id": "issue-3393",
+ "from_role": "tester",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "in-tool-loop liveness (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:39:55.031636+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "a92fc394-1da9-4e",
+ "pipeline_id": "issue-3393",
+ "from_role": "coder",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "in-tool-loop liveness (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:39:56.200189+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "d2371efd-ba54-4c",
+ "pipeline_id": "issue-3393",
+ "from_role": "tester",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "in-tool-loop liveness (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:42:42.076235+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "ba41bf1b-d32a-42",
+ "pipeline_id": "issue-3393",
+ "from_role": "coder",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "in-tool-loop liveness (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:42:42.059931+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "82c40eaf-75df-41",
+ "pipeline_id": "issue-3393",
+ "from_role": "tester",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "in-tool-loop liveness (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:55:37.174061+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "a33b3eb3-3ae9-4c",
+ "pipeline_id": "issue-3393",
+ "from_role": "coder",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "in-tool-loop liveness (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:55:44.090828+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "d4346bbb-df3a-4e",
+ "pipeline_id": "issue-3393",
+ "from_role": "coder",
+ "to_role": "all",
+ "message_type": "CONSENSUS_PROPOSE",
+ "subject": "Proposal from coder",
+ "body": "Slice-6 task-6-1: scope the implement-phase test gate, reviewer diff, per-repo check/lint commands, and agent cwd to the slice's repo (slice \u2194 repo 1:1). All wiring lands in `_run_concurrent_phase` (per-slice team spawn driver) in orchestrator/routes/pipelines.py. There is NO orchestrator-side `make test`/`make lint` subprocess (grep-confirmed) \u2014 the test gate is the tester agent's configured checks and the reviewer diff is the reviewers' in-worktree `git diff origin/...HEAD`; both are scoped by threading the slice's repo/worktree/base into the prompt builder + spawn. Changes: (1) `_resolve_slice_gate_repo(slice, pipeline)` \u2014 the single gate-repo source of truth (== resolve_slice_repo), matching the tester's pinned task-6-2 TestSliceGateRepoAccessor; (2) `_resolve_slice_worktree_path` helper (WORKTREE_BASE_DIR/pipeline.id/, mirrors slice-3 owner/repo layout); (3) a scoping block gated on `slice_id and len(pipeline.repos) > 1` that loads the contract, resolves the slice's repo via the accessor, and \u2014 only when it differs from the primary \u2014 sets slice_repo, slice_repo_path, per-repo base (from the matching RepoSpec), and reorders `repos` slice-first; (4) `_build_agent_prompt` gets repo=slice_repo + repo_path=slice_repo_path (drives get_repo_checks for per-repo commands, file-boundary patterns, reviewer diff base); (5) `create_concurrent_spawn_fn` gets repos=slice_repos \u2014 `_spawn.py:460` derives cwd/EGG_REPO_PATH from next(iter(repos)), so slice-first ordering sets the agent cwd to the slice's repo worktree. N=1 (egg) is byte-identical: block skipped for \u22641-repo pipelines, no extra contract read. Contract-load failure soft-degrades to primary. Merged tester task-6-2 tests (3db72777e) + documenter docs (6afc6dfb7).",
+ "metadata": {
+ "payload": {
+ "summary": "Slice-6 task-6-1: scope the implement-phase test gate, reviewer diff, per-repo check/lint commands, and agent cwd to the slice's repo (slice \u2194 repo 1:1). All wiring lands in `_run_concurrent_phase` (per-slice team spawn driver) in orchestrator/routes/pipelines.py. There is NO orchestrator-side `make test`/`make lint` subprocess (grep-confirmed) \u2014 the test gate is the tester agent's configured checks and the reviewer diff is the reviewers' in-worktree `git diff origin/...HEAD`; both are scoped by threading the slice's repo/worktree/base into the prompt builder + spawn. Changes: (1) `_resolve_slice_gate_repo(slice, pipeline)` \u2014 the single gate-repo source of truth (== resolve_slice_repo), matching the tester's pinned task-6-2 TestSliceGateRepoAccessor; (2) `_resolve_slice_worktree_path` helper (WORKTREE_BASE_DIR/pipeline.id/, mirrors slice-3 owner/repo layout); (3) a scoping block gated on `slice_id and len(pipeline.repos) > 1` that loads the contract, resolves the slice's repo via the accessor, and \u2014 only when it differs from the primary \u2014 sets slice_repo, slice_repo_path, per-repo base (from the matching RepoSpec), and reorders `repos` slice-first; (4) `_build_agent_prompt` gets repo=slice_repo + repo_path=slice_repo_path (drives get_repo_checks for per-repo commands, file-boundary patterns, reviewer diff base); (5) `create_concurrent_spawn_fn` gets repos=slice_repos \u2014 `_spawn.py:460` derives cwd/EGG_REPO_PATH from next(iter(repos)), so slice-first ordering sets the agent cwd to the slice's repo worktree. N=1 (egg) is byte-identical: block skipped for \u22641-repo pipelines, no extra contract read. Contract-load failure soft-degrades to primary. Merged tester task-6-2 tests (3db72777e) + documenter docs (6afc6dfb7).",
+ "attestation": {},
+ "artifacts": [
+ "orchestrator/routes/pipelines.py"
+ ],
+ "risk_considered": "N=1 byte-equivalence guaranteed by the `len(pipeline.repos) > 1` guard (the only runnable case today; secondary-repo worktree materialisation is task-7-1, still pending). Reordering `repos` makes the slice's repo primary for a secondary slice, so EGG_PIPELINE_REPO becomes the slice repo \u2014 correct under slice\u2194repo 1:1 (authorship/naming follow the slice's repo); EGG_PIPELINE_REPOS full owner/repo map is unchanged. Multi-repo end-to-end depends on task-7-1 populating repo_volumes with secondaries + creating the on-disk worktree; this slice is structurally complete and forward-compatible. Boundary: `_build_slice_diff_summary` (PR-body rendering, best-effort) intentionally not rescoped \u2014 it is not the reviewer audit diff or test gate.",
+ "commit_sha": "996d3cb1b",
+ "files_changed": [
+ "orchestrator/routes/pipelines.py",
+ ".egg-state/agent-outputs/coder/brc-memory-issue-3393.md"
+ ],
+ "tests_run": [],
+ "tasks_satisfied": [
+ "task-6-1"
+ ],
+ "no_changes_needed": false,
+ "no_changes_reason": ""
+ },
+ "version": 1,
+ "commit_sha": "996d3cb1b",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:58:01.130340+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "4812a307-d8c6-48",
+ "pipeline_id": "issue-3393",
+ "from_role": "coder",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "in-tool-loop liveness (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:58:01.745422+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "c6266913-42a3-4f",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_code",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "one-shot event handler action=ack (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:58:08.808989+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "71c91f42-7e99-4b",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_code_holistic",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "one-shot event handler action=ack (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:58:10.312000+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "22297563-de3a-4a",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_contract",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "one-shot event handler action=ack (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:58:11.660361+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "1651d55c-1d1a-4b",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_security",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "one-shot event handler action=ack (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:58:12.940456+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "a8aa2d18-bb29-4e",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_concurrency",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "one-shot event handler action=ack (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:58:14.000751+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "a4143ff4-be00-40",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_code",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "in-tool-loop liveness (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:58:16.037264+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "86729858-f670-4a",
+ "pipeline_id": "issue-3393",
+ "from_role": "coder",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "context-measure occ=177977 util=0.18 cache_hit=0.99 decision=no_warm_session",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:58:16.083194+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "561972ca-ac57-4a",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_code_holistic",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "in-tool-loop liveness (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:58:19.700439+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "5a23f9eb-e507-43",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_concurrency",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "in-tool-loop liveness (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:58:21.578690+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "8abd0cff-5370-43",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_security",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "in-tool-loop liveness (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:58:22.297960+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "36780167-109f-47",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_contract",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "in-tool-loop liveness (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:58:26.730007+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "d3701e0d-e715-41",
+ "pipeline_id": "issue-3393",
+ "from_role": "tester",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "in-tool-loop liveness (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:58:28.667615+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "32e78b93-1937-4a",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_security",
+ "to_role": "coder",
+ "message_type": "CONSENSUS_ACK",
+ "subject": "ACK from reviewer_security for coder",
+ "body": "Security-neutral. The slice-6 change scopes a slice's gate-repo/worktree/cwd to its own repo without introducing any credential, egress, or visibility surface: gateway/network mode (private-vs-public posture) is threaded through unchanged, repo_volumes stays owner/repo-keyed with per-repo token resolution untouched, and the slice_repos reordering only selects the agent cwd (EGG_REPO_PATH), not credential scope \u2014 so no cross-repo private\u2192public content flow. Worktree path resolution is bounded (split(\"/\")[-1] + exists() guard + safe fallback) and repo slugs come from validated submission RepoSpecs, not agent input, so no path-traversal vector. Contract-load failure degrades fail-safe to the pipeline primary. The _resolve_slice_gate_repo extraction is a behavior-preserving delegation to resolve_slice_repo. No security concerns.",
+ "metadata": {
+ "payload": {
+ "artifact_references": [
+ "orchestrator/routes/pipelines.py"
+ ],
+ "reason": "Security-neutral. The slice-6 change scopes a slice's gate-repo/worktree/cwd to its own repo without introducing any credential, egress, or visibility surface: gateway/network mode (private-vs-public posture) is threaded through unchanged, repo_volumes stays owner/repo-keyed with per-repo token resolution untouched, and the slice_repos reordering only selects the agent cwd (EGG_REPO_PATH), not credential scope \u2014 so no cross-repo private\u2192public content flow. Worktree path resolution is bounded (split(\"/\")[-1] + exists() guard + safe fallback) and repo slugs come from validated submission RepoSpecs, not agent input, so no path-traversal vector. Contract-load failure degrades fail-safe to the pipeline primary. The _resolve_slice_gate_repo extraction is a behavior-preserving delegation to resolve_slice_repo. No security concerns.",
+ "ack_version": 1
+ },
+ "version": 1,
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:59:03.085845+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "966ec29f-754c-4c",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_code_holistic",
+ "to_role": "coder",
+ "message_type": "CONSENSUS_ACK",
+ "subject": "ACK from reviewer_code_holistic for coder",
+ "body": "Slice-6 coder v1 ACK. The new routes-layer accessor _resolve_slice_gate_repo(slice, pipeline) is a clean single-source-of-truth wrapper over models.resolve_slice_repo, matching the tester's pinned TestSliceGateRepoAccessor interface exactly (secondary\u2192slice repo, repoless\u2192primary, N=1\u2192single repo; both tests pass at convergence). The inline resolve_slice_repo import was correctly removed from _run_concurrent_phase with no dangling reference \u2014 the only call site there now uses the accessor. Behavior is byte-identical for N=1 (gated on len(pipeline.repos) > 1) and pure delegation for multi-repo, so no new behavior beyond the already-reviewed slice-repo scoping. No correctness, architecture, or consistency concerns.",
+ "metadata": {
+ "payload": {
+ "artifact_references": [
+ "orchestrator/routes/pipelines.py",
+ "orchestrator/tests/test_pipelines.py"
+ ],
+ "reason": "Slice-6 coder v1 ACK. The new routes-layer accessor _resolve_slice_gate_repo(slice, pipeline) is a clean single-source-of-truth wrapper over models.resolve_slice_repo, matching the tester's pinned TestSliceGateRepoAccessor interface exactly (secondary\u2192slice repo, repoless\u2192primary, N=1\u2192single repo; both tests pass at convergence). The inline resolve_slice_repo import was correctly removed from _run_concurrent_phase with no dangling reference \u2014 the only call site there now uses the accessor. Behavior is byte-identical for N=1 (gated on len(pipeline.repos) > 1) and pure delegation for multi-repo, so no new behavior beyond the already-reviewed slice-repo scoping. No correctness, architecture, or consistency concerns.",
+ "ack_version": 1
+ },
+ "version": 1,
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:59:28.304371+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "d0dc3d70-cef6-46",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_security",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "context-measure occ=63224 util=0.06 cache_hit=0.97 decision=no_warm_session",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:59:32.229816+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "b4441b09-ce07-4c",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_code_holistic",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "context-measure occ=88982 util=0.09 cache_hit=0.97 decision=no_warm_session",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:59:35.536086+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "6eb0af16-b6ab-44",
+ "pipeline_id": "issue-3393",
+ "from_role": "tester",
+ "to_role": "all",
+ "message_type": "CONSENSUS_PROPOSE",
+ "subject": "Proposal from tester",
+ "body": "slice-6 (task-6-2): per-repo test-gate + reviewer-diff + check-command scoping tests \u2014 CONVERGED with coder. Added a section to orchestrator/tests/test_pipelines.py (commit 3db72777e, already integrated into the slice branch) following the slices-2/4/5 two-layer idiom. Always-green model-level rule tests pin that every implement-phase gate scopes to the slice's OWN repo (resolve_slice_repo), not the primary: TestPerRepoTestGateScoping (gate repo = slice repo; repoless\u2192primary; per-slice; egg-in-multi-repo baseline; N=1), TestPerRepoReviewerDiffBase (diff base = slice repo's RepoSpec.base_branch, differs from primary for cross-repo; repoless\u2192primary; N=1 unchanged), TestPerRepoCheckCommandResolution (check/lint commands keyed by slice repo; egg baseline; N=1). REAL test TestPerRepoWorktreeSelection exercises routes.resolve_worktree_path (monkeypatched _WORKTREE_BASE_DIR + tmp_path): the slice's repo path resolves to that repo's own per-repo worktree subdir, not the primary's. TestSliceGateRepoAccessor targets the coder seam routes.pipelines._resolve_slice_gate_repo \u2014 handed to the coder via task-6-1 gap-1 and now LANDED (coder commit 996d3cb1b), so this class has ACTIVATED and passes against the real accessor (asserts it == resolve_slice_repo for secondary/repoless/N=1). Post-convergence verification on the integrated branch tip: pytest orchestrator/tests/test_pipelines.py -k 'PerRepo or SliceGateRepo' = 23 passed, 0 skipped; full module 56 passed earlier; ruff check + format clean.",
+ "metadata": {
+ "payload": {
+ "summary": "slice-6 (task-6-2): per-repo test-gate + reviewer-diff + check-command scoping tests \u2014 CONVERGED with coder. Added a section to orchestrator/tests/test_pipelines.py (commit 3db72777e, already integrated into the slice branch) following the slices-2/4/5 two-layer idiom. Always-green model-level rule tests pin that every implement-phase gate scopes to the slice's OWN repo (resolve_slice_repo), not the primary: TestPerRepoTestGateScoping (gate repo = slice repo; repoless\u2192primary; per-slice; egg-in-multi-repo baseline; N=1), TestPerRepoReviewerDiffBase (diff base = slice repo's RepoSpec.base_branch, differs from primary for cross-repo; repoless\u2192primary; N=1 unchanged), TestPerRepoCheckCommandResolution (check/lint commands keyed by slice repo; egg baseline; N=1). REAL test TestPerRepoWorktreeSelection exercises routes.resolve_worktree_path (monkeypatched _WORKTREE_BASE_DIR + tmp_path): the slice's repo path resolves to that repo's own per-repo worktree subdir, not the primary's. TestSliceGateRepoAccessor targets the coder seam routes.pipelines._resolve_slice_gate_repo \u2014 handed to the coder via task-6-1 gap-1 and now LANDED (coder commit 996d3cb1b), so this class has ACTIVATED and passes against the real accessor (asserts it == resolve_slice_repo for secondary/repoless/N=1). Post-convergence verification on the integrated branch tip: pytest orchestrator/tests/test_pipelines.py -k 'PerRepo or SliceGateRepo' = 23 passed, 0 skipped; full module 56 passed earlier; ruff check + format clean.",
+ "attestation": {
+ "tests_execution_blocked": true,
+ "tests_execution_blocked_reason": "Full configured repo check suite (lint, security, test) cannot execute in this tester sandbox: PyPI egress is blocked (invalid peer certificate / UnknownIssuer via the squid proxy) so `make deps`/`sync-venv` cannot build .venv, and every `make lint`/`make test`/`make security` target depends on sync-venv-if-uv; security tooling (bandit, safety, trivy) and yamllint are also absent system-wide so the `security` check cannot run here at all. What I DID run directly with the system interpreter against the CONVERGED branch tip (996d3cb1b, coder + tester integrated) and which passed (reviewer detail, not a strict-mode checks_passed claim): `pytest orchestrator/tests/test_pipelines.py -k 'PerRepo or SliceGateRepo'` -> 23 passed / 0 skipped (the coder's _resolve_slice_gate_repo landed so TestSliceGateRepoAccessor activated and passes); full-module `pytest orchestrator/tests/test_pipelines.py` -> 56 passed / 4 skipped (pre-existing slice-2/4 guards) before convergence; `ruff check` + `ruff format --check` clean. The narrowed `make test` / full `make test-all` + `make security` remain the convergence-time gate in the CI/gateway environment."
+ },
+ "artifacts": [
+ "orchestrator/tests/test_pipelines.py",
+ "3db72777e"
+ ],
+ "risk_considered": "Converged with the coder: task-6-1 (gate scoping 4553fd012 + _resolve_slice_gate_repo accessor 996d3cb1b) is now integrated on the slice branch alongside my tests, so the previously skip-guarded TestSliceGateRepoAccessor is active and green \u2014 no dangling permanently-skipped test. Always-green rule tests pin the invariants against the slice-1 model API + shipped resolve_worktree_path resolver independent of coder internals. Commit already on origin (ancestor of tip) so push=false. Sandbox cannot build .venv (blocked egress) so make lint/security/test are unrunnable here \u2014 attested tests_execution_blocked with direct pytest+ruff results documented; make test-all/security is the convergence gate. Scope kept strictly to task-6-2 AC \u2014 not entangled with the slice-4/7 cq-4 secondary-repo worktree-materialization HITL.",
+ "commit_sha": "3db72777e",
+ "files_changed": [
+ "orchestrator/tests/test_pipelines.py"
+ ],
+ "tests_run": [],
+ "tasks_satisfied": [
+ "task-6-2"
+ ],
+ "no_changes_needed": false,
+ "no_changes_reason": ""
+ },
+ "version": 1,
+ "commit_sha": "3db72777e",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:59:37.082390+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "6e3afe2f-b386-44",
+ "pipeline_id": "issue-3393",
+ "from_role": "orchestrator",
+ "to_role": "documenter",
+ "message_type": "STATUS",
+ "subject": "Ready to confirm \u2014 all confirm preconditions satisfied",
+ "body": "Your proposal (version 1) is ready to confirm \u2014 all blocking reviews are clear and global confirm preconditions are met. Run `egg-orch consensus confirmed` to confirm.",
+ "metadata": {
+ "ready_to_confirm": true,
+ "version": 1,
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:59:37.083704+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "ad98d253-3b6d-45",
+ "pipeline_id": "issue-3393",
+ "from_role": "documenter",
+ "to_role": "all",
+ "message_type": "CONSENSUS_CONFIRMED",
+ "subject": "Consensus confirmed by documenter",
+ "body": "orchestrator-side confirm (#3064 event loop)",
+ "metadata": {
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:59:41.084732+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "74069485-e828-44",
+ "pipeline_id": "issue-3393",
+ "from_role": "tester",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "one-shot event handler action=ack (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:59:43.905238+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "8572e64c-3489-4c",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_code",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "one-shot event handler action=ack (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:59:44.154983+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "b86b258f-a8e9-48",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_code_holistic",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "one-shot event handler action=ack (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:59:45.331287+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "dcf2eb59-8d86-43",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_code",
+ "to_role": "coder",
+ "message_type": "CONSENSUS_ACK",
+ "subject": "ACK from reviewer_code for coder",
+ "body": "Verified the coder's slice-6 change to orchestrator/routes/pipelines.py against live code. (1) `_resolve_slice_gate_repo(slice, pipeline) -> str|None` wraps models.resolve_slice_repo and matches the tester's pinned TestSliceGateRepoAccessor interface exactly (secondary\u2192own repo, repoless\u2192primary, N=1\u2192singleton); py_compile clean. (2) The `_run_concurrent_phase` slice-scoping block is correctly gated (`slice_id and len(pipeline.repos) > 1`); contract load is best-effort and degrades to the pipeline primary on failure without blocking the spawn; `_resolve_slice_worktree_path` returns the per-repo worktree if it exists else fallback; per-repo base_branch is pulled from the matching RepoSpec; slice-repo-first `repos` ordering drives the spawner cwd/EGG_REPO_PATH; repo/repo_path are threaded to both the prompt builder and the spawn. (3) Correctness invariant confirmed: `_sync_repos_and_legacy_singleton` keeps `pipeline.repo == repos[0].repo == primary_repo`, so the `resolved != pipeline.repo` guard makes a repoless (primary) slice stay on the primary worktree and only a genuine secondary-repo slice divert \u2014 no false-divert bug. N=1 path is byte-identical (block skipped). No blocking issues.",
+ "metadata": {
+ "payload": {
+ "artifact_references": [
+ "orchestrator/routes/pipelines.py"
+ ],
+ "reason": "Verified the coder's slice-6 change to orchestrator/routes/pipelines.py against live code. (1) `_resolve_slice_gate_repo(slice, pipeline) -> str|None` wraps models.resolve_slice_repo and matches the tester's pinned TestSliceGateRepoAccessor interface exactly (secondary\u2192own repo, repoless\u2192primary, N=1\u2192singleton); py_compile clean. (2) The `_run_concurrent_phase` slice-scoping block is correctly gated (`slice_id and len(pipeline.repos) > 1`); contract load is best-effort and degrades to the pipeline primary on failure without blocking the spawn; `_resolve_slice_worktree_path` returns the per-repo worktree if it exists else fallback; per-repo base_branch is pulled from the matching RepoSpec; slice-repo-first `repos` ordering drives the spawner cwd/EGG_REPO_PATH; repo/repo_path are threaded to both the prompt builder and the spawn. (3) Correctness invariant confirmed: `_sync_repos_and_legacy_singleton` keeps `pipeline.repo == repos[0].repo == primary_repo`, so the `resolved != pipeline.repo` guard makes a repoless (primary) slice stay on the primary worktree and only a genuine secondary-repo slice divert \u2014 no false-divert bug. N=1 path is byte-identical (block skipped). No blocking issues.",
+ "ack_version": 1
+ },
+ "version": 1,
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:59:45.391461+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "394ff6bd-d343-49",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_contract",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "one-shot event handler action=ack (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:59:46.970106+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "cd7cfbc5-c7be-4b",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_security",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "one-shot event handler action=ack (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:59:49.331720+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "b9b620ae-8a23-49",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_concurrency",
+ "to_role": "coder",
+ "message_type": "CONSENSUS_ACK",
+ "subject": "ACK from reviewer_concurrency for coder",
+ "body": "Concurrency-clean. Slice-6 task-6-1/6-2 scopes test-gate/reviewer-diff/cwd/base-branch to slice.repo. (1) New vars (slice_repo, slice_repo_path, slice_repos, slice_base_branch) are all function-local to _run_concurrent_phase \u2014 no new shared mutable state; slice_repos is a freshly-built list, not a mutation of shared `repos`; repo_volumes threaded through read-only. (2) load_contract is read-only with a try/except that degrades to the primary repo \u2014 safe fallback, no write race. (3) _resolve_slice_worktree_path reuses the SAME per-repo keying as the pre-existing _resolve_pipeline_worktree_path; .exists() is a benign read, worst-case race falls back to the primary worktree. (4) N=1 gated (len(pipeline.repos) > 1) \u21d2 single-repo byte-identical, zero new concurrency surface. (5) _resolve_slice_gate_repo is a pure delegate \u2014 no concurrency concern. Non-blocking/out-of-scope: same-repo concurrent slices sharing one repo worktree is a pre-existing DAG-scheduler property (already true for the primary repo), not introduced here.",
+ "metadata": {
+ "payload": {
+ "artifact_references": [
+ "orchestrator/routes/pipelines.py"
+ ],
+ "reason": "Concurrency-clean. Slice-6 task-6-1/6-2 scopes test-gate/reviewer-diff/cwd/base-branch to slice.repo. (1) New vars (slice_repo, slice_repo_path, slice_repos, slice_base_branch) are all function-local to _run_concurrent_phase \u2014 no new shared mutable state; slice_repos is a freshly-built list, not a mutation of shared `repos`; repo_volumes threaded through read-only. (2) load_contract is read-only with a try/except that degrades to the primary repo \u2014 safe fallback, no write race. (3) _resolve_slice_worktree_path reuses the SAME per-repo keying as the pre-existing _resolve_pipeline_worktree_path; .exists() is a benign read, worst-case race falls back to the primary worktree. (4) N=1 gated (len(pipeline.repos) > 1) \u21d2 single-repo byte-identical, zero new concurrency surface. (5) _resolve_slice_gate_repo is a pure delegate \u2014 no concurrency concern. Non-blocking/out-of-scope: same-repo concurrent slices sharing one repo worktree is a pre-existing DAG-scheduler property (already true for the primary repo), not introduced here.",
+ "ack_version": 1
+ },
+ "version": 1,
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:59:49.410372+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "944e4616-1be5-4e",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_concurrency",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "one-shot event handler action=ack (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T18:59:57.989548+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "d2c2e549-be9c-4c",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_code_holistic",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "in-tool-loop liveness (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T19:00:15.583363+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "5cc12a4f-eb93-4a",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_security",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "in-tool-loop liveness (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T19:00:17.920033+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "651abf0a-55c5-48",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_contract",
+ "to_role": "coder",
+ "message_type": "CONSENSUS_ACK",
+ "subject": "ACK from reviewer_contract for coder",
+ "body": "ACK task-6-1 (scope test-gate/reviewer-diff/cwd/conventions to slice.repo). All three acceptance criteria met: (1) test gate + reviewer diff scoped to the slice's repo \u2014 repo=slice_repo, repo_path=slice_repo_path, base branch resolved from the slice's RepoSpec.base_branch (fallback pipeline base \u2192 get_default_branch in the slice worktree); (2) agent cwd + per-repo conventions \u2014 repos=[resolved,*rest] orders the slice's repo first, and I verified kubernetes_spawner/_spawn.py derives EGG_REPO_PATH/cwd from next(iter(repos)), so cwd points at the slice's worktree, while repo=slice_repo drives get_repo_checks and file-boundary patterns from that repo; (3) N=1 baseline unchanged \u2014 the whole block is gated on len(pipeline.repos)>1 and resolved!=pipeline.repo, so single-repo/egg slices are byte-identical. _resolve_slice_gate_repo is a thin wrapper over models.resolve_slice_repo (single source of truth, converges with the tester's TestSliceGateRepoAccessor). Scope confined to orchestrator/routes/pipelines.py (matches task-6-1 files_affected); py_compile clean. Non-blocking note: a secondary repo with RepoSpec.base_branch=None falls back to the primary base rather than auto-detecting the secondary default, but slice-2 auto-detects base at submission so RepoSpec.base_branch should already be populated.",
+ "metadata": {
+ "payload": {
+ "artifact_references": [
+ "orchestrator/routes/pipelines.py"
+ ],
+ "reason": "ACK task-6-1 (scope test-gate/reviewer-diff/cwd/conventions to slice.repo). All three acceptance criteria met: (1) test gate + reviewer diff scoped to the slice's repo \u2014 repo=slice_repo, repo_path=slice_repo_path, base branch resolved from the slice's RepoSpec.base_branch (fallback pipeline base \u2192 get_default_branch in the slice worktree); (2) agent cwd + per-repo conventions \u2014 repos=[resolved,*rest] orders the slice's repo first, and I verified kubernetes_spawner/_spawn.py derives EGG_REPO_PATH/cwd from next(iter(repos)), so cwd points at the slice's worktree, while repo=slice_repo drives get_repo_checks and file-boundary patterns from that repo; (3) N=1 baseline unchanged \u2014 the whole block is gated on len(pipeline.repos)>1 and resolved!=pipeline.repo, so single-repo/egg slices are byte-identical. _resolve_slice_gate_repo is a thin wrapper over models.resolve_slice_repo (single source of truth, converges with the tester's TestSliceGateRepoAccessor). Scope confined to orchestrator/routes/pipelines.py (matches task-6-1 files_affected); py_compile clean. Non-blocking note: a secondary repo with RepoSpec.base_branch=None falls back to the primary base rather than auto-detecting the secondary default, but slice-2 auto-detects base at submission so RepoSpec.base_branch should already be populated.",
+ "ack_version": 1,
+ "attestation": {
+ "tasks_verified": [
+ "task-6-1"
+ ],
+ "compiles": true,
+ "scope_confined": true,
+ "n1_baseline_unchanged": true
+ }
+ },
+ "version": 1,
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T19:00:17.960842+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "28594ce8-12d2-4c",
+ "pipeline_id": "issue-3393",
+ "from_role": "orchestrator",
+ "to_role": "coder",
+ "message_type": "STATUS",
+ "subject": "Ready to confirm \u2014 all confirm preconditions satisfied",
+ "body": "Your proposal (version 1) is ready to confirm \u2014 all blocking reviews are clear and global confirm preconditions are met. Run `egg-orch consensus confirmed` to confirm.",
+ "metadata": {
+ "ready_to_confirm": true,
+ "version": 1,
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T19:00:17.962090+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "47550371-d0aa-4c",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_code",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "one-shot event handler action=ack (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T19:00:18.418743+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "046bf195-24cc-40",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_concurrency",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "one-shot event handler action=ack (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T19:00:20.049953+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "0c8b542a-6729-40",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_concurrency",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "in-tool-loop liveness (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T19:00:22.314594+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "cd665b79-5561-40",
+ "pipeline_id": "issue-3393",
+ "from_role": "coder",
+ "to_role": "all",
+ "message_type": "CONSENSUS_CONFIRMED",
+ "subject": "Consensus confirmed by coder",
+ "body": "orchestrator-side confirm (#3064 event loop)",
+ "metadata": {
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T19:00:23.226702+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "e293c4ef-2f6a-46",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_security",
+ "to_role": "tester",
+ "message_type": "CONSENSUS_ACK",
+ "subject": "ACK from reviewer_security for tester",
+ "body": "Security-neutral (test-only). No security surface: pure model-level rule assertions + skip-guarded integration tests, no credential handling, network/egress, subprocess exec, or hardcoded secrets; standard tmp_path/monkeypatch fixtures. If anything it reinforces the multi-repo isolation posture \u2014 the tests pin that each slice's test gate, reviewer diff base, and check/lint commands scope to the slice's OWN repo (resolve_slice_repo), never cross-wired to the pipeline primary, which is exactly the property that prevents a secondary-repo slice from being validated against the wrong repo. Skip-guards degrade cleanly until the coder seam converges. No security concerns.",
+ "metadata": {
+ "payload": {
+ "artifact_references": [
+ "orchestrator/tests/test_pipelines.py"
+ ],
+ "reason": "Security-neutral (test-only). No security surface: pure model-level rule assertions + skip-guarded integration tests, no credential handling, network/egress, subprocess exec, or hardcoded secrets; standard tmp_path/monkeypatch fixtures. If anything it reinforces the multi-repo isolation posture \u2014 the tests pin that each slice's test gate, reviewer diff base, and check/lint commands scope to the slice's OWN repo (resolve_slice_repo), never cross-wired to the pipeline primary, which is exactly the property that prevents a secondary-repo slice from being validated against the wrong repo. Skip-guards degrade cleanly until the coder seam converges. No security concerns.",
+ "ack_version": 1
+ },
+ "version": 1,
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T19:00:42.926570+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "7ac1f11e-7eb0-44",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_contract",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "one-shot event handler action=ack (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T19:00:43.361579+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "c71503f2-a7f3-4f",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_security",
+ "to_role": "all",
+ "message_type": "CONSENSUS_CONFIRMED",
+ "subject": "Consensus confirmed by reviewer_security",
+ "body": "orchestrator-side confirm (#3064 event loop)",
+ "metadata": {
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T19:00:45.442775+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "2d5a701c-61f5-4f",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_contract",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "in-tool-loop liveness (slice=slice-6)",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T19:00:52.891589+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "a9a45d3d-af3a-47",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_security",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "context-measure occ=77607 util=0.08 cache_hit=0.98 decision=below_threshold",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T19:00:52.983425+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "2e01b1b6-c4ca-4f",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_concurrency",
+ "to_role": "tester",
+ "message_type": "CONSENSUS_ACK",
+ "subject": "ACK from reviewer_concurrency for tester",
+ "body": "Concurrency-clean (test-only). Slice-6 task-6-2 adds per-repo gate/diff/convention scoping tests. (1) All added tests are synchronous unit/rule tests \u2014 no threads, no async, no concurrent.futures, no shared mutable state across tests; zero concurrency surface introduced. (2) monkeypatch.setattr(_routes_pkg, '_WORKTREE_BASE_DIR', tmp_path) mutates a module global but pytest runs sequentially and monkeypatch auto-reverts per test \u2014 no cross-test race; each test uses isolated tmp_path fixtures so no filesystem contention. (3) Positively, the tests pin the per-slice worktree/gate scoping invariant I ACKed on the coder side: TestPerRepoWorktreeSelection asserts the resolver selects the slice's own per-repo subdir (not the primary's), and TestSliceGateRepoAccessor cross-checks _resolve_slice_gate_repo == resolve_slice_repo. Skip-guards on coder-owned seams (_resolve_slice_gate_repo, cross_repo_merge_gate) are correct convergence hygiene. Nothing in the concurrency domain to block on.",
+ "metadata": {
+ "payload": {
+ "artifact_references": [
+ "orchestrator/tests/test_pipelines.py"
+ ],
+ "reason": "Concurrency-clean (test-only). Slice-6 task-6-2 adds per-repo gate/diff/convention scoping tests. (1) All added tests are synchronous unit/rule tests \u2014 no threads, no async, no concurrent.futures, no shared mutable state across tests; zero concurrency surface introduced. (2) monkeypatch.setattr(_routes_pkg, '_WORKTREE_BASE_DIR', tmp_path) mutates a module global but pytest runs sequentially and monkeypatch auto-reverts per test \u2014 no cross-test race; each test uses isolated tmp_path fixtures so no filesystem contention. (3) Positively, the tests pin the per-slice worktree/gate scoping invariant I ACKed on the coder side: TestPerRepoWorktreeSelection asserts the resolver selects the slice's own per-repo subdir (not the primary's), and TestSliceGateRepoAccessor cross-checks _resolve_slice_gate_repo == resolve_slice_repo. Skip-guards on coder-owned seams (_resolve_slice_gate_repo, cross_repo_merge_gate) are correct convergence hygiene. Nothing in the concurrency domain to block on.",
+ "ack_version": 1
+ },
+ "version": 1,
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T19:01:14.196159+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "786839bd-7fc0-43",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_concurrency",
+ "to_role": "all",
+ "message_type": "CONSENSUS_CONFIRMED",
+ "subject": "Consensus confirmed by reviewer_concurrency",
+ "body": "orchestrator-side confirm (#3064 event loop)",
+ "metadata": {
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T19:01:15.524234+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "956c28bc-4aef-48",
+ "pipeline_id": "issue-3393",
+ "from_role": "tester",
+ "to_role": "coder",
+ "message_type": "CONSENSUS_ACK",
+ "subject": "ACK from tester for coder",
+ "body": "Verified coder proposal 996d3cb1b (slice-6 task-6-1). The new routes.pipelines._resolve_slice_gate_repo(slice, pipeline) accessor is exactly the seam my task-6-2 gap pinned: it delegates to models.resolve_slice_repo, so gate/diff/cwd all key off the slice's own repo (primary-fallback for a repoless slice, single-repo for N=1). Ran the slice-6 tester suite against this SHA: 16/16 pass, including the now-activated TestSliceGateRepoAccessor (test_accessor_matches_rule_for_multi_repo, test_accessor_n1_single_repo) which converges at exactly this commit. N=1 byte-equivalence assertions (gate/diff-base/checks) remain green. Call-site refactor in _run_concurrent_phase preserves the len(pipeline.repos)>1 guard.",
+ "metadata": {
+ "payload": {
+ "artifact_references": [
+ "orchestrator/routes/pipelines.py",
+ "orchestrator/tests/test_pipelines.py"
+ ],
+ "reason": "Verified coder proposal 996d3cb1b (slice-6 task-6-1). The new routes.pipelines._resolve_slice_gate_repo(slice, pipeline) accessor is exactly the seam my task-6-2 gap pinned: it delegates to models.resolve_slice_repo, so gate/diff/cwd all key off the slice's own repo (primary-fallback for a repoless slice, single-repo for N=1). Ran the slice-6 tester suite against this SHA: 16/16 pass, including the now-activated TestSliceGateRepoAccessor (test_accessor_matches_rule_for_multi_repo, test_accessor_n1_single_repo) which converges at exactly this commit. N=1 byte-equivalence assertions (gate/diff-base/checks) remain green. Call-site refactor in _run_concurrent_phase preserves the len(pipeline.repos)>1 guard.",
+ "ack_version": 1,
+ "attestation": {
+ "tasks_verified": [
+ "task-6-2"
+ ],
+ "tests_run": 16,
+ "checks_passed": [
+ "test"
+ ]
+ }
+ },
+ "version": 1,
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T19:01:16.686859+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "0f9ef7aa-95ee-45",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_code_holistic",
+ "to_role": "tester",
+ "message_type": "CONSENSUS_ACK",
+ "subject": "ACK from reviewer_code_holistic for tester",
+ "body": "Slice-6 tester v1 ACK. The per-repo gate/diff/convention scoping suite (task-6-2) follows the established two-layer shape: always-green rule tests (_expected_gate_repo / _expected_diff_base / _resolve_checks_for_slice) are thin mirrors over the real shipped model API (resolve_slice_repo, primary_repo, RepoSpec.base_branch) \u2014 so TestPerRepoTestGateScoping / TestPerRepoReviewerDiffBase / TestPerRepoCheckCommandResolution pin the slice\u2192repo invariant against production code, not tautologies. The diff-base logic agrees with the coder's slice_base_branch resolution in _run_concurrent_phase. Real integration coverage via TestPerRepoWorktreeSelection (routes.resolve_worktree_path per-repo subdir) and TestSliceGateRepoAccessor (coder accessor == rule); both activate at convergence and pass live. All 16 slice-6 tests green. Skip guards are principled (explicit reasons, activate at convergence \u2014 no false-green). Latent non-blocking gap: no direct end-to-end test that _run_concurrent_phase threads the accessor result into the spawn kwarg \u2014 inherent to the heavy Flask/docker surface, adequately compensated by the accessor + worktree-resolver units. No correctness or false-green concerns.",
+ "metadata": {
+ "payload": {
+ "artifact_references": [
+ "orchestrator/tests/test_pipelines.py"
+ ],
+ "reason": "Slice-6 tester v1 ACK. The per-repo gate/diff/convention scoping suite (task-6-2) follows the established two-layer shape: always-green rule tests (_expected_gate_repo / _expected_diff_base / _resolve_checks_for_slice) are thin mirrors over the real shipped model API (resolve_slice_repo, primary_repo, RepoSpec.base_branch) \u2014 so TestPerRepoTestGateScoping / TestPerRepoReviewerDiffBase / TestPerRepoCheckCommandResolution pin the slice\u2192repo invariant against production code, not tautologies. The diff-base logic agrees with the coder's slice_base_branch resolution in _run_concurrent_phase. Real integration coverage via TestPerRepoWorktreeSelection (routes.resolve_worktree_path per-repo subdir) and TestSliceGateRepoAccessor (coder accessor == rule); both activate at convergence and pass live. All 16 slice-6 tests green. Skip guards are principled (explicit reasons, activate at convergence \u2014 no false-green). Latent non-blocking gap: no direct end-to-end test that _run_concurrent_phase threads the accessor result into the spawn kwarg \u2014 inherent to the heavy Flask/docker surface, adequately compensated by the accessor + worktree-resolver units. No correctness or false-green concerns.",
+ "ack_version": 1
+ },
+ "version": 1,
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T19:01:21.405244+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "eefce2f3-7aab-48",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_concurrency",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "context-measure occ=85579 util=0.09 cache_hit=0.99 decision=below_threshold",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T19:01:23.412986+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "f0754cd4-e3d3-45",
+ "pipeline_id": "issue-3393",
+ "from_role": "tester",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "context-measure occ=68016 util=0.07 cache_hit=0.98 decision=no_warm_session",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T19:01:25.026349+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "e1f15a85-2b04-49",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_code_holistic",
+ "to_role": "all",
+ "message_type": "CONSENSUS_CONFIRMED",
+ "subject": "Consensus confirmed by reviewer_code_holistic",
+ "body": "orchestrator-side confirm (#3064 event loop)",
+ "metadata": {
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T19:01:25.575566+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "a0d41c2b-4e6b-4e",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_code_holistic",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "context-measure occ=105743 util=0.11 cache_hit=0.99 decision=below_threshold",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T19:01:26.538792+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "8fe28656-4074-49",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_code",
+ "to_role": "tester",
+ "message_type": "CONSENSUS_ACK",
+ "subject": "ACK from reviewer_code for tester",
+ "body": "Reviewed the tester's slice-6 (task-6-2) tests in orchestrator/tests/test_pipelines.py and RAN them: all 16 slice-6 tests PASS, none skipped \u2014 both Flask-surface classes (TestPerRepoWorktreeSelection, TestSliceGateRepoAccessor) activated, confirming convergence with the coder's landed _resolve_slice_gate_repo. Verified the spec-mirror helpers are faithful to the coder's real logic: _expected_gate_repo == resolve_slice_repo; _expected_diff_base iterates pipeline.repos for the matching RepoSpec.base_branch with a pipeline.base_branch fallback (matches _run_concurrent_phase); _resolve_checks_for_slice keys checks by the resolved repo. Real seams confirmed present: routes.resolve_worktree_path(pipeline_id, repo_path) at routes/__init__.py:246 (basename-keyed), the monkeypatch target _routes_pkg._WORKTREE_BASE_DIR at __init__.py:243, and _Path imported at line 432. TestSliceGateRepoAccessor cross-checks the coder's accessor against the expected rule. Skip guards are appropriate and N=1/egg baselines are pinned throughout. No blocking issues.",
+ "metadata": {
+ "payload": {
+ "artifact_references": [
+ "orchestrator/tests/test_pipelines.py"
+ ],
+ "reason": "Reviewed the tester's slice-6 (task-6-2) tests in orchestrator/tests/test_pipelines.py and RAN them: all 16 slice-6 tests PASS, none skipped \u2014 both Flask-surface classes (TestPerRepoWorktreeSelection, TestSliceGateRepoAccessor) activated, confirming convergence with the coder's landed _resolve_slice_gate_repo. Verified the spec-mirror helpers are faithful to the coder's real logic: _expected_gate_repo == resolve_slice_repo; _expected_diff_base iterates pipeline.repos for the matching RepoSpec.base_branch with a pipeline.base_branch fallback (matches _run_concurrent_phase); _resolve_checks_for_slice keys checks by the resolved repo. Real seams confirmed present: routes.resolve_worktree_path(pipeline_id, repo_path) at routes/__init__.py:246 (basename-keyed), the monkeypatch target _routes_pkg._WORKTREE_BASE_DIR at __init__.py:243, and _Path imported at line 432. TestSliceGateRepoAccessor cross-checks the coder's accessor against the expected rule. Skip guards are appropriate and N=1/egg baselines are pinned throughout. No blocking issues.",
+ "ack_version": 1
+ },
+ "version": 1,
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T19:01:58.523013+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "24e471d3-92af-44",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_code",
+ "to_role": "all",
+ "message_type": "CONSENSUS_CONFIRMED",
+ "subject": "Consensus confirmed by reviewer_code",
+ "body": "orchestrator-side confirm (#3064 event loop)",
+ "metadata": {
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T19:02:02.661389+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "fa1fa3f5-536d-4c",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_code",
+ "to_role": "all",
+ "message_type": "HEARTBEAT",
+ "subject": "heartbeat: WORKING",
+ "body": "context-measure occ=119606 util=0.12 cache_hit=0.97 decision=below_threshold",
+ "metadata": {
+ "state": "WORKING",
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T19:02:05.108124+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "cde084fa-634c-4c",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_contract",
+ "to_role": "tester",
+ "message_type": "CONSENSUS_ACK",
+ "subject": "ACK from reviewer_contract for tester",
+ "body": "ACK task-6-2 (slice-6 per-repo gate/diff/convention tests). All three AC assertions are covered: per-repo test-gate scoping (TestPerRepoTestGateScoping, plus TestPerRepoWorktreeSelection binding the real routes.resolve_worktree_path and TestSliceGateRepoAccessor binding the coder's routes.pipelines._resolve_slice_gate_repo), per-repo reviewer-diff base (TestPerRepoReviewerDiffBase), and per-repo check-command resolution (TestPerRepoCheckCommandResolution); each class carries an egg-repo / N=1 baseline. Green requirement met: full test_pipelines.py runs 58 passed / 2 skipped, the slice-6 subset runs 16 passed / 0 skipped (both coder seams \u2014 _resolve_slice_gate_repo @ pipelines.py:10604 and resolve_worktree_path @ routes/__init__.py:246 \u2014 are already integrated, so the integration tests are live coverage rather than silent skips), and ruff is clean. The two-layer always-green-rule + skip-guarded-integration shape matches the accepted slices-2/4/5 convention.",
+ "metadata": {
+ "payload": {
+ "artifact_references": [
+ "orchestrator/tests/test_pipelines.py"
+ ],
+ "reason": "ACK task-6-2 (slice-6 per-repo gate/diff/convention tests). All three AC assertions are covered: per-repo test-gate scoping (TestPerRepoTestGateScoping, plus TestPerRepoWorktreeSelection binding the real routes.resolve_worktree_path and TestSliceGateRepoAccessor binding the coder's routes.pipelines._resolve_slice_gate_repo), per-repo reviewer-diff base (TestPerRepoReviewerDiffBase), and per-repo check-command resolution (TestPerRepoCheckCommandResolution); each class carries an egg-repo / N=1 baseline. Green requirement met: full test_pipelines.py runs 58 passed / 2 skipped, the slice-6 subset runs 16 passed / 0 skipped (both coder seams \u2014 _resolve_slice_gate_repo @ pipelines.py:10604 and resolve_worktree_path @ routes/__init__.py:246 \u2014 are already integrated, so the integration tests are live coverage rather than silent skips), and ruff is clean. The two-layer always-green-rule + skip-guarded-integration shape matches the accepted slices-2/4/5 convention.",
+ "ack_version": 1,
+ "attestation": {
+ "tasks_verified": [
+ "task-6-2"
+ ],
+ "tests_result": "58 passed / 2 skipped (full test_pipelines.py); slice-6 subset 16 passed / 0 skipped; ruff clean",
+ "coder_seams_integrated": true
+ }
+ },
+ "version": 1,
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T19:02:27.144643+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "9b430758-f749-4b",
+ "pipeline_id": "issue-3393",
+ "from_role": "orchestrator",
+ "to_role": "tester",
+ "message_type": "STATUS",
+ "subject": "Ready to confirm \u2014 all confirm preconditions satisfied",
+ "body": "Your proposal (version 1) is ready to confirm \u2014 all blocking reviews are clear and global confirm preconditions are met. Run `egg-orch consensus confirmed` to confirm.",
+ "metadata": {
+ "ready_to_confirm": true,
+ "version": 1,
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T19:02:27.146100+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "2eb702b2-d360-46",
+ "pipeline_id": "issue-3393",
+ "from_role": "tester",
+ "to_role": "all",
+ "message_type": "CONSENSUS_CONFIRMED",
+ "subject": "Consensus confirmed by tester",
+ "body": "orchestrator-side confirm (#3064 event loop)",
+ "metadata": {
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T19:02:27.812126+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "7e3a14ff-6a85-47",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_contract",
+ "to_role": "all",
+ "message_type": "CONSENSUS_CONFIRMED",
+ "subject": "Consensus confirmed by reviewer_contract",
+ "body": "orchestrator-side confirm (#3064 event loop)",
+ "metadata": {
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T19:02:27.813954+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "5ab3b45e-eb67-4a",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_security",
+ "to_role": "all",
+ "message_type": "CONSENSUS_CONFIRMED",
+ "subject": "Consensus confirmed by reviewer_security",
+ "body": "orchestrator-side confirm (#3064 event loop)",
+ "metadata": {
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T19:02:27.815663+00:00",
+ "phase": "implement"
+ },
+ {
+ "id": "73c2f11d-b148-41",
+ "pipeline_id": "issue-3393",
+ "from_role": "reviewer_concurrency",
+ "to_role": "all",
+ "message_type": "CONSENSUS_CONFIRMED",
+ "subject": "Consensus confirmed by reviewer_concurrency",
+ "body": "orchestrator-side confirm (#3064 event loop)",
+ "metadata": {
+ "slice_id": "slice-6"
+ },
+ "timestamp": "2026-07-02T19:02:27.816572+00:00",
+ "phase": "implement"
+ }
+]
\ No newline at end of file
diff --git a/.egg-state/brc-history/3393-implement-slice-6.md b/.egg-state/brc-history/3393-implement-slice-6.md
new file mode 100644
index 0000000000..9a5db00c82
--- /dev/null
+++ b/.egg-state/brc-history/3393-implement-slice-6.md
@@ -0,0 +1,1423 @@
+# BRC Consensus History — implement phase, slice-6
+
+Generated: 2026-07-02T19:02:27Z
+Pipeline: issue-3393
+Slice: slice-6
+
+### [2026-07-02T18:33:13Z] coder (HEARTBEAT): heartbeat: WORKING
+
+one-shot event handler action=propose (slice=slice-6)
+
+````yaml
+id: c175a84e-8064-4a
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:33:13Z] tester (HEARTBEAT): heartbeat: WORKING
+
+one-shot event handler action=propose (slice=slice-6)
+
+````yaml
+id: ea4810bc-83e4-4a
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:33:14Z] documenter (HEARTBEAT): heartbeat: WORKING
+
+one-shot event handler action=propose (slice=slice-6)
+
+````yaml
+id: 27697764-8377-4d
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:33:19Z] coder (HEARTBEAT): heartbeat: WORKING
+
+in-tool-loop liveness (slice=slice-6)
+
+````yaml
+id: 9bc04f98-098f-43
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:33:21Z] tester (HEARTBEAT): heartbeat: WORKING
+
+in-tool-loop liveness (slice=slice-6)
+
+````yaml
+id: cee975b0-ba6f-41
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:33:22Z] documenter (HEARTBEAT): heartbeat: WORKING
+
+in-tool-loop liveness (slice=slice-6)
+
+````yaml
+id: 49f90c8d-2be5-49
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:35:30Z] documenter (HEARTBEAT): heartbeat: WORKING
+
+in-tool-loop liveness (slice=slice-6)
+
+````yaml
+id: bfacfcf8-5e10-40
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:35:35Z] tester (HEARTBEAT): heartbeat: WORKING
+
+in-tool-loop liveness (slice=slice-6)
+
+````yaml
+id: 51369776-e23f-48
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:35:39Z] coder (HEARTBEAT): heartbeat: WORKING
+
+in-tool-loop liveness (slice=slice-6)
+
+````yaml
+id: fc842ce0-e5d1-4f
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:36:40Z] documenter (CONSENSUS_PROPOSE): Proposal from documenter
+
+Documented the shipped multi-repo pipeline model across the two current-state docs (task-6-3). docs/guides/sdlc-pipeline.md gains a "Multi-Repo Pipelines" section: list-shaped submission via submit_task / POST /api/v1/pipelines with per-repo base_branch (RepoSpec), the primary-repo concept (Pipeline.primary_repo, resolve_slice_repo), and the uniform-visibility + uniform-auth submission-time rules (get_repo_visibility fail-closed, get_auth_mode; same-name-different-owner allowed). docs/architecture/slice-dag.md gains a Slice.repo row in the New Slice fields table plus a "Per-slice repo (multi-repo pipelines)" section covering: the 1:1 slice↔repo rule + runtime resolver + schemaVersion 1.4 additive stamp; owner/repo-keyed worktree map (EGG_PIPELINE_REPOS); lazy per-repo work branch + context PR; per-slice PR routing with sibling cross-references; cross-repo ordering via slice dependencies (B→A cross-repo iff resolve_slice_repo differs); the two-tier cq-1 merge-sequencing hold (Tier A automated draft→ready on mergedAt not head-SHA, mark_pr_ready, CLOSED-unmerged + bounded-timeout HITL terminals; Tier B beyond-merge-state HITL opt-in); and per-repo test-gate/reviewer-diff/convention scoping. Current-state snapshot, no slice-number or pipeline-process narration; bidirectional cross-links between the two docs.
+
+````yaml
+id: 3d2a4331-115a-4d
+phase: implement
+metadata:
+ payload:
+ summary: "Documented the shipped multi-repo pipeline model across the two current-state\
+ \ docs (task-6-3). docs/guides/sdlc-pipeline.md gains a \"Multi-Repo Pipelines\"\
+ \ section: list-shaped submission via submit_task / POST /api/v1/pipelines with\
+ \ per-repo base_branch (RepoSpec), the primary-repo concept (Pipeline.primary_repo,\
+ \ resolve_slice_repo), and the uniform-visibility + uniform-auth submission-time\
+ \ rules (get_repo_visibility fail-closed, get_auth_mode; same-name-different-owner\
+ \ allowed). docs/architecture/slice-dag.md gains a Slice.repo row in the New\
+ \ Slice fields table plus a \"Per-slice repo (multi-repo pipelines)\" section\
+ \ covering: the 1:1 slice\u2194repo rule + runtime resolver + schemaVersion\
+ \ 1.4 additive stamp; owner/repo-keyed worktree map (EGG_PIPELINE_REPOS); lazy\
+ \ per-repo work branch + context PR; per-slice PR routing with sibling cross-references;\
+ \ cross-repo ordering via slice dependencies (B\u2192A cross-repo iff resolve_slice_repo\
+ \ differs); the two-tier cq-1 merge-sequencing hold (Tier A automated draft\u2192\
+ ready on mergedAt not head-SHA, mark_pr_ready, CLOSED-unmerged + bounded-timeout\
+ \ HITL terminals; Tier B beyond-merge-state HITL opt-in); and per-repo test-gate/reviewer-diff/convention\
+ \ scoping. Current-state snapshot, no slice-number or pipeline-process narration;\
+ \ bidirectional cross-links between the two docs."
+ attestation: {}
+ artifacts:
+ - docs/guides/sdlc-pipeline.md
+ - docs/architecture/slice-dag.md
+ risk_considered: 'Docs-only change (documenter file boundary: docs/ + **/*.md).
+ No code/behavior change. Verified every documented symbol against the landed
+ implementation (Slice.repo + schemaVersion 1.4 in shared/egg_contracts/models.py;
+ RepoSpec/Pipeline.repos/primary_repo/resolve_slice_repo in orchestrator/models.py;
+ EGG_PIPELINE_REPOS in kubernetes_spawner/_spawn.py; mark_pr_ready in gateway_client/_pr.py;
+ cross_repo_merge_gate.py two-tier hold). Anchors verified consistent across
+ both docs.'
+ commit_sha: 6afc6dfb75b3581d056cf128068e3dbf1fe19846
+ files_changed:
+ - docs/guides/sdlc-pipeline.md
+ - docs/architecture/slice-dag.md
+ tests_run: []
+ tasks_satisfied:
+ - task-6-3
+ no_changes_needed: false
+ no_changes_reason: ''
+ version: 1
+ commit_sha: 6afc6dfb75b3581d056cf128068e3dbf1fe19846
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:36:49Z] reviewer_code (HEARTBEAT): heartbeat: WORKING
+
+one-shot event handler action=ack (slice=slice-6)
+
+````yaml
+id: c609056b-8ef4-43
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:36:49Z] reviewer_contract (HEARTBEAT): heartbeat: WORKING
+
+one-shot event handler action=ack (slice=slice-6)
+
+````yaml
+id: 737acca0-73ac-49
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:36:55Z] documenter (HEARTBEAT): heartbeat: WORKING
+
+context-measure occ=135475 util=0.14 cache_hit=0.99 decision=no_warm_session
+
+````yaml
+id: 48575b44-0ba9-4c
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:36:58Z] reviewer_code (HEARTBEAT): heartbeat: WORKING
+
+in-tool-loop liveness (slice=slice-6)
+
+````yaml
+id: bbd8e37f-62ed-4f
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:36:58Z] reviewer_contract (HEARTBEAT): heartbeat: WORKING
+
+in-tool-loop liveness (slice=slice-6)
+
+````yaml
+id: ec23e71f-8c65-40
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:37:42Z] tester (HEARTBEAT): heartbeat: WORKING
+
+in-tool-loop liveness (slice=slice-6)
+
+````yaml
+id: 46b6eeec-6ea1-4d
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:37:49Z] coder (HEARTBEAT): heartbeat: WORKING
+
+in-tool-loop liveness (slice=slice-6)
+
+````yaml
+id: da7dfca4-bbff-48
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:37:57Z] reviewer_code → documenter (CONSENSUS_ACK): ACK from reviewer_code for documenter
+
+Verified every concrete anchor in the docs diff against live code — all accurate and non-misleading. resolve_slice_repo (models.py:1484 returns slice.repo or pipeline.primary_repo), Pipeline.repos:list[RepoSpec] + primary_repo property, Slice.repo optional field with schema 1.4 additive _migrate_schema_version_to_1_4, cross_repo_merge_gate.py (draft-PR Tier-A hold; merge detection via mergedAt/merged NOT head-SHA per _is_merged; CLOSED-not-merged + attempt-bound HITL terminals), mark_pr_ready gateway verb, create_slice_pr repo-parameterized, gateway/repo_visibility.py:get_repo_visibility, and EGG_PIPELINE_REPOS owner/repo-keyed worktree map with naming env falling back to primary. Cross-file doc anchors resolve. No blocking issues.
+
+````yaml
+id: a5fe14a2-d55c-4e
+phase: implement
+metadata:
+ payload:
+ artifact_references:
+ - docs/guides/sdlc-pipeline.md
+ - docs/architecture/slice-dag.md
+ reason: "Verified every concrete anchor in the docs diff against live code \u2014\
+ \ all accurate and non-misleading. resolve_slice_repo (models.py:1484 returns\
+ \ slice.repo or pipeline.primary_repo), Pipeline.repos:list[RepoSpec] + primary_repo\
+ \ property, Slice.repo optional field with schema 1.4 additive _migrate_schema_version_to_1_4,\
+ \ cross_repo_merge_gate.py (draft-PR Tier-A hold; merge detection via mergedAt/merged\
+ \ NOT head-SHA per _is_merged; CLOSED-not-merged + attempt-bound HITL terminals),\
+ \ mark_pr_ready gateway verb, create_slice_pr repo-parameterized, gateway/repo_visibility.py:get_repo_visibility,\
+ \ and EGG_PIPELINE_REPOS owner/repo-keyed worktree map with naming env falling\
+ \ back to primary. Cross-file doc anchors resolve. No blocking issues."
+ ack_version: 1
+ version: 1
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:38:06Z] reviewer_code (HEARTBEAT): heartbeat: WORKING
+
+context-measure occ=66003 util=0.07 cache_hit=0.96 decision=no_warm_session
+
+````yaml
+id: 3f67c7c0-eed1-42
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:38:47Z] reviewer_contract → documenter (CONSENSUS_ACK): ACK from reviewer_contract for documenter
+
+ACK task-6-3 (docs snapshot of shipped multi-repo model). Both acceptance criteria met: (1) sdlc-pipeline.md documents list-shaped submission (MCP submit_task + POST /api/v1/pipelines, per-repo base_branch), the uniform-visibility + uniform-auth rules, and the primary-repo concept; (2) slice-dag.md documents Slice.repo (1:1 rule), per-repo work-branch/context-PR, cross-repo ordering via slice dependencies, the two-tier merge-sequencing hold, and per-repo gate/diff/convention scoping — as a current-state snapshot. Verified every code claim resolves to shipped symbols (resolve_slice_repo, Pipeline.primary_repo, RepoSpec/Pipeline.repos, _migrate_schema_version_to_1_4→schemaVersion 1.4, EGG_PIPELINE_REPOS + back-compat EGG_PIPELINE_REPO/EGG_REPO_PATH, cross_repo_merge_gate.py/mark_pr_ready, create_slice_pr, _assert_repo_set_uniform + get_repo_visibility/get_auth_mode). New sections contain no prohibited slice-N/task-N narration (only standard #3393 issue cites). Snapshot is faithful.
+
+````yaml
+id: 38c2ced7-c301-4d
+phase: implement
+metadata:
+ payload:
+ artifact_references:
+ - docs/guides/sdlc-pipeline.md
+ - docs/architecture/slice-dag.md
+ reason: "ACK task-6-3 (docs snapshot of shipped multi-repo model). Both acceptance\
+ \ criteria met: (1) sdlc-pipeline.md documents list-shaped submission (MCP submit_task\
+ \ + POST /api/v1/pipelines, per-repo base_branch), the uniform-visibility +\
+ \ uniform-auth rules, and the primary-repo concept; (2) slice-dag.md documents\
+ \ Slice.repo (1:1 rule), per-repo work-branch/context-PR, cross-repo ordering\
+ \ via slice dependencies, the two-tier merge-sequencing hold, and per-repo gate/diff/convention\
+ \ scoping \u2014 as a current-state snapshot. Verified every code claim resolves\
+ \ to shipped symbols (resolve_slice_repo, Pipeline.primary_repo, RepoSpec/Pipeline.repos,\
+ \ _migrate_schema_version_to_1_4\u2192schemaVersion 1.4, EGG_PIPELINE_REPOS\
+ \ + back-compat EGG_PIPELINE_REPO/EGG_REPO_PATH, cross_repo_merge_gate.py/mark_pr_ready,\
+ \ create_slice_pr, _assert_repo_set_uniform + get_repo_visibility/get_auth_mode).\
+ \ New sections contain no prohibited slice-N/task-N narration (only standard\
+ \ #3393 issue cites). Snapshot is faithful."
+ ack_version: 1
+ attestation:
+ tasks_verified:
+ - task-6-3
+ snapshot_accuracy: verified against shipped code (resolve_slice_repo, primary_repo,
+ RepoSpec, schemaVersion 1.4 additive after-stamp, EGG_PIPELINE_REPOS + back-compat
+ env, cross_repo_merge_gate/mark_pr_ready, create_slice_pr, uniform visibility+auth
+ validation)
+ no_slice_task_narration: true
+ version: 1
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:38:56Z] reviewer_contract (HEARTBEAT): heartbeat: WORKING
+
+context-measure occ=72801 util=0.07 cache_hit=0.99 decision=no_warm_session
+
+````yaml
+id: a5679d28-302d-44
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:39:55Z] tester (HEARTBEAT): heartbeat: WORKING
+
+in-tool-loop liveness (slice=slice-6)
+
+````yaml
+id: cc8e41bf-aa24-42
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:39:56Z] coder (HEARTBEAT): heartbeat: WORKING
+
+in-tool-loop liveness (slice=slice-6)
+
+````yaml
+id: a92fc394-1da9-4e
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:42:42Z] tester (HEARTBEAT): heartbeat: WORKING
+
+in-tool-loop liveness (slice=slice-6)
+
+````yaml
+id: d2371efd-ba54-4c
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:42:42Z] coder (HEARTBEAT): heartbeat: WORKING
+
+in-tool-loop liveness (slice=slice-6)
+
+````yaml
+id: ba41bf1b-d32a-42
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:55:37Z] tester (HEARTBEAT): heartbeat: WORKING
+
+in-tool-loop liveness (slice=slice-6)
+
+````yaml
+id: 82c40eaf-75df-41
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:55:44Z] coder (HEARTBEAT): heartbeat: WORKING
+
+in-tool-loop liveness (slice=slice-6)
+
+````yaml
+id: a33b3eb3-3ae9-4c
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:58:01Z] coder (CONSENSUS_PROPOSE): Proposal from coder
+
+Slice-6 task-6-1: scope the implement-phase test gate, reviewer diff, per-repo check/lint commands, and agent cwd to the slice's repo (slice ↔ repo 1:1). All wiring lands in `_run_concurrent_phase` (per-slice team spawn driver) in orchestrator/routes/pipelines.py. There is NO orchestrator-side `make test`/`make lint` subprocess (grep-confirmed) — the test gate is the tester agent's configured checks and the reviewer diff is the reviewers' in-worktree `git diff origin/...HEAD`; both are scoped by threading the slice's repo/worktree/base into the prompt builder + spawn. Changes: (1) `_resolve_slice_gate_repo(slice, pipeline)` — the single gate-repo source of truth (== resolve_slice_repo), matching the tester's pinned task-6-2 TestSliceGateRepoAccessor; (2) `_resolve_slice_worktree_path` helper (WORKTREE_BASE_DIR/pipeline.id/, mirrors slice-3 owner/repo layout); (3) a scoping block gated on `slice_id and len(pipeline.repos) > 1` that loads the contract, resolves the slice's repo via the accessor, and — only when it differs from the primary — sets slice_repo, slice_repo_path, per-repo base (from the matching RepoSpec), and reorders `repos` slice-first; (4) `_build_agent_prompt` gets repo=slice_repo + repo_path=slice_repo_path (drives get_repo_checks for per-repo commands, file-boundary patterns, reviewer diff base); (5) `create_concurrent_spawn_fn` gets repos=slice_repos — `_spawn.py:460` derives cwd/EGG_REPO_PATH from next(iter(repos)), so slice-first ordering sets the agent cwd to the slice's repo worktree. N=1 (egg) is byte-identical: block skipped for ≤1-repo pipelines, no extra contract read. Contract-load failure soft-degrades to primary. Merged tester task-6-2 tests (3db72777e) + documenter docs (6afc6dfb7).
+
+````yaml
+id: d4346bbb-df3a-4e
+phase: implement
+metadata:
+ payload:
+ summary: "Slice-6 task-6-1: scope the implement-phase test gate, reviewer diff,\
+ \ per-repo check/lint commands, and agent cwd to the slice's repo (slice \u2194\
+ \ repo 1:1). All wiring lands in `_run_concurrent_phase` (per-slice team spawn\
+ \ driver) in orchestrator/routes/pipelines.py. There is NO orchestrator-side\
+ \ `make test`/`make lint` subprocess (grep-confirmed) \u2014 the test gate is\
+ \ the tester agent's configured checks and the reviewer diff is the reviewers'\
+ \ in-worktree `git diff origin/...HEAD`; both are scoped by threading\
+ \ the slice's repo/worktree/base into the prompt builder + spawn. Changes: (1)\
+ \ `_resolve_slice_gate_repo(slice, pipeline)` \u2014 the single gate-repo source\
+ \ of truth (== resolve_slice_repo), matching the tester's pinned task-6-2 TestSliceGateRepoAccessor;\
+ \ (2) `_resolve_slice_worktree_path` helper (WORKTREE_BASE_DIR/pipeline.id/,\
+ \ mirrors slice-3 owner/repo layout); (3) a scoping block gated on `slice_id\
+ \ and len(pipeline.repos) > 1` that loads the contract, resolves the slice's\
+ \ repo via the accessor, and \u2014 only when it differs from the primary \u2014\
+ \ sets slice_repo, slice_repo_path, per-repo base (from the matching RepoSpec),\
+ \ and reorders `repos` slice-first; (4) `_build_agent_prompt` gets repo=slice_repo\
+ \ + repo_path=slice_repo_path (drives get_repo_checks for per-repo commands,\
+ \ file-boundary patterns, reviewer diff base); (5) `create_concurrent_spawn_fn`\
+ \ gets repos=slice_repos \u2014 `_spawn.py:460` derives cwd/EGG_REPO_PATH from\
+ \ next(iter(repos)), so slice-first ordering sets the agent cwd to the slice's\
+ \ repo worktree. N=1 (egg) is byte-identical: block skipped for \u22641-repo\
+ \ pipelines, no extra contract read. Contract-load failure soft-degrades to\
+ \ primary. Merged tester task-6-2 tests (3db72777e) + documenter docs (6afc6dfb7)."
+ attestation: {}
+ artifacts:
+ - orchestrator/routes/pipelines.py
+ risk_considered: "N=1 byte-equivalence guaranteed by the `len(pipeline.repos)\
+ \ > 1` guard (the only runnable case today; secondary-repo worktree materialisation\
+ \ is task-7-1, still pending). Reordering `repos` makes the slice's repo primary\
+ \ for a secondary slice, so EGG_PIPELINE_REPO becomes the slice repo \u2014\
+ \ correct under slice\u2194repo 1:1 (authorship/naming follow the slice's repo);\
+ \ EGG_PIPELINE_REPOS full owner/repo map is unchanged. Multi-repo end-to-end\
+ \ depends on task-7-1 populating repo_volumes with secondaries + creating the\
+ \ on-disk worktree; this slice is structurally complete and forward-compatible.\
+ \ Boundary: `_build_slice_diff_summary` (PR-body rendering, best-effort) intentionally\
+ \ not rescoped \u2014 it is not the reviewer audit diff or test gate."
+ commit_sha: 996d3cb1b
+ files_changed:
+ - orchestrator/routes/pipelines.py
+ - .egg-state/agent-outputs/coder/brc-memory-issue-3393.md
+ tests_run: []
+ tasks_satisfied:
+ - task-6-1
+ no_changes_needed: false
+ no_changes_reason: ''
+ version: 1
+ commit_sha: 996d3cb1b
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:58:01Z] coder (HEARTBEAT): heartbeat: WORKING
+
+in-tool-loop liveness (slice=slice-6)
+
+````yaml
+id: 4812a307-d8c6-48
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:58:08Z] reviewer_code (HEARTBEAT): heartbeat: WORKING
+
+one-shot event handler action=ack (slice=slice-6)
+
+````yaml
+id: c6266913-42a3-4f
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:58:10Z] reviewer_code_holistic (HEARTBEAT): heartbeat: WORKING
+
+one-shot event handler action=ack (slice=slice-6)
+
+````yaml
+id: 71c91f42-7e99-4b
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:58:11Z] reviewer_contract (HEARTBEAT): heartbeat: WORKING
+
+one-shot event handler action=ack (slice=slice-6)
+
+````yaml
+id: 22297563-de3a-4a
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:58:12Z] reviewer_security (HEARTBEAT): heartbeat: WORKING
+
+one-shot event handler action=ack (slice=slice-6)
+
+````yaml
+id: 1651d55c-1d1a-4b
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:58:14Z] reviewer_concurrency (HEARTBEAT): heartbeat: WORKING
+
+one-shot event handler action=ack (slice=slice-6)
+
+````yaml
+id: a8aa2d18-bb29-4e
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:58:16Z] reviewer_code (HEARTBEAT): heartbeat: WORKING
+
+in-tool-loop liveness (slice=slice-6)
+
+````yaml
+id: a4143ff4-be00-40
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:58:16Z] coder (HEARTBEAT): heartbeat: WORKING
+
+context-measure occ=177977 util=0.18 cache_hit=0.99 decision=no_warm_session
+
+````yaml
+id: 86729858-f670-4a
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:58:19Z] reviewer_code_holistic (HEARTBEAT): heartbeat: WORKING
+
+in-tool-loop liveness (slice=slice-6)
+
+````yaml
+id: 561972ca-ac57-4a
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:58:21Z] reviewer_concurrency (HEARTBEAT): heartbeat: WORKING
+
+in-tool-loop liveness (slice=slice-6)
+
+````yaml
+id: 5a23f9eb-e507-43
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:58:22Z] reviewer_security (HEARTBEAT): heartbeat: WORKING
+
+in-tool-loop liveness (slice=slice-6)
+
+````yaml
+id: 8abd0cff-5370-43
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:58:26Z] reviewer_contract (HEARTBEAT): heartbeat: WORKING
+
+in-tool-loop liveness (slice=slice-6)
+
+````yaml
+id: 36780167-109f-47
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:58:28Z] tester (HEARTBEAT): heartbeat: WORKING
+
+in-tool-loop liveness (slice=slice-6)
+
+````yaml
+id: d3701e0d-e715-41
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:59:03Z] reviewer_security → coder (CONSENSUS_ACK): ACK from reviewer_security for coder
+
+Security-neutral. The slice-6 change scopes a slice's gate-repo/worktree/cwd to its own repo without introducing any credential, egress, or visibility surface: gateway/network mode (private-vs-public posture) is threaded through unchanged, repo_volumes stays owner/repo-keyed with per-repo token resolution untouched, and the slice_repos reordering only selects the agent cwd (EGG_REPO_PATH), not credential scope — so no cross-repo private→public content flow. Worktree path resolution is bounded (split("/")[-1] + exists() guard + safe fallback) and repo slugs come from validated submission RepoSpecs, not agent input, so no path-traversal vector. Contract-load failure degrades fail-safe to the pipeline primary. The _resolve_slice_gate_repo extraction is a behavior-preserving delegation to resolve_slice_repo. No security concerns.
+
+````yaml
+id: 32e78b93-1937-4a
+phase: implement
+metadata:
+ payload:
+ artifact_references:
+ - orchestrator/routes/pipelines.py
+ reason: "Security-neutral. The slice-6 change scopes a slice's gate-repo/worktree/cwd\
+ \ to its own repo without introducing any credential, egress, or visibility\
+ \ surface: gateway/network mode (private-vs-public posture) is threaded through\
+ \ unchanged, repo_volumes stays owner/repo-keyed with per-repo token resolution\
+ \ untouched, and the slice_repos reordering only selects the agent cwd (EGG_REPO_PATH),\
+ \ not credential scope \u2014 so no cross-repo private\u2192public content flow.\
+ \ Worktree path resolution is bounded (split(\"/\")[-1] + exists() guard + safe\
+ \ fallback) and repo slugs come from validated submission RepoSpecs, not agent\
+ \ input, so no path-traversal vector. Contract-load failure degrades fail-safe\
+ \ to the pipeline primary. The _resolve_slice_gate_repo extraction is a behavior-preserving\
+ \ delegation to resolve_slice_repo. No security concerns."
+ ack_version: 1
+ version: 1
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:59:28Z] reviewer_code_holistic → coder (CONSENSUS_ACK): ACK from reviewer_code_holistic for coder
+
+Slice-6 coder v1 ACK. The new routes-layer accessor _resolve_slice_gate_repo(slice, pipeline) is a clean single-source-of-truth wrapper over models.resolve_slice_repo, matching the tester's pinned TestSliceGateRepoAccessor interface exactly (secondary→slice repo, repoless→primary, N=1→single repo; both tests pass at convergence). The inline resolve_slice_repo import was correctly removed from _run_concurrent_phase with no dangling reference — the only call site there now uses the accessor. Behavior is byte-identical for N=1 (gated on len(pipeline.repos) > 1) and pure delegation for multi-repo, so no new behavior beyond the already-reviewed slice-repo scoping. No correctness, architecture, or consistency concerns.
+
+````yaml
+id: 966ec29f-754c-4c
+phase: implement
+metadata:
+ payload:
+ artifact_references:
+ - orchestrator/routes/pipelines.py
+ - orchestrator/tests/test_pipelines.py
+ reason: "Slice-6 coder v1 ACK. The new routes-layer accessor _resolve_slice_gate_repo(slice,\
+ \ pipeline) is a clean single-source-of-truth wrapper over models.resolve_slice_repo,\
+ \ matching the tester's pinned TestSliceGateRepoAccessor interface exactly (secondary\u2192\
+ slice repo, repoless\u2192primary, N=1\u2192single repo; both tests pass at\
+ \ convergence). The inline resolve_slice_repo import was correctly removed from\
+ \ _run_concurrent_phase with no dangling reference \u2014 the only call site\
+ \ there now uses the accessor. Behavior is byte-identical for N=1 (gated on\
+ \ len(pipeline.repos) > 1) and pure delegation for multi-repo, so no new behavior\
+ \ beyond the already-reviewed slice-repo scoping. No correctness, architecture,\
+ \ or consistency concerns."
+ ack_version: 1
+ version: 1
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:59:32Z] reviewer_security (HEARTBEAT): heartbeat: WORKING
+
+context-measure occ=63224 util=0.06 cache_hit=0.97 decision=no_warm_session
+
+````yaml
+id: d0dc3d70-cef6-46
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:59:35Z] reviewer_code_holistic (HEARTBEAT): heartbeat: WORKING
+
+context-measure occ=88982 util=0.09 cache_hit=0.97 decision=no_warm_session
+
+````yaml
+id: b4441b09-ce07-4c
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:59:37Z] tester (CONSENSUS_PROPOSE): Proposal from tester
+
+slice-6 (task-6-2): per-repo test-gate + reviewer-diff + check-command scoping tests — CONVERGED with coder. Added a section to orchestrator/tests/test_pipelines.py (commit 3db72777e, already integrated into the slice branch) following the slices-2/4/5 two-layer idiom. Always-green model-level rule tests pin that every implement-phase gate scopes to the slice's OWN repo (resolve_slice_repo), not the primary: TestPerRepoTestGateScoping (gate repo = slice repo; repoless→primary; per-slice; egg-in-multi-repo baseline; N=1), TestPerRepoReviewerDiffBase (diff base = slice repo's RepoSpec.base_branch, differs from primary for cross-repo; repoless→primary; N=1 unchanged), TestPerRepoCheckCommandResolution (check/lint commands keyed by slice repo; egg baseline; N=1). REAL test TestPerRepoWorktreeSelection exercises routes.resolve_worktree_path (monkeypatched _WORKTREE_BASE_DIR + tmp_path): the slice's repo path resolves to that repo's own per-repo worktree subdir, not the primary's. TestSliceGateRepoAccessor targets the coder seam routes.pipelines._resolve_slice_gate_repo — handed to the coder via task-6-1 gap-1 and now LANDED (coder commit 996d3cb1b), so this class has ACTIVATED and passes against the real accessor (asserts it == resolve_slice_repo for secondary/repoless/N=1). Post-convergence verification on the integrated branch tip: pytest orchestrator/tests/test_pipelines.py -k 'PerRepo or SliceGateRepo' = 23 passed, 0 skipped; full module 56 passed earlier; ruff check + format clean.
+
+````yaml
+id: 6eb0af16-b6ab-44
+phase: implement
+metadata:
+ payload:
+ summary: "slice-6 (task-6-2): per-repo test-gate + reviewer-diff + check-command\
+ \ scoping tests \u2014 CONVERGED with coder. Added a section to orchestrator/tests/test_pipelines.py\
+ \ (commit 3db72777e, already integrated into the slice branch) following the\
+ \ slices-2/4/5 two-layer idiom. Always-green model-level rule tests pin that\
+ \ every implement-phase gate scopes to the slice's OWN repo (resolve_slice_repo),\
+ \ not the primary: TestPerRepoTestGateScoping (gate repo = slice repo; repoless\u2192\
+ primary; per-slice; egg-in-multi-repo baseline; N=1), TestPerRepoReviewerDiffBase\
+ \ (diff base = slice repo's RepoSpec.base_branch, differs from primary for cross-repo;\
+ \ repoless\u2192primary; N=1 unchanged), TestPerRepoCheckCommandResolution (check/lint\
+ \ commands keyed by slice repo; egg baseline; N=1). REAL test TestPerRepoWorktreeSelection\
+ \ exercises routes.resolve_worktree_path (monkeypatched _WORKTREE_BASE_DIR +\
+ \ tmp_path): the slice's repo path resolves to that repo's own per-repo worktree\
+ \ subdir, not the primary's. TestSliceGateRepoAccessor targets the coder seam\
+ \ routes.pipelines._resolve_slice_gate_repo \u2014 handed to the coder via task-6-1\
+ \ gap-1 and now LANDED (coder commit 996d3cb1b), so this class has ACTIVATED\
+ \ and passes against the real accessor (asserts it == resolve_slice_repo for\
+ \ secondary/repoless/N=1). Post-convergence verification on the integrated branch\
+ \ tip: pytest orchestrator/tests/test_pipelines.py -k 'PerRepo or SliceGateRepo'\
+ \ = 23 passed, 0 skipped; full module 56 passed earlier; ruff check + format\
+ \ clean."
+ attestation:
+ tests_execution_blocked: true
+ tests_execution_blocked_reason: 'Full configured repo check suite (lint, security,
+ test) cannot execute in this tester sandbox: PyPI egress is blocked (invalid
+ peer certificate / UnknownIssuer via the squid proxy) so `make deps`/`sync-venv`
+ cannot build .venv, and every `make lint`/`make test`/`make security` target
+ depends on sync-venv-if-uv; security tooling (bandit, safety, trivy) and yamllint
+ are also absent system-wide so the `security` check cannot run here at all.
+ What I DID run directly with the system interpreter against the CONVERGED
+ branch tip (996d3cb1b, coder + tester integrated) and which passed (reviewer
+ detail, not a strict-mode checks_passed claim): `pytest orchestrator/tests/test_pipelines.py
+ -k ''PerRepo or SliceGateRepo''` -> 23 passed / 0 skipped (the coder''s _resolve_slice_gate_repo
+ landed so TestSliceGateRepoAccessor activated and passes); full-module `pytest
+ orchestrator/tests/test_pipelines.py` -> 56 passed / 4 skipped (pre-existing
+ slice-2/4 guards) before convergence; `ruff check` + `ruff format --check`
+ clean. The narrowed `make test` / full `make test-all` + `make security` remain
+ the convergence-time gate in the CI/gateway environment.'
+ artifacts:
+ - orchestrator/tests/test_pipelines.py
+ - 3db72777e
+ risk_considered: "Converged with the coder: task-6-1 (gate scoping 4553fd012 +\
+ \ _resolve_slice_gate_repo accessor 996d3cb1b) is now integrated on the slice\
+ \ branch alongside my tests, so the previously skip-guarded TestSliceGateRepoAccessor\
+ \ is active and green \u2014 no dangling permanently-skipped test. Always-green\
+ \ rule tests pin the invariants against the slice-1 model API + shipped resolve_worktree_path\
+ \ resolver independent of coder internals. Commit already on origin (ancestor\
+ \ of tip) so push=false. Sandbox cannot build .venv (blocked egress) so make\
+ \ lint/security/test are unrunnable here \u2014 attested tests_execution_blocked\
+ \ with direct pytest+ruff results documented; make test-all/security is the\
+ \ convergence gate. Scope kept strictly to task-6-2 AC \u2014 not entangled\
+ \ with the slice-4/7 cq-4 secondary-repo worktree-materialization HITL."
+ commit_sha: 3db72777e
+ files_changed:
+ - orchestrator/tests/test_pipelines.py
+ tests_run: []
+ tasks_satisfied:
+ - task-6-2
+ no_changes_needed: false
+ no_changes_reason: ''
+ version: 1
+ commit_sha: 3db72777e
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:59:37Z] orchestrator → documenter (STATUS): Ready to confirm — all confirm preconditions satisfied
+
+Your proposal (version 1) is ready to confirm — all blocking reviews are clear and global confirm preconditions are met. Run `egg-orch consensus confirmed` to confirm.
+
+````yaml
+id: 6e3afe2f-b386-44
+phase: implement
+metadata:
+ ready_to_confirm: true
+ version: 1
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:59:41Z] documenter (CONSENSUS_CONFIRMED): Consensus confirmed by documenter
+
+orchestrator-side confirm (#3064 event loop)
+
+````yaml
+id: ad98d253-3b6d-45
+phase: implement
+metadata:
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:59:43Z] tester (HEARTBEAT): heartbeat: WORKING
+
+one-shot event handler action=ack (slice=slice-6)
+
+````yaml
+id: 74069485-e828-44
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:59:44Z] reviewer_code (HEARTBEAT): heartbeat: WORKING
+
+one-shot event handler action=ack (slice=slice-6)
+
+````yaml
+id: 8572e64c-3489-4c
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:59:45Z] reviewer_code_holistic (HEARTBEAT): heartbeat: WORKING
+
+one-shot event handler action=ack (slice=slice-6)
+
+````yaml
+id: b86b258f-a8e9-48
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:59:45Z] reviewer_code → coder (CONSENSUS_ACK): ACK from reviewer_code for coder
+
+Verified the coder's slice-6 change to orchestrator/routes/pipelines.py against live code. (1) `_resolve_slice_gate_repo(slice, pipeline) -> str|None` wraps models.resolve_slice_repo and matches the tester's pinned TestSliceGateRepoAccessor interface exactly (secondary→own repo, repoless→primary, N=1→singleton); py_compile clean. (2) The `_run_concurrent_phase` slice-scoping block is correctly gated (`slice_id and len(pipeline.repos) > 1`); contract load is best-effort and degrades to the pipeline primary on failure without blocking the spawn; `_resolve_slice_worktree_path` returns the per-repo worktree if it exists else fallback; per-repo base_branch is pulled from the matching RepoSpec; slice-repo-first `repos` ordering drives the spawner cwd/EGG_REPO_PATH; repo/repo_path are threaded to both the prompt builder and the spawn. (3) Correctness invariant confirmed: `_sync_repos_and_legacy_singleton` keeps `pipeline.repo == repos[0].repo == primary_repo`, so the `resolved != pipeline.repo` guard makes a repoless (primary) slice stay on the primary worktree and only a genuine secondary-repo slice divert — no false-divert bug. N=1 path is byte-identical (block skipped). No blocking issues.
+
+````yaml
+id: dcf2eb59-8d86-43
+phase: implement
+metadata:
+ payload:
+ artifact_references:
+ - orchestrator/routes/pipelines.py
+ reason: "Verified the coder's slice-6 change to orchestrator/routes/pipelines.py\
+ \ against live code. (1) `_resolve_slice_gate_repo(slice, pipeline) -> str|None`\
+ \ wraps models.resolve_slice_repo and matches the tester's pinned TestSliceGateRepoAccessor\
+ \ interface exactly (secondary\u2192own repo, repoless\u2192primary, N=1\u2192\
+ singleton); py_compile clean. (2) The `_run_concurrent_phase` slice-scoping\
+ \ block is correctly gated (`slice_id and len(pipeline.repos) > 1`); contract\
+ \ load is best-effort and degrades to the pipeline primary on failure without\
+ \ blocking the spawn; `_resolve_slice_worktree_path` returns the per-repo worktree\
+ \ if it exists else fallback; per-repo base_branch is pulled from the matching\
+ \ RepoSpec; slice-repo-first `repos` ordering drives the spawner cwd/EGG_REPO_PATH;\
+ \ repo/repo_path are threaded to both the prompt builder and the spawn. (3)\
+ \ Correctness invariant confirmed: `_sync_repos_and_legacy_singleton` keeps\
+ \ `pipeline.repo == repos[0].repo == primary_repo`, so the `resolved != pipeline.repo`\
+ \ guard makes a repoless (primary) slice stay on the primary worktree and only\
+ \ a genuine secondary-repo slice divert \u2014 no false-divert bug. N=1 path\
+ \ is byte-identical (block skipped). No blocking issues."
+ ack_version: 1
+ version: 1
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:59:46Z] reviewer_contract (HEARTBEAT): heartbeat: WORKING
+
+one-shot event handler action=ack (slice=slice-6)
+
+````yaml
+id: 394ff6bd-d343-49
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:59:49Z] reviewer_security (HEARTBEAT): heartbeat: WORKING
+
+one-shot event handler action=ack (slice=slice-6)
+
+````yaml
+id: cd7cfbc5-c7be-4b
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:59:49Z] reviewer_concurrency → coder (CONSENSUS_ACK): ACK from reviewer_concurrency for coder
+
+Concurrency-clean. Slice-6 task-6-1/6-2 scopes test-gate/reviewer-diff/cwd/base-branch to slice.repo. (1) New vars (slice_repo, slice_repo_path, slice_repos, slice_base_branch) are all function-local to _run_concurrent_phase — no new shared mutable state; slice_repos is a freshly-built list, not a mutation of shared `repos`; repo_volumes threaded through read-only. (2) load_contract is read-only with a try/except that degrades to the primary repo — safe fallback, no write race. (3) _resolve_slice_worktree_path reuses the SAME per-repo keying as the pre-existing _resolve_pipeline_worktree_path; .exists() is a benign read, worst-case race falls back to the primary worktree. (4) N=1 gated (len(pipeline.repos) > 1) ⇒ single-repo byte-identical, zero new concurrency surface. (5) _resolve_slice_gate_repo is a pure delegate — no concurrency concern. Non-blocking/out-of-scope: same-repo concurrent slices sharing one repo worktree is a pre-existing DAG-scheduler property (already true for the primary repo), not introduced here.
+
+````yaml
+id: b9b620ae-8a23-49
+phase: implement
+metadata:
+ payload:
+ artifact_references:
+ - orchestrator/routes/pipelines.py
+ reason: "Concurrency-clean. Slice-6 task-6-1/6-2 scopes test-gate/reviewer-diff/cwd/base-branch\
+ \ to slice.repo. (1) New vars (slice_repo, slice_repo_path, slice_repos, slice_base_branch)\
+ \ are all function-local to _run_concurrent_phase \u2014 no new shared mutable\
+ \ state; slice_repos is a freshly-built list, not a mutation of shared `repos`;\
+ \ repo_volumes threaded through read-only. (2) load_contract is read-only with\
+ \ a try/except that degrades to the primary repo \u2014 safe fallback, no write\
+ \ race. (3) _resolve_slice_worktree_path reuses the SAME per-repo keying as\
+ \ the pre-existing _resolve_pipeline_worktree_path; .exists() is a benign read,\
+ \ worst-case race falls back to the primary worktree. (4) N=1 gated (len(pipeline.repos)\
+ \ > 1) \u21D2 single-repo byte-identical, zero new concurrency surface. (5)\
+ \ _resolve_slice_gate_repo is a pure delegate \u2014 no concurrency concern.\
+ \ Non-blocking/out-of-scope: same-repo concurrent slices sharing one repo worktree\
+ \ is a pre-existing DAG-scheduler property (already true for the primary repo),\
+ \ not introduced here."
+ ack_version: 1
+ version: 1
+ slice_id: slice-6
+````
+
+### [2026-07-02T18:59:57Z] reviewer_concurrency (HEARTBEAT): heartbeat: WORKING
+
+one-shot event handler action=ack (slice=slice-6)
+
+````yaml
+id: 944e4616-1be5-4e
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T19:00:15Z] reviewer_code_holistic (HEARTBEAT): heartbeat: WORKING
+
+in-tool-loop liveness (slice=slice-6)
+
+````yaml
+id: d2c2e549-be9c-4c
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T19:00:17Z] reviewer_security (HEARTBEAT): heartbeat: WORKING
+
+in-tool-loop liveness (slice=slice-6)
+
+````yaml
+id: 5cc12a4f-eb93-4a
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T19:00:17Z] reviewer_contract → coder (CONSENSUS_ACK): ACK from reviewer_contract for coder
+
+ACK task-6-1 (scope test-gate/reviewer-diff/cwd/conventions to slice.repo). All three acceptance criteria met: (1) test gate + reviewer diff scoped to the slice's repo — repo=slice_repo, repo_path=slice_repo_path, base branch resolved from the slice's RepoSpec.base_branch (fallback pipeline base → get_default_branch in the slice worktree); (2) agent cwd + per-repo conventions — repos=[resolved,*rest] orders the slice's repo first, and I verified kubernetes_spawner/_spawn.py derives EGG_REPO_PATH/cwd from next(iter(repos)), so cwd points at the slice's worktree, while repo=slice_repo drives get_repo_checks and file-boundary patterns from that repo; (3) N=1 baseline unchanged — the whole block is gated on len(pipeline.repos)>1 and resolved!=pipeline.repo, so single-repo/egg slices are byte-identical. _resolve_slice_gate_repo is a thin wrapper over models.resolve_slice_repo (single source of truth, converges with the tester's TestSliceGateRepoAccessor). Scope confined to orchestrator/routes/pipelines.py (matches task-6-1 files_affected); py_compile clean. Non-blocking note: a secondary repo with RepoSpec.base_branch=None falls back to the primary base rather than auto-detecting the secondary default, but slice-2 auto-detects base at submission so RepoSpec.base_branch should already be populated.
+
+````yaml
+id: 651abf0a-55c5-48
+phase: implement
+metadata:
+ payload:
+ artifact_references:
+ - orchestrator/routes/pipelines.py
+ reason: "ACK task-6-1 (scope test-gate/reviewer-diff/cwd/conventions to slice.repo).\
+ \ All three acceptance criteria met: (1) test gate + reviewer diff scoped to\
+ \ the slice's repo \u2014 repo=slice_repo, repo_path=slice_repo_path, base branch\
+ \ resolved from the slice's RepoSpec.base_branch (fallback pipeline base \u2192\
+ \ get_default_branch in the slice worktree); (2) agent cwd + per-repo conventions\
+ \ \u2014 repos=[resolved,*rest] orders the slice's repo first, and I verified\
+ \ kubernetes_spawner/_spawn.py derives EGG_REPO_PATH/cwd from next(iter(repos)),\
+ \ so cwd points at the slice's worktree, while repo=slice_repo drives get_repo_checks\
+ \ and file-boundary patterns from that repo; (3) N=1 baseline unchanged \u2014\
+ \ the whole block is gated on len(pipeline.repos)>1 and resolved!=pipeline.repo,\
+ \ so single-repo/egg slices are byte-identical. _resolve_slice_gate_repo is\
+ \ a thin wrapper over models.resolve_slice_repo (single source of truth, converges\
+ \ with the tester's TestSliceGateRepoAccessor). Scope confined to orchestrator/routes/pipelines.py\
+ \ (matches task-6-1 files_affected); py_compile clean. Non-blocking note: a\
+ \ secondary repo with RepoSpec.base_branch=None falls back to the primary base\
+ \ rather than auto-detecting the secondary default, but slice-2 auto-detects\
+ \ base at submission so RepoSpec.base_branch should already be populated."
+ ack_version: 1
+ attestation:
+ tasks_verified:
+ - task-6-1
+ compiles: true
+ scope_confined: true
+ n1_baseline_unchanged: true
+ version: 1
+ slice_id: slice-6
+````
+
+### [2026-07-02T19:00:17Z] orchestrator → coder (STATUS): Ready to confirm — all confirm preconditions satisfied
+
+Your proposal (version 1) is ready to confirm — all blocking reviews are clear and global confirm preconditions are met. Run `egg-orch consensus confirmed` to confirm.
+
+````yaml
+id: 28594ce8-12d2-4c
+phase: implement
+metadata:
+ ready_to_confirm: true
+ version: 1
+ slice_id: slice-6
+````
+
+### [2026-07-02T19:00:18Z] reviewer_code (HEARTBEAT): heartbeat: WORKING
+
+one-shot event handler action=ack (slice=slice-6)
+
+````yaml
+id: 47550371-d0aa-4c
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T19:00:20Z] reviewer_concurrency (HEARTBEAT): heartbeat: WORKING
+
+one-shot event handler action=ack (slice=slice-6)
+
+````yaml
+id: 046bf195-24cc-40
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T19:00:22Z] reviewer_concurrency (HEARTBEAT): heartbeat: WORKING
+
+in-tool-loop liveness (slice=slice-6)
+
+````yaml
+id: 0c8b542a-6729-40
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T19:00:23Z] coder (CONSENSUS_CONFIRMED): Consensus confirmed by coder
+
+orchestrator-side confirm (#3064 event loop)
+
+````yaml
+id: cd665b79-5561-40
+phase: implement
+metadata:
+ slice_id: slice-6
+````
+
+### [2026-07-02T19:00:42Z] reviewer_security → tester (CONSENSUS_ACK): ACK from reviewer_security for tester
+
+Security-neutral (test-only). No security surface: pure model-level rule assertions + skip-guarded integration tests, no credential handling, network/egress, subprocess exec, or hardcoded secrets; standard tmp_path/monkeypatch fixtures. If anything it reinforces the multi-repo isolation posture — the tests pin that each slice's test gate, reviewer diff base, and check/lint commands scope to the slice's OWN repo (resolve_slice_repo), never cross-wired to the pipeline primary, which is exactly the property that prevents a secondary-repo slice from being validated against the wrong repo. Skip-guards degrade cleanly until the coder seam converges. No security concerns.
+
+````yaml
+id: e293c4ef-2f6a-46
+phase: implement
+metadata:
+ payload:
+ artifact_references:
+ - orchestrator/tests/test_pipelines.py
+ reason: "Security-neutral (test-only). No security surface: pure model-level rule\
+ \ assertions + skip-guarded integration tests, no credential handling, network/egress,\
+ \ subprocess exec, or hardcoded secrets; standard tmp_path/monkeypatch fixtures.\
+ \ If anything it reinforces the multi-repo isolation posture \u2014 the tests\
+ \ pin that each slice's test gate, reviewer diff base, and check/lint commands\
+ \ scope to the slice's OWN repo (resolve_slice_repo), never cross-wired to the\
+ \ pipeline primary, which is exactly the property that prevents a secondary-repo\
+ \ slice from being validated against the wrong repo. Skip-guards degrade cleanly\
+ \ until the coder seam converges. No security concerns."
+ ack_version: 1
+ version: 1
+ slice_id: slice-6
+````
+
+### [2026-07-02T19:00:43Z] reviewer_contract (HEARTBEAT): heartbeat: WORKING
+
+one-shot event handler action=ack (slice=slice-6)
+
+````yaml
+id: 7ac1f11e-7eb0-44
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T19:00:45Z] reviewer_security (CONSENSUS_CONFIRMED): Consensus confirmed by reviewer_security
+
+orchestrator-side confirm (#3064 event loop)
+
+````yaml
+id: c71503f2-a7f3-4f
+phase: implement
+metadata:
+ slice_id: slice-6
+````
+
+### [2026-07-02T19:00:52Z] reviewer_contract (HEARTBEAT): heartbeat: WORKING
+
+in-tool-loop liveness (slice=slice-6)
+
+````yaml
+id: 2d5a701c-61f5-4f
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T19:00:52Z] reviewer_security (HEARTBEAT): heartbeat: WORKING
+
+context-measure occ=77607 util=0.08 cache_hit=0.98 decision=below_threshold
+
+````yaml
+id: a9a45d3d-af3a-47
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T19:01:14Z] reviewer_concurrency → tester (CONSENSUS_ACK): ACK from reviewer_concurrency for tester
+
+Concurrency-clean (test-only). Slice-6 task-6-2 adds per-repo gate/diff/convention scoping tests. (1) All added tests are synchronous unit/rule tests — no threads, no async, no concurrent.futures, no shared mutable state across tests; zero concurrency surface introduced. (2) monkeypatch.setattr(_routes_pkg, '_WORKTREE_BASE_DIR', tmp_path) mutates a module global but pytest runs sequentially and monkeypatch auto-reverts per test — no cross-test race; each test uses isolated tmp_path fixtures so no filesystem contention. (3) Positively, the tests pin the per-slice worktree/gate scoping invariant I ACKed on the coder side: TestPerRepoWorktreeSelection asserts the resolver selects the slice's own per-repo subdir (not the primary's), and TestSliceGateRepoAccessor cross-checks _resolve_slice_gate_repo == resolve_slice_repo. Skip-guards on coder-owned seams (_resolve_slice_gate_repo, cross_repo_merge_gate) are correct convergence hygiene. Nothing in the concurrency domain to block on.
+
+````yaml
+id: 2e01b1b6-c4ca-4f
+phase: implement
+metadata:
+ payload:
+ artifact_references:
+ - orchestrator/tests/test_pipelines.py
+ reason: "Concurrency-clean (test-only). Slice-6 task-6-2 adds per-repo gate/diff/convention\
+ \ scoping tests. (1) All added tests are synchronous unit/rule tests \u2014\
+ \ no threads, no async, no concurrent.futures, no shared mutable state across\
+ \ tests; zero concurrency surface introduced. (2) monkeypatch.setattr(_routes_pkg,\
+ \ '_WORKTREE_BASE_DIR', tmp_path) mutates a module global but pytest runs sequentially\
+ \ and monkeypatch auto-reverts per test \u2014 no cross-test race; each test\
+ \ uses isolated tmp_path fixtures so no filesystem contention. (3) Positively,\
+ \ the tests pin the per-slice worktree/gate scoping invariant I ACKed on the\
+ \ coder side: TestPerRepoWorktreeSelection asserts the resolver selects the\
+ \ slice's own per-repo subdir (not the primary's), and TestSliceGateRepoAccessor\
+ \ cross-checks _resolve_slice_gate_repo == resolve_slice_repo. Skip-guards on\
+ \ coder-owned seams (_resolve_slice_gate_repo, cross_repo_merge_gate) are correct\
+ \ convergence hygiene. Nothing in the concurrency domain to block on."
+ ack_version: 1
+ version: 1
+ slice_id: slice-6
+````
+
+### [2026-07-02T19:01:15Z] reviewer_concurrency (CONSENSUS_CONFIRMED): Consensus confirmed by reviewer_concurrency
+
+orchestrator-side confirm (#3064 event loop)
+
+````yaml
+id: 786839bd-7fc0-43
+phase: implement
+metadata:
+ slice_id: slice-6
+````
+
+### [2026-07-02T19:01:16Z] tester → coder (CONSENSUS_ACK): ACK from tester for coder
+
+Verified coder proposal 996d3cb1b (slice-6 task-6-1). The new routes.pipelines._resolve_slice_gate_repo(slice, pipeline) accessor is exactly the seam my task-6-2 gap pinned: it delegates to models.resolve_slice_repo, so gate/diff/cwd all key off the slice's own repo (primary-fallback for a repoless slice, single-repo for N=1). Ran the slice-6 tester suite against this SHA: 16/16 pass, including the now-activated TestSliceGateRepoAccessor (test_accessor_matches_rule_for_multi_repo, test_accessor_n1_single_repo) which converges at exactly this commit. N=1 byte-equivalence assertions (gate/diff-base/checks) remain green. Call-site refactor in _run_concurrent_phase preserves the len(pipeline.repos)>1 guard.
+
+````yaml
+id: 956c28bc-4aef-48
+phase: implement
+metadata:
+ payload:
+ artifact_references:
+ - orchestrator/routes/pipelines.py
+ - orchestrator/tests/test_pipelines.py
+ reason: 'Verified coder proposal 996d3cb1b (slice-6 task-6-1). The new routes.pipelines._resolve_slice_gate_repo(slice,
+ pipeline) accessor is exactly the seam my task-6-2 gap pinned: it delegates
+ to models.resolve_slice_repo, so gate/diff/cwd all key off the slice''s own
+ repo (primary-fallback for a repoless slice, single-repo for N=1). Ran the slice-6
+ tester suite against this SHA: 16/16 pass, including the now-activated TestSliceGateRepoAccessor
+ (test_accessor_matches_rule_for_multi_repo, test_accessor_n1_single_repo) which
+ converges at exactly this commit. N=1 byte-equivalence assertions (gate/diff-base/checks)
+ remain green. Call-site refactor in _run_concurrent_phase preserves the len(pipeline.repos)>1
+ guard.'
+ ack_version: 1
+ attestation:
+ tasks_verified:
+ - task-6-2
+ tests_run: 16
+ checks_passed:
+ - test
+ version: 1
+ slice_id: slice-6
+````
+
+### [2026-07-02T19:01:21Z] reviewer_code_holistic → tester (CONSENSUS_ACK): ACK from reviewer_code_holistic for tester
+
+Slice-6 tester v1 ACK. The per-repo gate/diff/convention scoping suite (task-6-2) follows the established two-layer shape: always-green rule tests (_expected_gate_repo / _expected_diff_base / _resolve_checks_for_slice) are thin mirrors over the real shipped model API (resolve_slice_repo, primary_repo, RepoSpec.base_branch) — so TestPerRepoTestGateScoping / TestPerRepoReviewerDiffBase / TestPerRepoCheckCommandResolution pin the slice→repo invariant against production code, not tautologies. The diff-base logic agrees with the coder's slice_base_branch resolution in _run_concurrent_phase. Real integration coverage via TestPerRepoWorktreeSelection (routes.resolve_worktree_path per-repo subdir) and TestSliceGateRepoAccessor (coder accessor == rule); both activate at convergence and pass live. All 16 slice-6 tests green. Skip guards are principled (explicit reasons, activate at convergence — no false-green). Latent non-blocking gap: no direct end-to-end test that _run_concurrent_phase threads the accessor result into the spawn kwarg — inherent to the heavy Flask/docker surface, adequately compensated by the accessor + worktree-resolver units. No correctness or false-green concerns.
+
+````yaml
+id: 0f9ef7aa-95ee-45
+phase: implement
+metadata:
+ payload:
+ artifact_references:
+ - orchestrator/tests/test_pipelines.py
+ reason: "Slice-6 tester v1 ACK. The per-repo gate/diff/convention scoping suite\
+ \ (task-6-2) follows the established two-layer shape: always-green rule tests\
+ \ (_expected_gate_repo / _expected_diff_base / _resolve_checks_for_slice) are\
+ \ thin mirrors over the real shipped model API (resolve_slice_repo, primary_repo,\
+ \ RepoSpec.base_branch) \u2014 so TestPerRepoTestGateScoping / TestPerRepoReviewerDiffBase\
+ \ / TestPerRepoCheckCommandResolution pin the slice\u2192repo invariant against\
+ \ production code, not tautologies. The diff-base logic agrees with the coder's\
+ \ slice_base_branch resolution in _run_concurrent_phase. Real integration coverage\
+ \ via TestPerRepoWorktreeSelection (routes.resolve_worktree_path per-repo subdir)\
+ \ and TestSliceGateRepoAccessor (coder accessor == rule); both activate at convergence\
+ \ and pass live. All 16 slice-6 tests green. Skip guards are principled (explicit\
+ \ reasons, activate at convergence \u2014 no false-green). Latent non-blocking\
+ \ gap: no direct end-to-end test that _run_concurrent_phase threads the accessor\
+ \ result into the spawn kwarg \u2014 inherent to the heavy Flask/docker surface,\
+ \ adequately compensated by the accessor + worktree-resolver units. No correctness\
+ \ or false-green concerns."
+ ack_version: 1
+ version: 1
+ slice_id: slice-6
+````
+
+### [2026-07-02T19:01:23Z] reviewer_concurrency (HEARTBEAT): heartbeat: WORKING
+
+context-measure occ=85579 util=0.09 cache_hit=0.99 decision=below_threshold
+
+````yaml
+id: eefce2f3-7aab-48
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T19:01:25Z] tester (HEARTBEAT): heartbeat: WORKING
+
+context-measure occ=68016 util=0.07 cache_hit=0.98 decision=no_warm_session
+
+````yaml
+id: f0754cd4-e3d3-45
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T19:01:25Z] reviewer_code_holistic (CONSENSUS_CONFIRMED): Consensus confirmed by reviewer_code_holistic
+
+orchestrator-side confirm (#3064 event loop)
+
+````yaml
+id: e1f15a85-2b04-49
+phase: implement
+metadata:
+ slice_id: slice-6
+````
+
+### [2026-07-02T19:01:26Z] reviewer_code_holistic (HEARTBEAT): heartbeat: WORKING
+
+context-measure occ=105743 util=0.11 cache_hit=0.99 decision=below_threshold
+
+````yaml
+id: a0d41c2b-4e6b-4e
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T19:01:58Z] reviewer_code → tester (CONSENSUS_ACK): ACK from reviewer_code for tester
+
+Reviewed the tester's slice-6 (task-6-2) tests in orchestrator/tests/test_pipelines.py and RAN them: all 16 slice-6 tests PASS, none skipped — both Flask-surface classes (TestPerRepoWorktreeSelection, TestSliceGateRepoAccessor) activated, confirming convergence with the coder's landed _resolve_slice_gate_repo. Verified the spec-mirror helpers are faithful to the coder's real logic: _expected_gate_repo == resolve_slice_repo; _expected_diff_base iterates pipeline.repos for the matching RepoSpec.base_branch with a pipeline.base_branch fallback (matches _run_concurrent_phase); _resolve_checks_for_slice keys checks by the resolved repo. Real seams confirmed present: routes.resolve_worktree_path(pipeline_id, repo_path) at routes/__init__.py:246 (basename-keyed), the monkeypatch target _routes_pkg._WORKTREE_BASE_DIR at __init__.py:243, and _Path imported at line 432. TestSliceGateRepoAccessor cross-checks the coder's accessor against the expected rule. Skip guards are appropriate and N=1/egg baselines are pinned throughout. No blocking issues.
+
+````yaml
+id: 8fe28656-4074-49
+phase: implement
+metadata:
+ payload:
+ artifact_references:
+ - orchestrator/tests/test_pipelines.py
+ reason: "Reviewed the tester's slice-6 (task-6-2) tests in orchestrator/tests/test_pipelines.py\
+ \ and RAN them: all 16 slice-6 tests PASS, none skipped \u2014 both Flask-surface\
+ \ classes (TestPerRepoWorktreeSelection, TestSliceGateRepoAccessor) activated,\
+ \ confirming convergence with the coder's landed _resolve_slice_gate_repo. Verified\
+ \ the spec-mirror helpers are faithful to the coder's real logic: _expected_gate_repo\
+ \ == resolve_slice_repo; _expected_diff_base iterates pipeline.repos for the\
+ \ matching RepoSpec.base_branch with a pipeline.base_branch fallback (matches\
+ \ _run_concurrent_phase); _resolve_checks_for_slice keys checks by the resolved\
+ \ repo. Real seams confirmed present: routes.resolve_worktree_path(pipeline_id,\
+ \ repo_path) at routes/__init__.py:246 (basename-keyed), the monkeypatch target\
+ \ _routes_pkg._WORKTREE_BASE_DIR at __init__.py:243, and _Path imported at line\
+ \ 432. TestSliceGateRepoAccessor cross-checks the coder's accessor against the\
+ \ expected rule. Skip guards are appropriate and N=1/egg baselines are pinned\
+ \ throughout. No blocking issues."
+ ack_version: 1
+ version: 1
+ slice_id: slice-6
+````
+
+### [2026-07-02T19:02:02Z] reviewer_code (CONSENSUS_CONFIRMED): Consensus confirmed by reviewer_code
+
+orchestrator-side confirm (#3064 event loop)
+
+````yaml
+id: 24e471d3-92af-44
+phase: implement
+metadata:
+ slice_id: slice-6
+````
+
+### [2026-07-02T19:02:05Z] reviewer_code (HEARTBEAT): heartbeat: WORKING
+
+context-measure occ=119606 util=0.12 cache_hit=0.97 decision=below_threshold
+
+````yaml
+id: fa1fa3f5-536d-4c
+phase: implement
+metadata:
+ state: WORKING
+ slice_id: slice-6
+````
+
+### [2026-07-02T19:02:27Z] reviewer_contract → tester (CONSENSUS_ACK): ACK from reviewer_contract for tester
+
+ACK task-6-2 (slice-6 per-repo gate/diff/convention tests). All three AC assertions are covered: per-repo test-gate scoping (TestPerRepoTestGateScoping, plus TestPerRepoWorktreeSelection binding the real routes.resolve_worktree_path and TestSliceGateRepoAccessor binding the coder's routes.pipelines._resolve_slice_gate_repo), per-repo reviewer-diff base (TestPerRepoReviewerDiffBase), and per-repo check-command resolution (TestPerRepoCheckCommandResolution); each class carries an egg-repo / N=1 baseline. Green requirement met: full test_pipelines.py runs 58 passed / 2 skipped, the slice-6 subset runs 16 passed / 0 skipped (both coder seams — _resolve_slice_gate_repo @ pipelines.py:10604 and resolve_worktree_path @ routes/__init__.py:246 — are already integrated, so the integration tests are live coverage rather than silent skips), and ruff is clean. The two-layer always-green-rule + skip-guarded-integration shape matches the accepted slices-2/4/5 convention.
+
+````yaml
+id: cde084fa-634c-4c
+phase: implement
+metadata:
+ payload:
+ artifact_references:
+ - orchestrator/tests/test_pipelines.py
+ reason: "ACK task-6-2 (slice-6 per-repo gate/diff/convention tests). All three\
+ \ AC assertions are covered: per-repo test-gate scoping (TestPerRepoTestGateScoping,\
+ \ plus TestPerRepoWorktreeSelection binding the real routes.resolve_worktree_path\
+ \ and TestSliceGateRepoAccessor binding the coder's routes.pipelines._resolve_slice_gate_repo),\
+ \ per-repo reviewer-diff base (TestPerRepoReviewerDiffBase), and per-repo check-command\
+ \ resolution (TestPerRepoCheckCommandResolution); each class carries an egg-repo\
+ \ / N=1 baseline. Green requirement met: full test_pipelines.py runs 58 passed\
+ \ / 2 skipped, the slice-6 subset runs 16 passed / 0 skipped (both coder seams\
+ \ \u2014 _resolve_slice_gate_repo @ pipelines.py:10604 and resolve_worktree_path\
+ \ @ routes/__init__.py:246 \u2014 are already integrated, so the integration\
+ \ tests are live coverage rather than silent skips), and ruff is clean. The\
+ \ two-layer always-green-rule + skip-guarded-integration shape matches the accepted\
+ \ slices-2/4/5 convention."
+ ack_version: 1
+ attestation:
+ tasks_verified:
+ - task-6-2
+ tests_result: 58 passed / 2 skipped (full test_pipelines.py); slice-6 subset
+ 16 passed / 0 skipped; ruff clean
+ coder_seams_integrated: true
+ version: 1
+ slice_id: slice-6
+````
+
+### [2026-07-02T19:02:27Z] orchestrator → tester (STATUS): Ready to confirm — all confirm preconditions satisfied
+
+Your proposal (version 1) is ready to confirm — all blocking reviews are clear and global confirm preconditions are met. Run `egg-orch consensus confirmed` to confirm.
+
+````yaml
+id: 9b430758-f749-4b
+phase: implement
+metadata:
+ ready_to_confirm: true
+ version: 1
+ slice_id: slice-6
+````
+
+### [2026-07-02T19:02:27Z] tester (CONSENSUS_CONFIRMED): Consensus confirmed by tester
+
+orchestrator-side confirm (#3064 event loop)
+
+````yaml
+id: 2eb702b2-d360-46
+phase: implement
+metadata:
+ slice_id: slice-6
+````
+
+### [2026-07-02T19:02:27Z] reviewer_contract (CONSENSUS_CONFIRMED): Consensus confirmed by reviewer_contract
+
+orchestrator-side confirm (#3064 event loop)
+
+````yaml
+id: 7e3a14ff-6a85-47
+phase: implement
+metadata:
+ slice_id: slice-6
+````
+
+### [2026-07-02T19:02:27Z] reviewer_security (CONSENSUS_CONFIRMED): Consensus confirmed by reviewer_security
+
+orchestrator-side confirm (#3064 event loop)
+
+````yaml
+id: 5ab3b45e-eb67-4a
+phase: implement
+metadata:
+ slice_id: slice-6
+````
+
+### [2026-07-02T19:02:27Z] reviewer_concurrency (CONSENSUS_CONFIRMED): Consensus confirmed by reviewer_concurrency
+
+orchestrator-side confirm (#3064 event loop)
+
+````yaml
+id: 73c2f11d-b148-41
+phase: implement
+metadata:
+ slice_id: slice-6
+````
diff --git a/docs/architecture/slice-dag.md b/docs/architecture/slice-dag.md
index 8f0e0a2223..5320909a2a 100644
--- a/docs/architecture/slice-dag.md
+++ b/docs/architecture/slice-dag.md
@@ -69,6 +69,7 @@ pass either.
| Field | Type | Default | Purpose |
|-------|------|---------|---------|
+| `repo` | `str \| None` | `None` | The single repository this slice operates in, `owner/name`-shaped (#3393). **Slice ↔ repo is 1:1** — exactly one repo per slice. `None` ⇒ resolved to the pipeline's primary repo at runtime by `resolve_slice_repo(slice, pipeline)` in `orchestrator/models.py` (the contract model cannot see the pipeline, so the default is *not* filled in by the model). See [Per-slice repo](#per-slice-repo-multi-repo-pipelines). |
| `serialized_chain_order` | `list[str]` | `[]` | Architect-emitted ordering for would-be multi-parent slices (#2809). When the architect identifies a slice that would naturally have >1 parents, it serialises the upstream cluster into a chain and records the chosen order on the downstream slice. |
| `parent_branch_at_creation` | `str \| None` | `None` | Git branch the slice's integration branch was forked off when its worktree was provisioned. Eager-persisted under the per-pipeline state lock in the same contract write that flips `SliceStatus.PENDING → IN_PROGRESS` ([#2777](https://github.com/jwbron/egg/issues/2777)), so Layer-C bootstrap reconciliation has a single signal that distinguishes a fresh slice from an interrupted one and the value is durable across orchestrator restarts. Read by the stacked-PR reconciler when the parent's branch has been deleted by a merge so it can compute the correct rebase target. Empty on legacy/orphaned slices that pre-date the eager-persist contract — in that case `_resolve_slice_base_branch` falls back to a merge-base probe against the dependency-derived parent before routing onto `pipeline_branch`. |
| `integration_base_sha` | `str \| None` | `None` | Origin SHA the slice's integration branch was forked at when first created (#2871). Written right after branch creation and before any agent is spawned (so the tip still equals this SHA at that point), but can be overwritten by out-of-band actors such as `restart_phase`, `salvage_agent_commits`, or manual contract edits. Lets `is_slice_branch_merged_into_parent` distinguish an *empty, un-started* branch (tip still equals this SHA → trivially an ancestor of any advanced parent, but not merged work) from a *genuinely merged* one (tip has moved past this SHA). Also used by `create_slice_integration_branch` to verify that an existing integration branch is a resumable additive fork (#2947); when this field is absent or corrupted (e.g. overwritten to the advanced parent tip by a restart actor), that method re-derives the fork point via a runtime `git merge-base` (executed on the gateway) and adopts the branch in place rather than non-fast-forward-failing the slice (#3245). Slices provisioned before this field existed fall back to the prior ancestor-only check. |
@@ -719,6 +720,122 @@ synthetic agent session: no general-purpose privileged gh-command surface
is introduced — the route accepts only `repo`/`limit` and constructs the
fixed read-only argv server-side.
+## Per-slice repo (multi-repo pipelines)
+
+A pipeline can coordinate PRs across an **arbitrary number of repositories**
+(#3393). The repo set is list-shaped end to end — `Pipeline.repos:
+list[RepoSpec]`, one entry per repo, each pinning its own `base_branch`. There
+is no two-repo special case and no primary+secondary shape in the data model;
+nothing may assume `len(repos)` ∈ {1, 2}. See the
+[SDLC Pipeline Guide § Multi-Repo Pipelines](../guides/sdlc-pipeline.md#multi-repo-pipelines)
+for the submission surface, the primary-repo concept, and the uniform-visibility
+/ uniform-auth rules enforced at submission time.
+
+The slice DAG is where cross-repo coordination lives. Each slice maps to
+**exactly one** repo, and cross-repo work is expressed as **multiple slices with
+dependencies** — never a single slice touching two repos.
+
+### `Slice.repo` — the 1:1 rule
+
+`Slice.repo` (`str | None`, `owner/name`-shaped) names the one repository a
+slice operates in. **Slice ↔ repo is 1:1**: a slice's work, worktree, branch,
+review diff, test scope, and PR all live in that single repo. This keeps
+worktree selection, PR routing, reviewer diffs, and test gating single-repo at
+the slice level — the coordination complexity lives only in the slice DAG.
+
+`Slice.repo` defaults to `None`. The absent ⇒ primary default is resolved at
+**runtime** by `resolve_slice_repo(slice, pipeline)` (orchestrator layer), which
+returns `slice.repo` when set else `pipeline.primary_repo`. The contract model
+holds no repo list and cannot see the pipeline, so the default is deliberately
+*not* a model-side migration. Adding the optional field bumped the contract
+`schemaVersion` to `1.4` via a **pure additive after-stamp**
+(`_migrate_schema_version_to_1_4`, mirroring the `1.3` precedent): a persisted
+pre-1.4 contract loads cleanly and `Slice.repo` stays `None` — no field is
+filled by the migration.
+
+### Owner/repo-keyed worktree map
+
+The gateway's `create_worktrees(repos=[...])` already returns a
+`repo → worktree path` map; that map is keyed by the **full `owner/repo` slug**
+(not the bare short name) so two repos with the same short name under different
+owners stay distinct. The full map is exposed to the agent environment as
+`EGG_PIPELINE_REPOS` — a JSON object of `owner/repo → container worktree path` —
+so a per-slice agent can select the worktree for *its* slice's repo rather than
+being collapsed onto the primary. Naming-oriented env (`EGG_PIPELINE_REPO`,
+`EGG_REPO_PATH`) still resolves to the primary for back-compat.
+
+### Per-repo work branch & context PR
+
+Work branches and context PRs are **lazy-per-repo**: every repo that owns ≥1
+slice gets its own `egg//work` branch and its own context PR
+(`egg//work → `); a submitted repo that ends up
+with no slices gets neither. A single-repo pipeline gets exactly one work branch
+and one context PR, byte-equivalent to the pre-multi-repo path. Context-PR
+bodies cross-reference their sibling context PRs in the pipeline.
+
+### Per-slice PR routing
+
+`GatewayClient.create_slice_pr` is repo-parameterized; the run loop passes each
+slice's resolved repo (`resolve_slice_repo`, falling back to the primary when
+`Slice.repo` is absent), so a slice's PR opens in that slice's repo against that
+repo's `egg//work` context branch. Slice-PR bodies render **sibling
+cross-references** — the other pipeline PRs (repo + number) and, for a dependent
+slice, the upstream slice's PR it is ordered behind.
+
+### Cross-repo ordering via slice dependencies
+
+A cross-repo dependency is an ordinary slice `dependencies` edge whose two
+endpoints resolve to different repos: an edge `B → A` is cross-repo iff
+`resolve_slice_repo(A) != resolve_slice_repo(B)`. The canonical case — add a new
+schema version in repo A, then migrate the consumer in repo B, where B's cutover
+can't land until A's PR merges — is expressed as slice B depending on slice A,
+with A in repo A and B in repo B. **Dependencies gate merge-readiness, not
+development**: B is developed in parallel with A; only B's PR ready-state waits.
+
+### Cross-repo merge-sequencing hold (two-tier)
+
+`orchestrator/cross_repo_merge_gate.py` implements the two-tier hold that
+sequences a cross-repo dependent PR behind its upstream. It is pure logic over
+injected gateway-read/write and HITL callables, driven on the existing
+stacked-PR reconciler cadence.
+
+- **Tier A — automated merge-state hold (default).** The dependent slice's PR
+ opens as a **draft** while the upstream PR is unmerged. A bounded poll watches
+ the upstream PR's merge state and, on merge, auto-marks the dependent PR ready
+ via the `mark_pr_ready(repo, pr_number)` gateway verb (wrapping `gh pr
+ ready`). **Merge detection keys off the PR `mergedAt` / merged boolean, not
+ head-SHA equality** — a squash or rebase merge produces a merge-commit SHA ≠
+ the PR head, so SHA-equality would misfire. Two failure terminals fall through
+ to a HITL hold rather than hanging: an upstream that reaches
+ **CLOSED-not-merged**, and a poll that exceeds its **attempt bound** (a
+ never-merging upstream). Both surface on pipeline status.
+- **Tier B — HITL beyond-merge-state hold (opt-in).** For an edge the plan (or
+ task description) marks with a beyond-merge-state condition — a
+ release/publish of the upstream repo, a version-pin choice, or a genuine
+ cannot-continue development block — the dependent PR is held and released
+ **only by a HITL decision**, never by programmatic detection. Absent that
+ marker, a cross-repo edge defaults to the Tier-A automated hold.
+
+All HITL holds (Tier B, plus the two Tier-A failure terminals) route through the
+same decision-queue mechanism and share a single release path.
+
+### Per-repo gate, diff & convention scoping
+
+Because slice ↔ repo is 1:1, the implement-phase gates scope naturally to the
+slice's repo:
+
+- The **test gate** runs in the slice's repo worktree only (resolved from the
+ `owner/repo`-keyed worktree map).
+- The **reviewer diff** is `git diff` in that worktree against **that repo's**
+ base branch.
+- The slice agent's **cwd** is the slice's repo worktree, and check/lint
+ commands resolve from **that repo's** conventions — its own `CLAUDE.md`,
+ linters, and check commands. egg's `make lint` / `make test` apply only to
+ slices whose repo is egg.
+
+A slice whose repo is egg (the common case) behaves exactly as a single-repo
+pipeline does today.
+
## Architect, planner & plan-reviewer prompts
The dynamic prompt builders for `task_planner` and `reviewer_plan` teach
diff --git a/docs/guides/sdlc-pipeline.md b/docs/guides/sdlc-pipeline.md
index e8a13ff91c..8bbdb3721b 100644
--- a/docs/guides/sdlc-pipeline.md
+++ b/docs/guides/sdlc-pipeline.md
@@ -425,6 +425,64 @@ The terminal "PR phase" as a separate pipeline stage was **deleted** in [#2777](
This structural enforcement prevents incidents where agents push code during planning or manually create PRs before implementation is complete.
+## Multi-Repo Pipelines
+
+A pipeline can coordinate PRs across an **arbitrary number of repositories** in
+a single run. The submission surface, pipeline state, agent environment, and PR
+coordination are all list-shaped end to end — there is no two-repo special case
+and no "primary + one secondary" shape baked into the data model.
+
+### List-shaped submission
+
+Both submission entry points accept a **list** of repositories, each pinning its
+own base branch:
+
+- **MCP `submit_task`** accepts either a single `repo` + `base_branch`
+ (back-compat) or a list of `{repo, base_branch}` entries.
+- **`POST /api/v1/pipelines`** threads the full list through pipeline
+ construction into `Pipeline.repos: list[RepoSpec]` (each `RepoSpec` carries a
+ `repo` in `owner/name` form and its own optional `base_branch`; when a
+ `base_branch` is `None` it is auto-detected from that repo's default branch).
+
+A bare single-repo submission is trivially a one-element list, so single-repo
+(N=1) pipelines behave exactly as before. Nothing downstream may assume
+`len(repos)` ∈ {1, 2}.
+
+### Primary repo
+
+One repo in the list is the **primary** — the first entry unless another is
+explicitly flagged. The primary is used for pipeline naming and as the
+slice-default repo (a slice that does not name its own repo resolves to the
+primary). Code reads the primary through the `Pipeline.primary_repo` property
+(not `repos[0]`), and resolves a slice's effective repo through
+`resolve_slice_repo(slice, pipeline)` — which returns `slice.repo` when set,
+else `pipeline.primary_repo`.
+
+### Uniform visibility and uniform auth
+
+Private mode is a **pipeline-wide posture** (context filtering, egress rules,
+what may be referenced where), so every repo in a single run must be uniformly
+private or uniformly public — a mixed set would let content from a private repo
+flow through shared plan/contract/PR surfaces into a public one. The submission
+path validates two uniformity rules and rejects a non-uniform set with an
+actionable, repo-naming error:
+
+- **Uniform visibility** — all repos private or all public, checked via
+ `gateway/repo_visibility.py:get_repo_visibility(owner, repo)`. Indeterminate
+ visibility fails closed (the run is rejected rather than assumed public).
+- **Uniform auth mode** — a single auth mode across the run, checked via the
+ per-repo `get_auth_mode(repo)` credential lookup.
+
+Same-name repos under **different owners** are *not* rejected — the collision is
+resolved by keying the repo→worktree map on the full `owner/repo` slug (see the
+[Slice-DAG architecture doc](../architecture/slice-dag.md#per-slice-repo-multi-repo-pipelines)).
+Different runs can, of course, be on different sides of the public/private line.
+
+For the slice-level model — the `Slice.repo` 1:1 rule, per-repo work branch and
+context PR, cross-repo ordering through slice dependencies, the two-tier
+merge-sequencing hold, and per-repo gate/diff/convention scoping — see
+[Per-slice repo (multi-repo pipelines)](../architecture/slice-dag.md#per-slice-repo-multi-repo-pipelines).
+
## Contract System
### Directory Structure
diff --git a/orchestrator/routes/pipelines.py b/orchestrator/routes/pipelines.py
index 3afc9537df..ae4236bb04 100644
--- a/orchestrator/routes/pipelines.py
+++ b/orchestrator/routes/pipelines.py
@@ -10601,6 +10601,47 @@ def _resolve_pipeline_worktree_path(pipeline: Pipeline, fallback: Path) -> Path:
return fallback
+def _resolve_slice_gate_repo(slice_obj, pipeline: Pipeline) -> str | None:
+ """The repo every implement-phase gate for *slice_obj* is scoped to (#3393).
+
+ Single source of truth for slice → gate-repo resolution (task-6-1): the
+ test gate, the reviewer diff base, the per-repo check/lint commands, and
+ the slice agent's cwd all key off this one accessor. It is exactly
+ :func:`models.resolve_slice_repo` — the slice's own ``repo`` when set,
+ else the pipeline's primary repo (so a repoless slice, or any slice in an
+ N=1 pipeline, scopes to the single/primary repo). Returns ``None`` only
+ for a genuinely repoless pipeline (test scaffolds with no repo at all).
+ """
+ try:
+ from models import resolve_slice_repo # type: ignore[no-redef]
+ except ImportError:
+ from ..models import resolve_slice_repo # type: ignore[no-redef]
+ return resolve_slice_repo(slice_obj, pipeline)
+
+
+def _resolve_slice_worktree_path(
+ pipeline: Pipeline, slice_repo: str | None, fallback: Path
+) -> Path:
+ """Resolve the on-disk worktree path for a slice's repo (#3393 task-6-1).
+
+ A multi-repo pipeline materialises one worktree per participating repo
+ under ``WORKTREE_BASE_DIR / pipeline.id / `` — the same
+ owner/repo-keyed layout as :func:`_resolve_pipeline_worktree_path`, one
+ directory per repo. Given a slice's resolved repo (``owner/name``), this
+ returns that repo's worktree when it exists on disk, else *fallback*
+ (the pipeline-primary worktree). For an N=1 pipeline the slice's repo IS
+ the primary, so ``slice_repo`` matches ``pipeline.repo`` and the answer
+ is byte-identical to the pipeline-primary worktree — callers therefore
+ only reach here for a genuine secondary-repo slice.
+ """
+ repo_short = slice_repo.split("/")[-1] if slice_repo else None
+ if repo_short:
+ candidate = WORKTREE_BASE_DIR / pipeline.id / repo_short
+ if candidate.exists():
+ return candidate
+ return fallback
+
+
def _persist_phase_brc_history(
pipeline: Pipeline,
store: StateStore,
@@ -20729,11 +20770,84 @@ def _run_concurrent_phase(
]
filtered_graph = ReviewGraph(filtered_edges)
- # Resolve base branch for diff commands in agent prompts.
- _resolved_base_branch = pipeline.base_branch
+ # Scope the per-slice team to the slice's repo (#3393 task-6-1).
+ #
+ # Every slice maps to exactly one repo (slice ↔ repo, 1:1). For a
+ # multi-repo pipeline the slice's work, worktree, test gate, reviewer
+ # diff and PR all live in ITS repo — not necessarily the pipeline
+ # primary. We resolve the slice's repo via ``resolve_slice_repo`` and
+ # thread the slice-scoped repo / worktree / base-branch into the agent
+ # prompts (which drive ``get_repo_checks`` for the tester's configured
+ # checks, the file-boundary patterns, and the reviewer's
+ # ``git diff origin/...HEAD``) and the spawn (via ``base_branch``
+ # → ``EGG_BASE_BRANCH`` and a slice-primary-first ``repos`` ordering so
+ # the spawner sets the agent cwd / ``EGG_REPO_PATH`` to the slice's
+ # repo worktree).
+ #
+ # N=1 stays byte-identical: a single-repo pipeline has one RepoSpec, so
+ # the block below is skipped entirely (``len(pipeline.repos) <= 1``),
+ # leaving ``slice_repo == pipeline.repo``, ``worktree_repo_path``, and
+ # the pipeline base branch exactly as before — no extra contract read.
+ slice_repo = pipeline.repo
+ slice_repo_path = worktree_repo_path
+ slice_repos = repos
+ slice_base_branch: str | None = None
+ if slice_id and len(getattr(pipeline, "repos", None) or []) > 1:
+ from egg_contracts.loader import load_contract
+
+ slice_obj = None
+ try:
+ _contract = load_contract(pipeline_id, worktree_repo_path)
+ slice_obj = next(
+ (s for s in _contract.slices if s.id == slice_id), None
+ )
+ except Exception as contract_err: # noqa: BLE001
+ # Best-effort: a contract load/parse failure degrades to the
+ # pipeline-primary repo (today's behaviour), it does not block
+ # the spawn. The slice still runs, just against the primary.
+ logger.warning(
+ "Slice-repo scoping: contract load failed; using pipeline "
+ "primary repo (#3393)",
+ pipeline_id=pipeline_id,
+ slice_id=slice_id,
+ error=str(contract_err),
+ )
+
+ # Single gate-repo accessor (shared with the tester's task-6-2
+ # TestSliceGateRepoAccessor): the repo the whole slice team scopes to.
+ resolved = _resolve_slice_gate_repo(slice_obj, pipeline) if slice_obj else None
+ if resolved and resolved != pipeline.repo:
+ slice_repo = resolved
+ slice_repo_path = _resolve_slice_worktree_path(
+ pipeline, resolved, worktree_repo_path
+ )
+ # Per-repo base branch from the pipeline's RepoSpec list.
+ for spec in pipeline.repos or []:
+ if getattr(spec, "repo", None) == resolved:
+ slice_base_branch = getattr(spec, "base_branch", None)
+ break
+ # Order the slice's repo first so the spawner treats it as the
+ # effective repo for this per-slice team (cwd / EGG_REPO_PATH).
+ # ``repo_volumes`` already carries every repo owner/repo-keyed
+ # (slice-3), so only the ordering changes here.
+ slice_repos = [resolved, *[r for r in repos if r != resolved]]
+ logger.info(
+ "Slice scoped to secondary repo (#3393 task-6-1)",
+ pipeline_id=pipeline_id,
+ slice_id=slice_id,
+ slice_repo=slice_repo,
+ slice_worktree=str(slice_repo_path),
+ )
+
+ # Resolve base branch for diff commands in agent prompts. Prefer the
+ # slice repo's own base (its RepoSpec.base_branch) over the pipeline
+ # singleton, then fall back to auto-detecting the default branch in the
+ # slice's worktree (#3393 task-6-1). For N=1 this is the pipeline base /
+ # pipeline worktree exactly as before.
+ _resolved_base_branch = slice_base_branch or pipeline.base_branch
if not _resolved_base_branch:
try:
- _resolved_base_branch = get_default_branch(worktree_repo_path)
+ _resolved_base_branch = get_default_branch(slice_repo_path)
except Exception:
_resolved_base_branch = None
@@ -20752,10 +20866,15 @@ def _run_concurrent_phase(
pipeline_mode=pipeline_mode,
prompt=pipeline.prompt,
issue_number=pipeline.issue_number,
- repo=pipeline.repo,
+ # Slice-scoped repo / worktree (#3393 task-6-1): drives the
+ # tester's ``get_repo_checks`` (per-repo configured checks),
+ # the role file-boundary patterns, and the reviewer diff base —
+ # all resolve from the slice's repo, not the pipeline primary.
+ # N=1 ⇒ these equal ``pipeline.repo`` / ``worktree_repo_path``.
+ repo=slice_repo,
branch=pipeline.branch,
base_branch=_resolved_base_branch,
- repo_path=str(worktree_repo_path),
+ repo_path=str(slice_repo_path),
concurrent=True,
review_feedback=review_feedback,
network_mode=gateway_mode,
@@ -20770,7 +20889,11 @@ def _run_concurrent_phase(
issue_number=pipeline.issue_number,
repo_volumes=repo_volumes,
mode=gateway_mode,
- repos=repos,
+ # Slice's repo first (#3393 task-6-1): the spawner derives the agent
+ # cwd / EGG_REPO_PATH from the primary (first) repo, so ordering the
+ # slice's repo first sets the working directory to that repo's
+ # worktree. N=1 / primary-repo slices leave ``repos`` unchanged.
+ repos=slice_repos,
phase=phase_str,
sandbox_env=sandbox_env,
certs_volume=certs_volume,
diff --git a/orchestrator/tests/test_pipelines.py b/orchestrator/tests/test_pipelines.py
index ecda7caccd..0b9dfe0e65 100644
--- a/orchestrator/tests/test_pipelines.py
+++ b/orchestrator/tests/test_pipelines.py
@@ -992,3 +992,320 @@ def test_keep_verdict_does_not_ready_the_pr(self):
def test_unresolved_hold_keeps_waiting(self):
readied, _ = self._run_hold_then_verdict(verdict=None)
assert readied == []
+
+
+# ============================================================================
+# Slice-6 (#3393): per-repo test-gate + reviewer-diff scoping + per-repo
+# conventions (task-6-3 tests; operator rulings #3, #5).
+#
+# The implement-phase gates must be scoped to the *slice's* repo, not the
+# pipeline primary: the test gate runs in the slice's repo worktree; the
+# reviewer diff is ``git diff`` in that worktree against *that repo's* base
+# branch; and check / lint commands resolve from the slice's repo conventions
+# (its own ``CLAUDE.md`` / linters) rather than hard-coding egg's ``make
+# lint`` / ``make test``. An egg-repo slice (the N=1 common case) must behave
+# exactly as today.
+#
+# Same two-layer shape as slices 2/4/5:
+#
+# * **Always-green rule tests** built on the slice-1 model API
+# (``resolve_slice_repo`` / ``primary_repo`` / ``Pipeline.repos``) and the
+# already-shipped ``resolve_worktree_path`` per-repo worktree resolver. These
+# pin the exact contract the slice-6 coder change must satisfy — which repo
+# each gate is scoped to — so they are meaningful before the coder half
+# integrates and never spuriously red.
+# * **Skip-guarded integration test** on the coder-owned scoping seam
+# (``routes.pipelines._resolve_slice_gate_repo``), handed to the coder via a
+# task-6-1 gap so the two halves converge on the same shape. It skips with an
+# explicit reason until the parallel coder producer lands, then activates at
+# convergence.
+# ============================================================================
+
+
+def _expected_gate_repo(slice, pipeline) -> str | None:
+ """The repo an implement-phase gate for ``slice`` is scoped to (rulings #3/#5).
+
+ The test gate's worktree, the reviewer diff's repo, and the check/lint
+ command conventions all key off the *single* repo the slice operates in —
+ ``resolve_slice_repo(slice, pipeline)`` — never the pipeline primary (unless
+ the slice itself resolves to the primary). This is the one invariant every
+ slice-6 gate-scoping site must honour.
+ """
+ return resolve_slice_repo(slice, pipeline)
+
+
+def _expected_diff_base(slice, pipeline) -> str | None:
+ """The base branch the reviewer diff for ``slice`` is computed against.
+
+ Per operator ruling #3 the diff is ``git diff`` in the slice's repo
+ worktree against *that repo's* base — i.e. the ``base_branch`` of the
+ ``RepoSpec`` for ``resolve_slice_repo(slice, pipeline)``, which for a
+ cross-repo slice differs from the primary's base. Falls back to the
+ pipeline's legacy ``base_branch`` when the resolved repo is not in the
+ ``repos`` list (belt-and-braces; the validator keeps them in sync).
+ """
+ repo = resolve_slice_repo(slice, pipeline)
+ for spec in pipeline.repos:
+ if spec.repo == repo:
+ return spec.base_branch
+ return pipeline.base_branch
+
+
+def _resolve_checks_for_slice(slice, pipeline, checks_by_repo) -> list:
+ """Spec mirror: a slice's check/lint commands come from its OWN repo.
+
+ ``checks_by_repo`` stands in for the per-repo convention source
+ (``get_repo_checks(repo)`` in the orchestrator): the commands a slice runs
+ are those registered for ``resolve_slice_repo(slice, pipeline)``, so an
+ egg slice gets egg's ``make lint`` / ``make test`` and a consumer-repo
+ slice gets the consumer repo's own checks — never a cross-wired set.
+ """
+ return checks_by_repo.get(resolve_slice_repo(slice, pipeline), [])
+
+
+class TestPerRepoTestGateScoping:
+ """The test gate runs in the *slice's* repo worktree, not the primary (AC-1)."""
+
+ @staticmethod
+ def _pipeline() -> Pipeline:
+ return Pipeline(
+ id="issue-3393",
+ repos=[
+ RepoSpec(repo="jwbron/schema", base_branch="main"), # primary
+ RepoSpec(repo="jwbron/consumer", base_branch="develop"),
+ ],
+ )
+
+ def test_gate_scopes_to_slice_repo_not_primary(self):
+ pipeline = self._pipeline()
+ s = Slice(id="slice-2", name="consumer migration", repo="jwbron/consumer")
+ assert _expected_gate_repo(s, pipeline) == "jwbron/consumer"
+ # The whole point of slice-6: the secondary slice's gate must NOT run
+ # against the primary.
+ assert _expected_gate_repo(s, pipeline) != pipeline.primary_repo
+
+ def test_repoless_slice_gate_scopes_to_primary(self):
+ pipeline = self._pipeline()
+ s = Slice(id="slice-1", name="schema add") # repo is None → primary
+ assert s.repo is None
+ assert _expected_gate_repo(s, pipeline) == "jwbron/schema"
+
+ def test_each_slice_gate_scopes_to_its_own_repo(self):
+ pipeline = self._pipeline()
+ slices = [
+ Slice(id="slice-1", name="schema", repo="jwbron/schema"),
+ Slice(id="slice-2", name="consumer", repo="jwbron/consumer"),
+ ]
+ assert [_expected_gate_repo(s, pipeline) for s in slices] == [
+ "jwbron/schema",
+ "jwbron/consumer",
+ ]
+
+ def test_egg_slice_in_multi_repo_pipeline_scopes_to_egg(self):
+ """An egg slice keeps egg's gate even alongside other repos (baseline)."""
+ pipeline = Pipeline(
+ id="issue-3393",
+ repos=[
+ RepoSpec(repo="jwbron/egg", base_branch="main"),
+ RepoSpec(repo="jwbron/consumer", base_branch="develop"),
+ ],
+ )
+ s = Slice(id="slice-1", name="egg change", repo="jwbron/egg")
+ assert _expected_gate_repo(s, pipeline) == "jwbron/egg"
+
+ def test_n1_gate_scopes_to_single_repo(self):
+ """N=1 (bare single-repo) behaviour is identical to today."""
+ pipeline = Pipeline(id="issue-3393", repo="jwbron/egg", base_branch="main")
+ s = Slice(id="slice-1", name="only")
+ assert _expected_gate_repo(s, pipeline) == "jwbron/egg"
+
+
+class TestPerRepoReviewerDiffBase:
+ """The reviewer diff is scoped to the slice's repo base branch (AC-1)."""
+
+ @staticmethod
+ def _pipeline() -> Pipeline:
+ return Pipeline(
+ id="issue-3393",
+ repos=[
+ RepoSpec(repo="jwbron/schema", base_branch="main"), # primary
+ RepoSpec(repo="jwbron/consumer", base_branch="develop"),
+ ],
+ )
+
+ def test_diff_base_is_slice_repo_base_not_primary(self):
+ pipeline = self._pipeline()
+ s = Slice(id="slice-2", name="consumer migration", repo="jwbron/consumer")
+ # The consumer repo's base is ``develop``; the primary's is ``main``.
+ assert _expected_diff_base(s, pipeline) == "develop"
+ assert _expected_diff_base(s, pipeline) != pipeline.base_branch
+
+ def test_repoless_slice_diff_base_is_primary_base(self):
+ pipeline = self._pipeline()
+ s = Slice(id="slice-1", name="schema add")
+ assert _expected_diff_base(s, pipeline) == "main"
+
+ def test_each_slice_diffs_against_its_own_repo_base(self):
+ pipeline = self._pipeline()
+ slices = [
+ Slice(id="slice-1", name="schema", repo="jwbron/schema"),
+ Slice(id="slice-2", name="consumer", repo="jwbron/consumer"),
+ ]
+ assert [_expected_diff_base(s, pipeline) for s in slices] == ["main", "develop"]
+
+ def test_n1_diff_base_unchanged(self):
+ pipeline = Pipeline(id="issue-3393", repo="jwbron/egg", base_branch="release-1.0")
+ s = Slice(id="slice-1", name="only")
+ assert _expected_diff_base(s, pipeline) == "release-1.0"
+
+
+class TestPerRepoCheckCommandResolution:
+ """Check / lint commands resolve from the slice's repo conventions (AC-2)."""
+
+ @staticmethod
+ def _checks_by_repo() -> dict:
+ return {
+ "jwbron/egg": [{"name": "lint", "command": "make lint"}],
+ "jwbron/consumer": [{"name": "ci", "command": "npm test"}],
+ }
+
+ def test_check_commands_resolve_from_slice_repo(self):
+ pipeline = Pipeline(
+ id="issue-3393",
+ repos=[
+ RepoSpec(repo="jwbron/egg", base_branch="main"),
+ RepoSpec(repo="jwbron/consumer", base_branch="develop"),
+ ],
+ )
+ s = Slice(id="slice-2", name="consumer migration", repo="jwbron/consumer")
+ # The consumer slice gets the consumer repo's checks, NOT egg's make lint.
+ assert _resolve_checks_for_slice(s, pipeline, self._checks_by_repo()) == [
+ {"name": "ci", "command": "npm test"}
+ ]
+
+ def test_egg_slice_check_commands_match_baseline(self):
+ pipeline = Pipeline(
+ id="issue-3393",
+ repos=[
+ RepoSpec(repo="jwbron/egg", base_branch="main"),
+ RepoSpec(repo="jwbron/consumer", base_branch="develop"),
+ ],
+ )
+ s = Slice(id="slice-1", name="egg change", repo="jwbron/egg")
+ assert _resolve_checks_for_slice(s, pipeline, self._checks_by_repo()) == [
+ {"name": "lint", "command": "make lint"}
+ ]
+
+ def test_n1_check_commands_from_single_repo(self):
+ pipeline = Pipeline(id="issue-3393", repo="jwbron/egg", base_branch="main")
+ s = Slice(id="slice-1", name="only") # repo None → primary egg
+ assert _resolve_checks_for_slice(s, pipeline, self._checks_by_repo()) == [
+ {"name": "lint", "command": "make lint"}
+ ]
+
+
+# --- Real per-repo worktree resolver ----------------------------------------
+#
+# ``resolve_worktree_path(pipeline_id, repo_path)`` already selects the worktree
+# by the repo directory name, so the slice-6 test gate runs in the slice's repo
+# worktree iff its call site passes the *slice's* repo path. These tests pin
+# that the resolver picks the slice's own per-repo subdir (not the primary's)
+# when handed the slice's repo path — the mechanism the coder wires the gate
+# onto. Guarded because importing ``routes`` pulls in the Flask surface; the
+# ``sys.path`` / ``docker`` bootstrap ran in the slice-4 block above.
+
+_RESOLVE_WORKTREE_IMPORT_ERROR: str | None = None
+try: # pragma: no cover - exercised via skip path in stripped environments
+ import routes as _routes_pkg
+ from routes import resolve_worktree_path as _resolve_worktree_path
+except Exception as exc: # noqa: BLE001
+ _routes_pkg = None # type: ignore[assignment]
+ _resolve_worktree_path = None # type: ignore[assignment]
+ _RESOLVE_WORKTREE_IMPORT_ERROR = repr(exc)
+
+
+@pytest.mark.skipif(
+ _resolve_worktree_path is None,
+ reason=(
+ "routes.resolve_worktree_path import failed (Flask surface unavailable): "
+ f"{_RESOLVE_WORKTREE_IMPORT_ERROR}"
+ ),
+)
+class TestPerRepoWorktreeSelection:
+ """The worktree resolver selects the slice's own per-repo subdir (AC-1)."""
+
+ def test_slice_worktree_is_own_repo_subdir_not_primary(self, tmp_path, monkeypatch):
+ pid = "issue-3393"
+ wt_dir = tmp_path / pid
+ (wt_dir / "schema").mkdir(parents=True) # primary repo worktree
+ (wt_dir / "consumer").mkdir(parents=True) # secondary repo worktree
+ monkeypatch.setattr(_routes_pkg, "_WORKTREE_BASE_DIR", tmp_path)
+
+ # Handed the consumer slice's repo path, the resolver returns the
+ # consumer worktree — not the primary's.
+ consumer_wt = _resolve_worktree_path(pid, _Path("/home/egg/repos/consumer"))
+ primary_wt = _resolve_worktree_path(pid, _Path("/home/egg/repos/schema"))
+ assert consumer_wt == wt_dir / "consumer"
+ assert primary_wt == wt_dir / "schema"
+ assert consumer_wt != primary_wt
+
+ def test_n1_worktree_resolves_to_single_repo(self, tmp_path, monkeypatch):
+ pid = "issue-3393"
+ wt_dir = tmp_path / pid
+ (wt_dir / "egg").mkdir(parents=True)
+ monkeypatch.setattr(_routes_pkg, "_WORKTREE_BASE_DIR", tmp_path)
+ assert _resolve_worktree_path(pid, _Path("/home/egg/repos/egg")) == wt_dir / "egg"
+
+
+# --- Coder-owned scoping seam: skip until the slice-6 coder change integrates -
+#
+# Handed to the coder via a task-6-1 gap: a single scoping accessor
+# ``routes.pipelines._resolve_slice_gate_repo(slice, pipeline) -> str | None``
+# that returns the repo every implement-phase gate for the slice is scoped to
+# (== ``resolve_slice_repo(slice, pipeline)``). Skips with an explicit reason
+# until the parallel coder producer lands, then activates at convergence.
+
+_GATE_REPO_IMPORT_ERROR: str | None = None
+try: # pragma: no cover - exercised via skip path until coder lands
+ from routes.pipelines import _resolve_slice_gate_repo # type: ignore[attr-defined]
+except Exception as exc: # noqa: BLE001
+ _resolve_slice_gate_repo = None # type: ignore[assignment]
+ _GATE_REPO_IMPORT_ERROR = repr(exc)
+
+
+@pytest.mark.skipif(
+ _resolve_slice_gate_repo is None,
+ reason=(
+ "slice-6 coder helper ``routes.pipelines._resolve_slice_gate_repo`` not "
+ "yet integrated into the tester worktree (parallel producer); activates "
+ f"at convergence. import error: {_GATE_REPO_IMPORT_ERROR}"
+ ),
+)
+class TestSliceGateRepoAccessor:
+ """The coder's gate-scoping accessor matches the rule (AC-1/AC-2/AC-3)."""
+
+ def test_accessor_matches_rule_for_multi_repo(self):
+ pipeline = Pipeline(
+ id="issue-3393",
+ repos=[
+ RepoSpec(repo="jwbron/schema", base_branch="main"),
+ RepoSpec(repo="jwbron/consumer", base_branch="develop"),
+ ],
+ )
+ secondary = Slice(id="slice-2", name="consumer", repo="jwbron/consumer")
+ repoless = Slice(id="slice-1", name="schema")
+ assert (
+ _resolve_slice_gate_repo(secondary, pipeline)
+ == _expected_gate_repo(secondary, pipeline)
+ == "jwbron/consumer"
+ )
+ assert (
+ _resolve_slice_gate_repo(repoless, pipeline)
+ == _expected_gate_repo(repoless, pipeline)
+ == "jwbron/schema"
+ )
+
+ def test_accessor_n1_single_repo(self):
+ pipeline = Pipeline(id="issue-3393", repo="jwbron/egg", base_branch="main")
+ s = Slice(id="slice-1", name="only")
+ assert _resolve_slice_gate_repo(s, pipeline) == "jwbron/egg"