diff --git a/.egg-state/brc-history/3288-implement-slice-3.json b/.egg-state/brc-history/3288-implement-slice-3.json new file mode 100644 index 0000000000..948e17797a --- /dev/null +++ b/.egg-state/brc-history/3288-implement-slice-3.json @@ -0,0 +1,487 @@ +[ + { + "id": "d079018a-c615-48", + "pipeline_id": "issue-3288", + "from_role": "coder", + "to_role": "all", + "message_type": "HEARTBEAT", + "subject": "heartbeat: WORKING", + "body": "one-shot event handler action=propose (slice=slice-3)", + "metadata": { + "state": "WORKING", + "slice_id": "slice-3" + }, + "timestamp": "2026-06-26T21:35:59.314750+00:00", + "phase": "implement" + }, + { + "id": "aab412c0-3a45-4c", + "pipeline_id": "issue-3288", + "from_role": "tester", + "to_role": "all", + "message_type": "HEARTBEAT", + "subject": "heartbeat: WORKING", + "body": "one-shot event handler action=propose (slice=slice-3)", + "metadata": { + "state": "WORKING", + "slice_id": "slice-3" + }, + "timestamp": "2026-06-26T21:36:01.024029+00:00", + "phase": "implement" + }, + { + "id": "b4c2bf8c-0a3b-48", + "pipeline_id": "issue-3288", + "from_role": "documenter", + "to_role": "all", + "message_type": "HEARTBEAT", + "subject": "heartbeat: WORKING", + "body": "one-shot event handler action=propose (slice=slice-3)", + "metadata": { + "state": "WORKING", + "slice_id": "slice-3" + }, + "timestamp": "2026-06-26T21:36:02.134732+00:00", + "phase": "implement" + }, + { + "id": "895a933c-c2f7-49", + "pipeline_id": "issue-3288", + "from_role": "tester", + "to_role": "all", + "message_type": "CONSENSUS_PROPOSE", + "subject": "Proposal from tester", + "body": "No tester work in slice-3: all tasks are documenter doc-only rewrites of architecture pages; per the contract test plan these slices have no behavioral tests and are review-verified. No test suite or test artifact is impacted by this slice, so the tester producer proposes a no-op for this slice.", + "metadata": { + "payload": { + "summary": "No tester work in slice-3: all tasks are documenter doc-only rewrites of architecture pages; per the contract test plan these slices have no behavioral tests and are review-verified. No test suite or test artifact is impacted by this slice, so the tester producer proposes a no-op for this slice.", + "attestation": {}, + "artifacts": [], + "risk_considered": "", + "commit_sha": "", + "files_changed": [], + "tests_run": [], + "tasks_satisfied": [], + "no_changes_needed": true, + "no_changes_reason": "slice-3 (Total-refactor load-bearing architecture pages) contains only documenter-role tasks (task-3-1/3-2/3-3), all of which are doc-only rewrites of docs/architecture/*.md pages with no code-behavior change. The contract test plan explicitly states the doc/comment slices have no behavioral tests and are verified by review. No tester-assigned task exists in this slice and no test artifact is impacted, so the tester producer has no work here." + }, + "version": 1, + "commit_sha": "", + "slice_id": "slice-3" + }, + "timestamp": "2026-06-26T21:36:36.532835+00:00", + "phase": "implement" + }, + { + "id": "ee77ebc9-59c0-4b", + "pipeline_id": "issue-3288", + "from_role": "coder", + "to_role": "all", + "message_type": "CONSENSUS_PROPOSE", + "subject": "Proposal from coder", + "body": "Coder no-op for slice-3 (Total-refactor load-bearing architecture pages). All three slice-3 tasks (task-3-1, task-3-2, task-3-3) are assigned to the documenter role and touch only docs/architecture/*.md (gateway-auto-filter.md, coordination-state.md, slice-dag.md). There is no coder task in this slice, and the coder's gateway boundary blocks docs/** writes regardless. Working tree is clean on the slice-3 base. Coder .py de-ledgering work is scoped to slices 4/5/6, not slice-3.", + "metadata": { + "payload": { + "summary": "Coder no-op for slice-3 (Total-refactor load-bearing architecture pages). All three slice-3 tasks (task-3-1, task-3-2, task-3-3) are assigned to the documenter role and touch only docs/architecture/*.md (gateway-auto-filter.md, coordination-state.md, slice-dag.md). There is no coder task in this slice, and the coder's gateway boundary blocks docs/** writes regardless. Working tree is clean on the slice-3 base. Coder .py de-ledgering work is scoped to slices 4/5/6, not slice-3.", + "attestation": {}, + "artifacts": [], + "risk_considered": "", + "commit_sha": "", + "files_changed": [], + "tests_run": [], + "tasks_satisfied": [], + "no_changes_needed": true, + "no_changes_reason": "slice-3 is documenter-only: all tasks (task-3-1/3-2/3-3) are role=documenter editing docs/architecture/*.md. The coder has no assigned task and cannot write docs/**. Coder .py edits land in slices 4/5/6." + }, + "version": 1, + "commit_sha": "", + "slice_id": "slice-3" + }, + "timestamp": "2026-06-26T21:37:07.257158+00:00", + "phase": "implement" + }, + { + "id": "101c94e1-51bf-49", + "pipeline_id": "issue-3288", + "from_role": "documenter", + "to_role": "all", + "message_type": "CONSENSUS_PROPOSE", + "subject": "Proposal from documenter", + "body": "De-ledgered the three slice-3 architecture pages into current-state snapshots. gateway-auto-filter.md: removed the \"## What was removed\" inventory, the \"preserved for context\" framing, and the sections describing the removed silent-strip rewrite path (filtered_push.py per-commit rewrite + binary re-staging, verified deleted from the tree); the page now describes the live 403 restricted_path_modified rejection and the commit-authorship registry that still backs attribution (all cited symbols \u2014 partition_files_by_role, get_attributed_changed_files_in_push, _derive_push_denied_hint \u2014 confirmed present; gateway README anchor verified). coordination-state.md: replaced the \"landed in six slices\" slice-landing table with a direct statement of the three-clause invariant, collapsed the wipe-semantics Threat/Status table to the one live designed phase-boundary wipe (retired in-memory-backend row folded into a #3159 rationale note), and stripped a HITL-Q reference. slice-dag.md: replaced the \"used to run as a single monolithic team \u2026 replaces that\" opening with a current-model description plus a Known-limitations callout for the genuinely-unwired #2199 follow-ups; stripped TASK-N / decision-N / HITL-iteration / v6 / pre-#2137 ledger references throughout, converting \"Resolved design decisions (from refine phase)\" into a \"Design rationale\" section, while keeping the live \"slice\" domain term, issue links as rationale, and a verbatim live-code error string (plan_parser.py:1523) and the generated \"## What's in this PR\" PR header untouched. No inbound anchor links broken (the one cross-page link to #why-a-commit-authorship-registry still resolves).", + "metadata": { + "payload": { + "summary": "De-ledgered the three slice-3 architecture pages into current-state snapshots. gateway-auto-filter.md: removed the \"## What was removed\" inventory, the \"preserved for context\" framing, and the sections describing the removed silent-strip rewrite path (filtered_push.py per-commit rewrite + binary re-staging, verified deleted from the tree); the page now describes the live 403 restricted_path_modified rejection and the commit-authorship registry that still backs attribution (all cited symbols \u2014 partition_files_by_role, get_attributed_changed_files_in_push, _derive_push_denied_hint \u2014 confirmed present; gateway README anchor verified). coordination-state.md: replaced the \"landed in six slices\" slice-landing table with a direct statement of the three-clause invariant, collapsed the wipe-semantics Threat/Status table to the one live designed phase-boundary wipe (retired in-memory-backend row folded into a #3159 rationale note), and stripped a HITL-Q reference. slice-dag.md: replaced the \"used to run as a single monolithic team \u2026 replaces that\" opening with a current-model description plus a Known-limitations callout for the genuinely-unwired #2199 follow-ups; stripped TASK-N / decision-N / HITL-iteration / v6 / pre-#2137 ledger references throughout, converting \"Resolved design decisions (from refine phase)\" into a \"Design rationale\" section, while keeping the live \"slice\" domain term, issue links as rationale, and a verbatim live-code error string (plan_parser.py:1523) and the generated \"## What's in this PR\" PR header untouched. No inbound anchor links broken (the one cross-page link to #why-a-commit-authorship-registry still resolves).", + "attestation": {}, + "artifacts": [ + "docs/architecture/gateway-auto-filter.md", + "docs/architecture/coordination-state.md", + "docs/architecture/slice-dag.md" + ], + "risk_considered": "Verified current code state before rewriting (filtered_push.py is deleted; the live model is the 403 rejection) so the snapshot does not describe removed code. Preserved issue links that justify current design, the documenter gateway file boundary (docs/ only), and the BRC no-op propose path. Left verbatim live-code quotes and generated PR section headers intact to avoid making the docs inaccurate relative to the code. Checked that no internal or cross-page anchor links break from heading renames.", + "commit_sha": "ee79a2781adb3150d2d2a530b42f00136b905db4", + "files_changed": [ + "docs/architecture/gateway-auto-filter.md", + "docs/architecture/coordination-state.md", + "docs/architecture/slice-dag.md" + ], + "tests_run": [], + "tasks_satisfied": [ + "task-3-1", + "task-3-2", + "task-3-3" + ], + "no_changes_needed": false, + "no_changes_reason": "" + }, + "version": 1, + "commit_sha": "ee79a2781adb3150d2d2a530b42f00136b905db4", + "slice_id": "slice-3" + }, + "timestamp": "2026-06-26T21:47:11.762905+00:00", + "phase": "implement" + }, + { + "id": "783425c8-c986-48", + "pipeline_id": "issue-3288", + "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-3" + }, + "timestamp": "2026-06-26T21:47:11.764811+00:00", + "phase": "implement" + }, + { + "id": "950ea52f-a5bf-41", + "pipeline_id": "issue-3288", + "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-3" + }, + "timestamp": "2026-06-26T21:47:11.765573+00:00", + "phase": "implement" + }, + { + "id": "ecadee08-f459-45", + "pipeline_id": "issue-3288", + "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-3" + }, + "timestamp": "2026-06-26T21:47:17.306162+00:00", + "phase": "implement" + }, + { + "id": "7017f076-1265-4e", + "pipeline_id": "issue-3288", + "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-3" + }, + "timestamp": "2026-06-26T21:47:17.307902+00:00", + "phase": "implement" + }, + { + "id": "cafbf0d9-e183-43", + "pipeline_id": "issue-3288", + "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-3" + }, + "timestamp": "2026-06-26T21:47:18.811835+00:00", + "phase": "implement" + }, + { + "id": "997e3435-2d09-46", + "pipeline_id": "issue-3288", + "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-3" + }, + "timestamp": "2026-06-26T21:47:20.099592+00:00", + "phase": "implement" + }, + { + "id": "ee093004-c0ea-44", + "pipeline_id": "issue-3288", + "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-3" + }, + "timestamp": "2026-06-26T21:47:20.101200+00:00", + "phase": "implement" + }, + { + "id": "869a6fb1-2002-44", + "pipeline_id": "issue-3288", + "from_role": "reviewer_code", + "to_role": "all", + "message_type": "HEARTBEAT", + "subject": "heartbeat: WORKING", + "body": "one-shot event handler action=ack (slice=slice-3)", + "metadata": { + "state": "WORKING", + "slice_id": "slice-3" + }, + "timestamp": "2026-06-26T21:47:22.689909+00:00", + "phase": "implement" + }, + { + "id": "5f350583-2eed-49", + "pipeline_id": "issue-3288", + "from_role": "reviewer_contract", + "to_role": "all", + "message_type": "HEARTBEAT", + "subject": "heartbeat: WORKING", + "body": "one-shot event handler action=ack (slice=slice-3)", + "metadata": { + "state": "WORKING", + "slice_id": "slice-3" + }, + "timestamp": "2026-06-26T21:47:22.710306+00:00", + "phase": "implement" + }, + { + "id": "e3189aae-8ea5-4f", + "pipeline_id": "issue-3288", + "from_role": "reviewer_contract", + "to_role": "documenter", + "message_type": "CONSENSUS_NACK", + "subject": "NACK from reviewer_contract for documenter", + "body": "slice-3 is close \u2014 gateway-auto-filter.md (task-3-1) and coordination-state.md (task-3-2) fully satisfy their acceptance criteria (historical-record sections removed, status tables reduced to live rows, direct invariant stated; grep clean). Blocking only on two residual ledger/change-log references in slice-dag.md (task-3-3) that the binding directive and the task's own AC (\"'used to\u2026 replaces that\u2026' narration is gone\") require removed:\n\n1. ~line 498: \"(the v6 shared-branch shape)\" \u2014 strip the \"v6\" process-iteration tag (the commit message itself claims to strip v6 ledger references). Describe the shared-branch shape in present tense without the version tag.\n\n2. ~line 534: \"...not the pre-#3115 hard `title[:67] + \\\"...\\\"` cut that produced mid-word fragments\" \u2014 this is \"used to X, now Y\" change-log narration. Reframe to current behavior only (titles over 70 chars truncate at a word boundary), dropping the description of the removed hard-cut.\n\nNOT defects \u2014 please do NOT change these on re-propose:\n- line 137 \"issue #2137 plan TASK-2-3\" is a verbatim copy of the live error string in shared/egg_contracts/plan_parser.py:1522-1523 (a .py file outside your writable boundary); de-ledgering the doc would desync it from live output. Keep as-is.\n- line 546 \"pre-#3115 contracts\" is load-bearing back-compat rationale (explains the fallback for older on-disk contracts). Keep.\n\nFix the two items above and re-propose; I'll re-ACK.", + "metadata": { + "payload": { + "reason": "slice-3 is close \u2014 gateway-auto-filter.md (task-3-1) and coordination-state.md (task-3-2) fully satisfy their acceptance criteria (historical-record sections removed, status tables reduced to live rows, direct invariant stated; grep clean). Blocking only on two residual ledger/change-log references in slice-dag.md (task-3-3) that the binding directive and the task's own AC (\"'used to\u2026 replaces that\u2026' narration is gone\") require removed:\n\n1. ~line 498: \"(the v6 shared-branch shape)\" \u2014 strip the \"v6\" process-iteration tag (the commit message itself claims to strip v6 ledger references). Describe the shared-branch shape in present tense without the version tag.\n\n2. ~line 534: \"...not the pre-#3115 hard `title[:67] + \\\"...\\\"` cut that produced mid-word fragments\" \u2014 this is \"used to X, now Y\" change-log narration. Reframe to current behavior only (titles over 70 chars truncate at a word boundary), dropping the description of the removed hard-cut.\n\nNOT defects \u2014 please do NOT change these on re-propose:\n- line 137 \"issue #2137 plan TASK-2-3\" is a verbatim copy of the live error string in shared/egg_contracts/plan_parser.py:1522-1523 (a .py file outside your writable boundary); de-ledgering the doc would desync it from live output. Keep as-is.\n- line 546 \"pre-#3115 contracts\" is load-bearing back-compat rationale (explains the fallback for older on-disk contracts). Keep.\n\nFix the two items above and re-propose; I'll re-ACK.", + "artifact_references": [ + "docs/architecture/gateway-auto-filter.md", + "docs/architecture/coordination-state.md", + "docs/architecture/slice-dag.md" + ], + "nack_version": 1 + }, + "reason": "slice-3 is close \u2014 gateway-auto-filter.md (task-3-1) and coordination-state.md (task-3-2) fully satisfy their acceptance criteria (historical-record sections removed, status tables reduced to live rows, direct invariant stated; grep clean). Blocking only on two residual ledger/change-log references in slice-dag.md (task-3-3) that the binding directive and the task's own AC (\"'used to\u2026 replaces that\u2026' narration is gone\") require removed:\n\n1. ~line 498: \"(the v6 shared-branch shape)\" \u2014 strip the \"v6\" process-iteration tag (the commit message itself claims to strip v6 ledger references). Describe the shared-branch shape in present tense without the version tag.\n\n2. ~line 534: \"...not the pre-#3115 hard `title[:67] + \\\"...\\\"` cut that produced mid-word fragments\" \u2014 this is \"used to X, now Y\" change-log narration. Reframe to current behavior only (titles over 70 chars truncate at a word boundary), dropping the description of the removed hard-cut.\n\nNOT defects \u2014 please do NOT change these on re-propose:\n- line 137 \"issue #2137 plan TASK-2-3\" is a verbatim copy of the live error string in shared/egg_contracts/plan_parser.py:1522-1523 (a .py file outside your writable boundary); de-ledgering the doc would desync it from live output. Keep as-is.\n- line 546 \"pre-#3115 contracts\" is load-bearing back-compat rationale (explains the fallback for older on-disk contracts). Keep.\n\nFix the two items above and re-propose; I'll re-ACK.", + "revision_count": 1, + "slice_id": "slice-3" + }, + "timestamp": "2026-06-26T21:50:33.500741+00:00", + "phase": "implement" + }, + { + "id": "f5e3890a-e421-43", + "pipeline_id": "issue-3288", + "from_role": "documenter", + "to_role": "all", + "message_type": "CONSENSUS_PROPOSE", + "subject": "Proposal from documenter", + "body": "Address reviewer_contract NACK (v1) on task-3-3 (slice-dag.md). Removed the two residual ledger/change-log references the directive and AC require gone: (1) ~line 498 dropped the \"v6\" process-iteration tag \u2014 the shared per-slice integration branch is now described in present tense; (2) ~line 534 reframed title truncation to current behavior only (\"titles over 70 chars truncate at a word boundary\"), dropping the \"not the pre-#3115 hard cut that produced mid-word fragments\" change-log narration. Left untouched the two reviewer-confirmed keep-as-is items: line 137 \"issue #2137 plan TASK-2-3\" (verbatim copy of the live error string in plan_parser.py, a .py file outside my boundary) and line 545 \"pre-#3115 contracts\" (load-bearing back-compat rationale). grep confirms v6/title[:67]/mid-word references are gone; both keep items remain.\n\n**Adversarial re-review**\n\n**Your v2 review has TWO equal-weight mandates:**\n\n1. **Verify named v1 blockers were addressed** \u2014 confirm the producer fixed what you NACK'd.\n2. **Audit the delta since your last review (per REVIEWER-SYNC.md: `git log {last_reviewed_commit}..HEAD --not origin/{base_branch} -p` \u2014 the commits landed since the version you last verdicted, NOT the whole accumulated proposal surface) as a fresh reviewer** \u2014 ignore your v1 NACK history. Read that diff as if you'd never seen the prior version. Apply your lens (security threat-model, concurrency races, contract AC, line-by-line bugs, silent-fallback shapes \u2014 whichever your role owns) to the delta itself, not to whether your previous concerns were satisfied. **Mandate 2 is bounded to this delta** \u2014 it does NOT ask you to re-traverse the whole accumulated surface from earlier cycles; that work was amortized when you first reviewed those commits.\n\nBoth mandates have equal weight. If (1) passes but (2) finds new issues, you NACK. ACK requires both pass.\n\n**The named-blockers anchor is a known trap. Every reviewer lens has a mandate-2 in its own territory** \u2014 security has newly-introduced threat surfaces, concurrency has newly-introduced races, contract has newly-introduced AC drift, code has newly-introduced line-by-line bugs. The four issues that escaped PR #2724 to the GitHub bot were all of code-lens shape (`${ANSWER}` as bare Python, deprecated `datetime.utcnow()`, non-atomic write, bare `except: pass`) \u2014 the persistent reviewer correctly answered mandate 1 (\"did prior issues get fixed? yes\") and skipped mandate 2 (\"does this delta introduce new issues? actually yes\"). The shape generalizes: whatever your lens, this delta can introduce issues your prior NACK didn't name. Watching the producer deliver a targeted fix pulls strongly toward \"verify my fix-request landed \u2192 ACK.\" Recognize the pull and do mandate 2 anyway.\n\n**How to execute mandate 2:**\n\n- Read each new hunk as an operator who's about to copy-paste / run / integrate it. Would this code execute as written? Would these docs send a copy-paster down a working path?\n- Apply every rubric pass to the new hunks. New issues outside the scope of your prior NACK are blocking; your prior NACK does not bound this re-review.\n- **Fresh-reviewer simulation.** Before issuing your v2 verdict, ask: would a reviewer who has only seen this delta (the commits since your last review) with no NACK history ACK this? If you can't argue yes from that diff alone, NACK.\n- **External-bot anchor.** Imagine `egg-reviewer[bot]` reads only this delta (the commits since your last review) with no NACK context. What would it flag? Anything it'd flag, you should NACK first.\n\n**Your v2 verdict must enumerate both halves** so mandate 2 doesn't silently disappear from the record:\n\n- (a) Which v1 blockers you verified-fixed (mandate 1).\n- (b) What new issues you audited-and-did-not-find (mandate 2). Name the specific shapes you checked \u2014 not \"reviewed thoroughly,\" but \"checked for silent fallbacks, doc-snippet executability, API-deprecation, atomicity of file writes.\" If you can't enumerate (b), you haven't done mandate 2.\n\n**Re-reviews are cheap by design.** Your amortized context means the work is \"read the delta, apply your rubric, decide\" \u2014 minutes, not hours. NACK without hesitance; the orchestrator absorbs cycles. Two NACKs on the same producer where the second names new findings is the correct trajectory, not goalpost-moving. The downstream GitHub reviewer should find nothing in this delta. Anything it catches that lives in this cycle's diff is a miss attributable to this re-review.", + "metadata": { + "payload": { + "summary": "Address reviewer_contract NACK (v1) on task-3-3 (slice-dag.md). Removed the two residual ledger/change-log references the directive and AC require gone: (1) ~line 498 dropped the \"v6\" process-iteration tag \u2014 the shared per-slice integration branch is now described in present tense; (2) ~line 534 reframed title truncation to current behavior only (\"titles over 70 chars truncate at a word boundary\"), dropping the \"not the pre-#3115 hard cut that produced mid-word fragments\" change-log narration. Left untouched the two reviewer-confirmed keep-as-is items: line 137 \"issue #2137 plan TASK-2-3\" (verbatim copy of the live error string in plan_parser.py, a .py file outside my boundary) and line 545 \"pre-#3115 contracts\" (load-bearing back-compat rationale). grep confirms v6/title[:67]/mid-word references are gone; both keep items remain.", + "attestation": {}, + "artifacts": [ + "docs/architecture/slice-dag.md" + ], + "risk_considered": "Edits are doc-only and surgical (4 insertions, 5 deletions). No code or invariant references altered. Verified the two keep-as-is references the reviewer explicitly flagged remain in place to avoid desyncing the doc from live error output and dropping back-compat rationale.", + "commit_sha": "4dff12d51", + "files_changed": [ + "docs/architecture/slice-dag.md" + ], + "tests_run": [], + "tasks_satisfied": [ + "task-3-3" + ], + "no_changes_needed": false, + "no_changes_reason": "" + }, + "version": 2, + "commit_sha": "4dff12d51", + "slice_id": "slice-3" + }, + "timestamp": "2026-06-26T21:51:47.785756+00:00", + "phase": "implement" + }, + { + "id": "02fd3047-48f8-40", + "pipeline_id": "issue-3288", + "from_role": "reviewer_code", + "to_role": "documenter", + "message_type": "CONSENSUS_ACK", + "subject": "ACK from reviewer_code for documenter", + "body": "reviewer_code ACK at v2. Re-reviewed the ee79a27\u21924dff12d51 delta: two change-log narrations in slice-dag.md de-ledgered \u2014 the 'v6 shared-branch shape' tag now reads as current behavior ('returns the shared per-slice integration branch all roles on a slice push to'), and the 'pre-#3115 hard title[:67]+\"...\" cut that produced mid-word fragments' comparison collapsed to current behavior ('titles over that length truncate at a word boundary (#3115)'), keeping the #3115 rationale link. Both are accurate to current behavior, no regression. All v1-reviewed claims remain unchanged and were already verified against the live tree (filtered_push.py removed / 403 restricted_path_modified live; redis-only message store; _clear_concurrent_state in phases.py+pipelines.py; record_cycle not wired per TODO #2199). No correctness defects.", + "metadata": { + "payload": { + "artifact_references": [ + "docs/architecture/slice-dag.md", + "docs/architecture/gateway-auto-filter.md", + "docs/architecture/coordination-state.md" + ], + "reason": "reviewer_code ACK at v2. Re-reviewed the ee79a27\u21924dff12d51 delta: two change-log narrations in slice-dag.md de-ledgered \u2014 the 'v6 shared-branch shape' tag now reads as current behavior ('returns the shared per-slice integration branch all roles on a slice push to'), and the 'pre-#3115 hard title[:67]+\"...\" cut that produced mid-word fragments' comparison collapsed to current behavior ('titles over that length truncate at a word boundary (#3115)'), keeping the #3115 rationale link. Both are accurate to current behavior, no regression. All v1-reviewed claims remain unchanged and were already verified against the live tree (filtered_push.py removed / 403 restricted_path_modified live; redis-only message store; _clear_concurrent_state in phases.py+pipelines.py; record_cycle not wired per TODO #2199). No correctness defects.", + "ack_version": 2, + "attestation": { + "files_reviewed": [ + "docs/architecture/slice-dag.md", + "docs/architecture/gateway-auto-filter.md", + "docs/architecture/coordination-state.md" + ], + "issues_found": 0, + "delta_reviewed": "ee79a27..4dff12d51", + "claims_verified": [ + "v6 tag removed, current-behavior wording accurate", + "pre-#3115 title-cut narration removed, #3115 rationale kept", + "all v1 code-accuracy claims unchanged" + ] + } + }, + "version": 2, + "slice_id": "slice-3" + }, + "timestamp": "2026-06-26T21:52:19.176845+00:00", + "phase": "implement" + }, + { + "id": "a6a6b0ab-f105-4e", + "pipeline_id": "issue-3288", + "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-3" + }, + "timestamp": "2026-06-26T21:52:19.520504+00:00", + "phase": "implement" + }, + { + "id": "bb0863dc-1255-4a", + "pipeline_id": "issue-3288", + "from_role": "reviewer_contract", + "to_role": "documenter", + "message_type": "CONSENSUS_ACK", + "subject": "ACK from reviewer_contract for documenter", + "body": "Re-review of v2 (4dff12d51) confirms both prior NACK items on task-3-3 (slice-dag.md) are resolved: (1) the 'v6 shared-branch shape' process-iteration tag is replaced with present-tense prose ('returns the shared per-slice integration branch all roles on a slice push to'); (2) the 'not the pre-#3115 hard title[:67]+\"...\" cut' change-log narration is removed, reframed to current behavior only ('titles over that length truncate at a word boundary (#3115)'). The #3115 anchor survives as a justification link (permitted). The two flagged NOT-defects (line 137 verbatim plan_parser error string, line 546 back-compat rationale) were correctly left untouched. Diff is surgical \u2014 only the two requested hunks changed. slice-3 fully satisfies all acceptance criteria across task-3-1 (gateway-auto-filter.md), task-3-2 (coordination-state.md), task-3-3 (slice-dag.md): historical-record sections removed, ledger/process references stripped, change-log narration rewritten to current-state.", + "metadata": { + "payload": { + "artifact_references": [ + "docs/architecture/slice-dag.md" + ], + "reason": "Re-review of v2 (4dff12d51) confirms both prior NACK items on task-3-3 (slice-dag.md) are resolved: (1) the 'v6 shared-branch shape' process-iteration tag is replaced with present-tense prose ('returns the shared per-slice integration branch all roles on a slice push to'); (2) the 'not the pre-#3115 hard title[:67]+\"...\" cut' change-log narration is removed, reframed to current behavior only ('titles over that length truncate at a word boundary (#3115)'). The #3115 anchor survives as a justification link (permitted). The two flagged NOT-defects (line 137 verbatim plan_parser error string, line 546 back-compat rationale) were correctly left untouched. Diff is surgical \u2014 only the two requested hunks changed. slice-3 fully satisfies all acceptance criteria across task-3-1 (gateway-auto-filter.md), task-3-2 (coordination-state.md), task-3-3 (slice-dag.md): historical-record sections removed, ledger/process references stripped, change-log narration rewritten to current-state.", + "ack_version": 2, + "attestation": { + "tasks_verified": [ + "task-3-1", + "task-3-2", + "task-3-3" + ] + } + }, + "version": 2, + "slice_id": "slice-3" + }, + "timestamp": "2026-06-26T21:52:20.642606+00:00", + "phase": "implement" + }, + { + "id": "6497ed04-77d1-47", + "pipeline_id": "issue-3288", + "from_role": "orchestrator", + "to_role": "documenter", + "message_type": "STATUS", + "subject": "Ready to confirm \u2014 all confirm preconditions satisfied", + "body": "Your proposal (version 2) 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": 2, + "slice_id": "slice-3" + }, + "timestamp": "2026-06-26T21:52:20.644766+00:00", + "phase": "implement" + }, + { + "id": "8c14e074-5d14-43", + "pipeline_id": "issue-3288", + "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-3" + }, + "timestamp": "2026-06-26T21:52:24.559599+00:00", + "phase": "implement" + }, + { + "id": "b9cf44b8-a186-48", + "pipeline_id": "issue-3288", + "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-3" + }, + "timestamp": "2026-06-26T21:52:24.564276+00:00", + "phase": "implement" + }, + { + "id": "42533e73-9006-45", + "pipeline_id": "issue-3288", + "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-3" + }, + "timestamp": "2026-06-26T21:52:24.564791+00:00", + "phase": "implement" + }, + { + "id": "57c5a4c0-8aa3-45", + "pipeline_id": "issue-3288", + "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-3" + }, + "timestamp": "2026-06-26T21:52:24.566002+00:00", + "phase": "implement" + } +] \ No newline at end of file diff --git a/.egg-state/brc-history/3288-implement-slice-3.md b/.egg-state/brc-history/3288-implement-slice-3.md new file mode 100644 index 0000000000..3fd3050f6b --- /dev/null +++ b/.egg-state/brc-history/3288-implement-slice-3.md @@ -0,0 +1,543 @@ +# BRC Consensus History — implement phase, slice-3 + +Generated: 2026-06-26T21:52:24Z +Pipeline: issue-3288 +Slice: slice-3 + +### [2026-06-26T21:35:59Z] coder (HEARTBEAT): heartbeat: WORKING + +one-shot event handler action=propose (slice=slice-3) + +````yaml +id: d079018a-c615-48 +phase: implement +metadata: + state: WORKING + slice_id: slice-3 +```` + +### [2026-06-26T21:36:01Z] tester (HEARTBEAT): heartbeat: WORKING + +one-shot event handler action=propose (slice=slice-3) + +````yaml +id: aab412c0-3a45-4c +phase: implement +metadata: + state: WORKING + slice_id: slice-3 +```` + +### [2026-06-26T21:36:02Z] documenter (HEARTBEAT): heartbeat: WORKING + +one-shot event handler action=propose (slice=slice-3) + +````yaml +id: b4c2bf8c-0a3b-48 +phase: implement +metadata: + state: WORKING + slice_id: slice-3 +```` + +### [2026-06-26T21:36:36Z] tester (CONSENSUS_PROPOSE): Proposal from tester + +No tester work in slice-3: all tasks are documenter doc-only rewrites of architecture pages; per the contract test plan these slices have no behavioral tests and are review-verified. No test suite or test artifact is impacted by this slice, so the tester producer proposes a no-op for this slice. + +````yaml +id: 895a933c-c2f7-49 +phase: implement +metadata: + payload: + summary: 'No tester work in slice-3: all tasks are documenter doc-only rewrites + of architecture pages; per the contract test plan these slices have no behavioral + tests and are review-verified. No test suite or test artifact is impacted by + this slice, so the tester producer proposes a no-op for this slice.' + attestation: {} + artifacts: [] + risk_considered: '' + commit_sha: '' + files_changed: [] + tests_run: [] + tasks_satisfied: [] + no_changes_needed: true + no_changes_reason: slice-3 (Total-refactor load-bearing architecture pages) contains + only documenter-role tasks (task-3-1/3-2/3-3), all of which are doc-only rewrites + of docs/architecture/*.md pages with no code-behavior change. The contract test + plan explicitly states the doc/comment slices have no behavioral tests and are + verified by review. No tester-assigned task exists in this slice and no test + artifact is impacted, so the tester producer has no work here. + version: 1 + commit_sha: '' + slice_id: slice-3 +```` + +### [2026-06-26T21:37:07Z] coder (CONSENSUS_PROPOSE): Proposal from coder + +Coder no-op for slice-3 (Total-refactor load-bearing architecture pages). All three slice-3 tasks (task-3-1, task-3-2, task-3-3) are assigned to the documenter role and touch only docs/architecture/*.md (gateway-auto-filter.md, coordination-state.md, slice-dag.md). There is no coder task in this slice, and the coder's gateway boundary blocks docs/** writes regardless. Working tree is clean on the slice-3 base. Coder .py de-ledgering work is scoped to slices 4/5/6, not slice-3. + +````yaml +id: ee77ebc9-59c0-4b +phase: implement +metadata: + payload: + summary: Coder no-op for slice-3 (Total-refactor load-bearing architecture pages). + All three slice-3 tasks (task-3-1, task-3-2, task-3-3) are assigned to the documenter + role and touch only docs/architecture/*.md (gateway-auto-filter.md, coordination-state.md, + slice-dag.md). There is no coder task in this slice, and the coder's gateway + boundary blocks docs/** writes regardless. Working tree is clean on the slice-3 + base. Coder .py de-ledgering work is scoped to slices 4/5/6, not slice-3. + attestation: {} + artifacts: [] + risk_considered: '' + commit_sha: '' + files_changed: [] + tests_run: [] + tasks_satisfied: [] + no_changes_needed: true + no_changes_reason: 'slice-3 is documenter-only: all tasks (task-3-1/3-2/3-3) are + role=documenter editing docs/architecture/*.md. The coder has no assigned task + and cannot write docs/**. Coder .py edits land in slices 4/5/6.' + version: 1 + commit_sha: '' + slice_id: slice-3 +```` + +### [2026-06-26T21:47:11Z] documenter (CONSENSUS_PROPOSE): Proposal from documenter + +De-ledgered the three slice-3 architecture pages into current-state snapshots. gateway-auto-filter.md: removed the "## What was removed" inventory, the "preserved for context" framing, and the sections describing the removed silent-strip rewrite path (filtered_push.py per-commit rewrite + binary re-staging, verified deleted from the tree); the page now describes the live 403 restricted_path_modified rejection and the commit-authorship registry that still backs attribution (all cited symbols — partition_files_by_role, get_attributed_changed_files_in_push, _derive_push_denied_hint — confirmed present; gateway README anchor verified). coordination-state.md: replaced the "landed in six slices" slice-landing table with a direct statement of the three-clause invariant, collapsed the wipe-semantics Threat/Status table to the one live designed phase-boundary wipe (retired in-memory-backend row folded into a #3159 rationale note), and stripped a HITL-Q reference. slice-dag.md: replaced the "used to run as a single monolithic team … replaces that" opening with a current-model description plus a Known-limitations callout for the genuinely-unwired #2199 follow-ups; stripped TASK-N / decision-N / HITL-iteration / v6 / pre-#2137 ledger references throughout, converting "Resolved design decisions (from refine phase)" into a "Design rationale" section, while keeping the live "slice" domain term, issue links as rationale, and a verbatim live-code error string (plan_parser.py:1523) and the generated "## What's in this PR" PR header untouched. No inbound anchor links broken (the one cross-page link to #why-a-commit-authorship-registry still resolves). + +````yaml +id: 101c94e1-51bf-49 +phase: implement +metadata: + payload: + summary: "De-ledgered the three slice-3 architecture pages into current-state\ + \ snapshots. gateway-auto-filter.md: removed the \"## What was removed\" inventory,\ + \ the \"preserved for context\" framing, and the sections describing the removed\ + \ silent-strip rewrite path (filtered_push.py per-commit rewrite + binary re-staging,\ + \ verified deleted from the tree); the page now describes the live 403 restricted_path_modified\ + \ rejection and the commit-authorship registry that still backs attribution\ + \ (all cited symbols \u2014 partition_files_by_role, get_attributed_changed_files_in_push,\ + \ _derive_push_denied_hint \u2014 confirmed present; gateway README anchor verified).\ + \ coordination-state.md: replaced the \"landed in six slices\" slice-landing\ + \ table with a direct statement of the three-clause invariant, collapsed the\ + \ wipe-semantics Threat/Status table to the one live designed phase-boundary\ + \ wipe (retired in-memory-backend row folded into a #3159 rationale note), and\ + \ stripped a HITL-Q reference. slice-dag.md: replaced the \"used to run as a\ + \ single monolithic team \u2026 replaces that\" opening with a current-model\ + \ description plus a Known-limitations callout for the genuinely-unwired #2199\ + \ follow-ups; stripped TASK-N / decision-N / HITL-iteration / v6 / pre-#2137\ + \ ledger references throughout, converting \"Resolved design decisions (from\ + \ refine phase)\" into a \"Design rationale\" section, while keeping the live\ + \ \"slice\" domain term, issue links as rationale, and a verbatim live-code\ + \ error string (plan_parser.py:1523) and the generated \"## What's in this PR\"\ + \ PR header untouched. No inbound anchor links broken (the one cross-page link\ + \ to #why-a-commit-authorship-registry still resolves)." + attestation: {} + artifacts: + - docs/architecture/gateway-auto-filter.md + - docs/architecture/coordination-state.md + - docs/architecture/slice-dag.md + risk_considered: Verified current code state before rewriting (filtered_push.py + is deleted; the live model is the 403 rejection) so the snapshot does not describe + removed code. Preserved issue links that justify current design, the documenter + gateway file boundary (docs/ only), and the BRC no-op propose path. Left verbatim + live-code quotes and generated PR section headers intact to avoid making the + docs inaccurate relative to the code. Checked that no internal or cross-page + anchor links break from heading renames. + commit_sha: ee79a2781adb3150d2d2a530b42f00136b905db4 + files_changed: + - docs/architecture/gateway-auto-filter.md + - docs/architecture/coordination-state.md + - docs/architecture/slice-dag.md + tests_run: [] + tasks_satisfied: + - task-3-1 + - task-3-2 + - task-3-3 + no_changes_needed: false + no_changes_reason: '' + version: 1 + commit_sha: ee79a2781adb3150d2d2a530b42f00136b905db4 + slice_id: slice-3 +```` + +### [2026-06-26T21:47:11Z] 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: 783425c8-c986-48 +phase: implement +metadata: + ready_to_confirm: true + version: 1 + slice_id: slice-3 +```` + +### [2026-06-26T21:47:11Z] 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: 950ea52f-a5bf-41 +phase: implement +metadata: + ready_to_confirm: true + version: 1 + slice_id: slice-3 +```` + +### [2026-06-26T21:47:17Z] coder (CONSENSUS_CONFIRMED): Consensus confirmed by coder + +orchestrator-side confirm (#3064 event loop) + +````yaml +id: ecadee08-f459-45 +phase: implement +metadata: + slice_id: slice-3 +```` + +### [2026-06-26T21:47:17Z] tester (CONSENSUS_CONFIRMED): Consensus confirmed by tester + +orchestrator-side confirm (#3064 event loop) + +````yaml +id: 7017f076-1265-4e +phase: implement +metadata: + slice_id: slice-3 +```` + +### [2026-06-26T21:47:18Z] reviewer_code_holistic (CONSENSUS_CONFIRMED): Consensus confirmed by reviewer_code_holistic + +orchestrator-side confirm (#3064 event loop) + +````yaml +id: cafbf0d9-e183-43 +phase: implement +metadata: + slice_id: slice-3 +```` + +### [2026-06-26T21:47:20Z] reviewer_security (CONSENSUS_CONFIRMED): Consensus confirmed by reviewer_security + +orchestrator-side confirm (#3064 event loop) + +````yaml +id: 997e3435-2d09-46 +phase: implement +metadata: + slice_id: slice-3 +```` + +### [2026-06-26T21:47:20Z] reviewer_concurrency (CONSENSUS_CONFIRMED): Consensus confirmed by reviewer_concurrency + +orchestrator-side confirm (#3064 event loop) + +````yaml +id: ee093004-c0ea-44 +phase: implement +metadata: + slice_id: slice-3 +```` + +### [2026-06-26T21:47:22Z] reviewer_code (HEARTBEAT): heartbeat: WORKING + +one-shot event handler action=ack (slice=slice-3) + +````yaml +id: 869a6fb1-2002-44 +phase: implement +metadata: + state: WORKING + slice_id: slice-3 +```` + +### [2026-06-26T21:47:22Z] reviewer_contract (HEARTBEAT): heartbeat: WORKING + +one-shot event handler action=ack (slice=slice-3) + +````yaml +id: 5f350583-2eed-49 +phase: implement +metadata: + state: WORKING + slice_id: slice-3 +```` + +### [2026-06-26T21:50:33Z] reviewer_contract → documenter (CONSENSUS_NACK): NACK from reviewer_contract for documenter + +slice-3 is close — gateway-auto-filter.md (task-3-1) and coordination-state.md (task-3-2) fully satisfy their acceptance criteria (historical-record sections removed, status tables reduced to live rows, direct invariant stated; grep clean). Blocking only on two residual ledger/change-log references in slice-dag.md (task-3-3) that the binding directive and the task's own AC ("'used to… replaces that…' narration is gone") require removed: + +1. ~line 498: "(the v6 shared-branch shape)" — strip the "v6" process-iteration tag (the commit message itself claims to strip v6 ledger references). Describe the shared-branch shape in present tense without the version tag. + +2. ~line 534: "...not the pre-#3115 hard `title[:67] + \"...\"` cut that produced mid-word fragments" — this is "used to X, now Y" change-log narration. Reframe to current behavior only (titles over 70 chars truncate at a word boundary), dropping the description of the removed hard-cut. + +NOT defects — please do NOT change these on re-propose: +- line 137 "issue #2137 plan TASK-2-3" is a verbatim copy of the live error string in shared/egg_contracts/plan_parser.py:1522-1523 (a .py file outside your writable boundary); de-ledgering the doc would desync it from live output. Keep as-is. +- line 546 "pre-#3115 contracts" is load-bearing back-compat rationale (explains the fallback for older on-disk contracts). Keep. + +Fix the two items above and re-propose; I'll re-ACK. + +````yaml +id: e3189aae-8ea5-4f +phase: implement +metadata: + payload: + reason: "slice-3 is close \u2014 gateway-auto-filter.md (task-3-1) and coordination-state.md\ + \ (task-3-2) fully satisfy their acceptance criteria (historical-record sections\ + \ removed, status tables reduced to live rows, direct invariant stated; grep\ + \ clean). Blocking only on two residual ledger/change-log references in slice-dag.md\ + \ (task-3-3) that the binding directive and the task's own AC (\"'used to\u2026\ + \ replaces that\u2026' narration is gone\") require removed:\n\n1. ~line 498:\ + \ \"(the v6 shared-branch shape)\" \u2014 strip the \"v6\" process-iteration\ + \ tag (the commit message itself claims to strip v6 ledger references). Describe\ + \ the shared-branch shape in present tense without the version tag.\n\n2. ~line\ + \ 534: \"...not the pre-#3115 hard `title[:67] + \\\"...\\\"` cut that produced\ + \ mid-word fragments\" \u2014 this is \"used to X, now Y\" change-log narration.\ + \ Reframe to current behavior only (titles over 70 chars truncate at a word\ + \ boundary), dropping the description of the removed hard-cut.\n\nNOT defects\ + \ \u2014 please do NOT change these on re-propose:\n- line 137 \"issue #2137\ + \ plan TASK-2-3\" is a verbatim copy of the live error string in shared/egg_contracts/plan_parser.py:1522-1523\ + \ (a .py file outside your writable boundary); de-ledgering the doc would desync\ + \ it from live output. Keep as-is.\n- line 546 \"pre-#3115 contracts\" is load-bearing\ + \ back-compat rationale (explains the fallback for older on-disk contracts).\ + \ Keep.\n\nFix the two items above and re-propose; I'll re-ACK." + artifact_references: + - docs/architecture/gateway-auto-filter.md + - docs/architecture/coordination-state.md + - docs/architecture/slice-dag.md + nack_version: 1 + reason: "slice-3 is close \u2014 gateway-auto-filter.md (task-3-1) and coordination-state.md\ + \ (task-3-2) fully satisfy their acceptance criteria (historical-record sections\ + \ removed, status tables reduced to live rows, direct invariant stated; grep clean).\ + \ Blocking only on two residual ledger/change-log references in slice-dag.md (task-3-3)\ + \ that the binding directive and the task's own AC (\"'used to\u2026 replaces\ + \ that\u2026' narration is gone\") require removed:\n\n1. ~line 498: \"(the v6\ + \ shared-branch shape)\" \u2014 strip the \"v6\" process-iteration tag (the commit\ + \ message itself claims to strip v6 ledger references). Describe the shared-branch\ + \ shape in present tense without the version tag.\n\n2. ~line 534: \"...not the\ + \ pre-#3115 hard `title[:67] + \\\"...\\\"` cut that produced mid-word fragments\"\ + \ \u2014 this is \"used to X, now Y\" change-log narration. Reframe to current\ + \ behavior only (titles over 70 chars truncate at a word boundary), dropping the\ + \ description of the removed hard-cut.\n\nNOT defects \u2014 please do NOT change\ + \ these on re-propose:\n- line 137 \"issue #2137 plan TASK-2-3\" is a verbatim\ + \ copy of the live error string in shared/egg_contracts/plan_parser.py:1522-1523\ + \ (a .py file outside your writable boundary); de-ledgering the doc would desync\ + \ it from live output. Keep as-is.\n- line 546 \"pre-#3115 contracts\" is load-bearing\ + \ back-compat rationale (explains the fallback for older on-disk contracts). Keep.\n\ + \nFix the two items above and re-propose; I'll re-ACK." + revision_count: 1 + slice_id: slice-3 +```` + +### [2026-06-26T21:51:47Z] documenter (CONSENSUS_PROPOSE): Proposal from documenter + +Address reviewer_contract NACK (v1) on task-3-3 (slice-dag.md). Removed the two residual ledger/change-log references the directive and AC require gone: (1) ~line 498 dropped the "v6" process-iteration tag — the shared per-slice integration branch is now described in present tense; (2) ~line 534 reframed title truncation to current behavior only ("titles over 70 chars truncate at a word boundary"), dropping the "not the pre-#3115 hard cut that produced mid-word fragments" change-log narration. Left untouched the two reviewer-confirmed keep-as-is items: line 137 "issue #2137 plan TASK-2-3" (verbatim copy of the live error string in plan_parser.py, a .py file outside my boundary) and line 545 "pre-#3115 contracts" (load-bearing back-compat rationale). grep confirms v6/title[:67]/mid-word references are gone; both keep items remain. + +**Adversarial re-review** + +**Your v2 review has TWO equal-weight mandates:** + +1. **Verify named v1 blockers were addressed** — confirm the producer fixed what you NACK'd. +2. **Audit the delta since your last review (per REVIEWER-SYNC.md: `git log {last_reviewed_commit}..HEAD --not origin/{base_branch} -p` — the commits landed since the version you last verdicted, NOT the whole accumulated proposal surface) as a fresh reviewer** — ignore your v1 NACK history. Read that diff as if you'd never seen the prior version. Apply your lens (security threat-model, concurrency races, contract AC, line-by-line bugs, silent-fallback shapes — whichever your role owns) to the delta itself, not to whether your previous concerns were satisfied. **Mandate 2 is bounded to this delta** — it does NOT ask you to re-traverse the whole accumulated surface from earlier cycles; that work was amortized when you first reviewed those commits. + +Both mandates have equal weight. If (1) passes but (2) finds new issues, you NACK. ACK requires both pass. + +**The named-blockers anchor is a known trap. Every reviewer lens has a mandate-2 in its own territory** — security has newly-introduced threat surfaces, concurrency has newly-introduced races, contract has newly-introduced AC drift, code has newly-introduced line-by-line bugs. The four issues that escaped PR #2724 to the GitHub bot were all of code-lens shape (`${ANSWER}` as bare Python, deprecated `datetime.utcnow()`, non-atomic write, bare `except: pass`) — the persistent reviewer correctly answered mandate 1 ("did prior issues get fixed? yes") and skipped mandate 2 ("does this delta introduce new issues? actually yes"). The shape generalizes: whatever your lens, this delta can introduce issues your prior NACK didn't name. Watching the producer deliver a targeted fix pulls strongly toward "verify my fix-request landed → ACK." Recognize the pull and do mandate 2 anyway. + +**How to execute mandate 2:** + +- Read each new hunk as an operator who's about to copy-paste / run / integrate it. Would this code execute as written? Would these docs send a copy-paster down a working path? +- Apply every rubric pass to the new hunks. New issues outside the scope of your prior NACK are blocking; your prior NACK does not bound this re-review. +- **Fresh-reviewer simulation.** Before issuing your v2 verdict, ask: would a reviewer who has only seen this delta (the commits since your last review) with no NACK history ACK this? If you can't argue yes from that diff alone, NACK. +- **External-bot anchor.** Imagine `egg-reviewer[bot]` reads only this delta (the commits since your last review) with no NACK context. What would it flag? Anything it'd flag, you should NACK first. + +**Your v2 verdict must enumerate both halves** so mandate 2 doesn't silently disappear from the record: + +- (a) Which v1 blockers you verified-fixed (mandate 1). +- (b) What new issues you audited-and-did-not-find (mandate 2). Name the specific shapes you checked — not "reviewed thoroughly," but "checked for silent fallbacks, doc-snippet executability, API-deprecation, atomicity of file writes." If you can't enumerate (b), you haven't done mandate 2. + +**Re-reviews are cheap by design.** Your amortized context means the work is "read the delta, apply your rubric, decide" — minutes, not hours. NACK without hesitance; the orchestrator absorbs cycles. Two NACKs on the same producer where the second names new findings is the correct trajectory, not goalpost-moving. The downstream GitHub reviewer should find nothing in this delta. Anything it catches that lives in this cycle's diff is a miss attributable to this re-review. + +````yaml +id: f5e3890a-e421-43 +phase: implement +metadata: + payload: + summary: "Address reviewer_contract NACK (v1) on task-3-3 (slice-dag.md). Removed\ + \ the two residual ledger/change-log references the directive and AC require\ + \ gone: (1) ~line 498 dropped the \"v6\" process-iteration tag \u2014 the shared\ + \ per-slice integration branch is now described in present tense; (2) ~line\ + \ 534 reframed title truncation to current behavior only (\"titles over 70 chars\ + \ truncate at a word boundary\"), dropping the \"not the pre-#3115 hard cut\ + \ that produced mid-word fragments\" change-log narration. Left untouched the\ + \ two reviewer-confirmed keep-as-is items: line 137 \"issue #2137 plan TASK-2-3\"\ + \ (verbatim copy of the live error string in plan_parser.py, a .py file outside\ + \ my boundary) and line 545 \"pre-#3115 contracts\" (load-bearing back-compat\ + \ rationale). grep confirms v6/title[:67]/mid-word references are gone; both\ + \ keep items remain." + attestation: {} + artifacts: + - docs/architecture/slice-dag.md + risk_considered: Edits are doc-only and surgical (4 insertions, 5 deletions). + No code or invariant references altered. Verified the two keep-as-is references + the reviewer explicitly flagged remain in place to avoid desyncing the doc from + live error output and dropping back-compat rationale. + commit_sha: 4dff12d51 + files_changed: + - docs/architecture/slice-dag.md + tests_run: [] + tasks_satisfied: + - task-3-3 + no_changes_needed: false + no_changes_reason: '' + version: 2 + commit_sha: 4dff12d51 + slice_id: slice-3 +```` + +### [2026-06-26T21:52:19Z] reviewer_code → documenter (CONSENSUS_ACK): ACK from reviewer_code for documenter + +reviewer_code ACK at v2. Re-reviewed the ee79a27→4dff12d51 delta: two change-log narrations in slice-dag.md de-ledgered — the 'v6 shared-branch shape' tag now reads as current behavior ('returns the shared per-slice integration branch all roles on a slice push to'), and the 'pre-#3115 hard title[:67]+"..." cut that produced mid-word fragments' comparison collapsed to current behavior ('titles over that length truncate at a word boundary (#3115)'), keeping the #3115 rationale link. Both are accurate to current behavior, no regression. All v1-reviewed claims remain unchanged and were already verified against the live tree (filtered_push.py removed / 403 restricted_path_modified live; redis-only message store; _clear_concurrent_state in phases.py+pipelines.py; record_cycle not wired per TODO #2199). No correctness defects. + +````yaml +id: 02fd3047-48f8-40 +phase: implement +metadata: + payload: + artifact_references: + - docs/architecture/slice-dag.md + - docs/architecture/gateway-auto-filter.md + - docs/architecture/coordination-state.md + reason: "reviewer_code ACK at v2. Re-reviewed the ee79a27\u21924dff12d51 delta:\ + \ two change-log narrations in slice-dag.md de-ledgered \u2014 the 'v6 shared-branch\ + \ shape' tag now reads as current behavior ('returns the shared per-slice integration\ + \ branch all roles on a slice push to'), and the 'pre-#3115 hard title[:67]+\"\ + ...\" cut that produced mid-word fragments' comparison collapsed to current\ + \ behavior ('titles over that length truncate at a word boundary (#3115)'),\ + \ keeping the #3115 rationale link. Both are accurate to current behavior, no\ + \ regression. All v1-reviewed claims remain unchanged and were already verified\ + \ against the live tree (filtered_push.py removed / 403 restricted_path_modified\ + \ live; redis-only message store; _clear_concurrent_state in phases.py+pipelines.py;\ + \ record_cycle not wired per TODO #2199). No correctness defects." + ack_version: 2 + attestation: + files_reviewed: + - docs/architecture/slice-dag.md + - docs/architecture/gateway-auto-filter.md + - docs/architecture/coordination-state.md + issues_found: 0 + delta_reviewed: ee79a27..4dff12d51 + claims_verified: + - v6 tag removed, current-behavior wording accurate + - 'pre-#3115 title-cut narration removed, #3115 rationale kept' + - all v1 code-accuracy claims unchanged + version: 2 + slice_id: slice-3 +```` + +### [2026-06-26T21:52:19Z] reviewer_code (CONSENSUS_CONFIRMED): Consensus confirmed by reviewer_code + +orchestrator-side confirm (#3064 event loop) + +````yaml +id: a6a6b0ab-f105-4e +phase: implement +metadata: + slice_id: slice-3 +```` + +### [2026-06-26T21:52:20Z] reviewer_contract → documenter (CONSENSUS_ACK): ACK from reviewer_contract for documenter + +Re-review of v2 (4dff12d51) confirms both prior NACK items on task-3-3 (slice-dag.md) are resolved: (1) the 'v6 shared-branch shape' process-iteration tag is replaced with present-tense prose ('returns the shared per-slice integration branch all roles on a slice push to'); (2) the 'not the pre-#3115 hard title[:67]+"..." cut' change-log narration is removed, reframed to current behavior only ('titles over that length truncate at a word boundary (#3115)'). The #3115 anchor survives as a justification link (permitted). The two flagged NOT-defects (line 137 verbatim plan_parser error string, line 546 back-compat rationale) were correctly left untouched. Diff is surgical — only the two requested hunks changed. slice-3 fully satisfies all acceptance criteria across task-3-1 (gateway-auto-filter.md), task-3-2 (coordination-state.md), task-3-3 (slice-dag.md): historical-record sections removed, ledger/process references stripped, change-log narration rewritten to current-state. + +````yaml +id: bb0863dc-1255-4a +phase: implement +metadata: + payload: + artifact_references: + - docs/architecture/slice-dag.md + reason: "Re-review of v2 (4dff12d51) confirms both prior NACK items on task-3-3\ + \ (slice-dag.md) are resolved: (1) the 'v6 shared-branch shape' process-iteration\ + \ tag is replaced with present-tense prose ('returns the shared per-slice integration\ + \ branch all roles on a slice push to'); (2) the 'not the pre-#3115 hard title[:67]+\"\ + ...\" cut' change-log narration is removed, reframed to current behavior only\ + \ ('titles over that length truncate at a word boundary (#3115)'). The #3115\ + \ anchor survives as a justification link (permitted). The two flagged NOT-defects\ + \ (line 137 verbatim plan_parser error string, line 546 back-compat rationale)\ + \ were correctly left untouched. Diff is surgical \u2014 only the two requested\ + \ hunks changed. slice-3 fully satisfies all acceptance criteria across task-3-1\ + \ (gateway-auto-filter.md), task-3-2 (coordination-state.md), task-3-3 (slice-dag.md):\ + \ historical-record sections removed, ledger/process references stripped, change-log\ + \ narration rewritten to current-state." + ack_version: 2 + attestation: + tasks_verified: + - task-3-1 + - task-3-2 + - task-3-3 + version: 2 + slice_id: slice-3 +```` + +### [2026-06-26T21:52:20Z] orchestrator → documenter (STATUS): Ready to confirm — all confirm preconditions satisfied + +Your proposal (version 2) is ready to confirm — all blocking reviews are clear and global confirm preconditions are met. Run `egg-orch consensus confirmed` to confirm. + +````yaml +id: 6497ed04-77d1-47 +phase: implement +metadata: + ready_to_confirm: true + version: 2 + slice_id: slice-3 +```` + +### [2026-06-26T21:52:24Z] documenter (CONSENSUS_CONFIRMED): Consensus confirmed by documenter + +orchestrator-side confirm (#3064 event loop) + +````yaml +id: 8c14e074-5d14-43 +phase: implement +metadata: + slice_id: slice-3 +```` + +### [2026-06-26T21:52:24Z] reviewer_contract (CONSENSUS_CONFIRMED): Consensus confirmed by reviewer_contract + +orchestrator-side confirm (#3064 event loop) + +````yaml +id: b9cf44b8-a186-48 +phase: implement +metadata: + slice_id: slice-3 +```` + +### [2026-06-26T21:52:24Z] reviewer_security (CONSENSUS_CONFIRMED): Consensus confirmed by reviewer_security + +orchestrator-side confirm (#3064 event loop) + +````yaml +id: 42533e73-9006-45 +phase: implement +metadata: + slice_id: slice-3 +```` + +### [2026-06-26T21:52:24Z] reviewer_concurrency (CONSENSUS_CONFIRMED): Consensus confirmed by reviewer_concurrency + +orchestrator-side confirm (#3064 event loop) + +````yaml +id: 57c5a4c0-8aa3-45 +phase: implement +metadata: + slice_id: slice-3 +```` diff --git a/docs/architecture/coordination-state.md b/docs/architecture/coordination-state.md index 2d781a3dbc..6b011f34fa 100644 --- a/docs/architecture/coordination-state.md +++ b/docs/architecture/coordination-state.md @@ -22,32 +22,10 @@ > has produced zero such incidents. The invariant below is the structural fix > that retires the failure class. -## Slice landings (status as of slice-6) - -This page describes the **final shape** of the #3077 invariant. The epic was -landed in six slices, all of which have now shipped to `main`; every enforcing -mechanism cited below exists. The table records each slice landing so a reader -can trace which mechanism arrived in which slice. - -| Mechanism | Cited under | Status | -|-----------|-------------|--------| -| Wrapper `sync_to_proposals()` per-SHA outcome recording + "worktree NOT synced" banner | Clause 2 | **Shipped** (slice-1) | -| Empty-delta caution cross-reference to the wrapper banner | Clause 2 | **Shipped** (slice-1) | -| `mcp__brc__read_peer_artifact` (live store + on-disk merge, `live` flag) | Clause 1 | **Shipped** (predates #3077) | -| Contract reads via `mcp__sdlc__show_contract` / `mcp__task__*` / `mcp__phase__get_context` | Clause 1 | **Shipped** (predates #3077) | -| `mcp__progress__query_status` / `mcp__progress__emit` HTTP-backed reads | Clause 1 | **Shipped** (predates #3077) | -| `shared/egg_contracts/artifact_spec.py` declarative artifact registry | Clause 3 | **Shipped** (slice-2) | -| `shared/egg_contracts/tests/test_artifact_spec.py` spec-consistency tests | Clause 3 | **Shipped** (slice-2) | -| `handle_consensus_propose_signal` generalisation to every spec-registered artifact | Clause 3 | **Shipped** (slice-3) | -| Gateway `POST /api/v1/artifact/get` + `orchestrator/routes/artifacts.py` | Clause 1 | **Shipped** (slice-4) | -| Sandbox `egg-artifact` verb | Clauses 1, 3 | **Shipped** (slice-4) | -| `orchestrator/tests/test_prompt_sync_ratchet.py` no-sync-mechanics ratchet | Clause 2 | **Shipped** (slice-5) | -| Redis-only message store (memory backend removed, [#3159](https://github.com/jwbron/egg/issues/3159)) + Redis restart-semantics test | Wipe-semantics | **Shipped** (slice-6 fail-loud signal, superseded by the #3159 removal) | - -The clause descriptions below are written in present tense. With all six -slices shipped, every "Enforcing mechanisms" bullet now describes a -current-state claim rather than a design target. The slice column above -records when each mechanism landed. +This page states the invariant and names the live mechanism that enforces +each of its three clauses. Every mechanism cited below exists in the current +codebase; the [Mechanism map](#mechanism-map) at the foot of the page binds +each one to its module and the clause it enforces. ## The Three-Clause Invariant @@ -82,7 +60,7 @@ explicitly justify the exception in this page before it ships. by **spec-registered name** + hex-validated ref, executing `git show :` against the authoritative repo. The endpoint is strict — it accepts no raw repo-path escape hatch - ([#3077](https://github.com/jwbron/egg/issues/3077) HITL Q2) — and + ([#3077](https://github.com/jwbron/egg/issues/3077)) — and unblocks [#3002](https://github.com/jwbron/egg/issues/3002) by removing the implicit shared-object-store assumption that made `git show` work on a single-host deployment but break on a split-object-store runtime @@ -168,34 +146,26 @@ explicitly justify the exception in this page before it ships. ## Wipe semantics: designed boundary wipe vs accidental mid-phase loss -The message store and the BRC consensus tracker are wiped at **two** -points. These MUST NOT be conflated when reasoning about durability: - -| Wipe | Where | When | Status | -|------|-------|------|--------| -| **Designed phase-boundary wipe** | `_clear_concurrent_state()` in `orchestrator/routes/phases.py` (called from `phases.py` at the phase transitions and from `routes/pipelines.py`) | At phase transitions, **after** the brc-history persistence has captured the transcript | **Required behaviour.** This wipe must keep happening. The persisted history is the audit trail; the live message store is per-phase scratch space. | -| **Accidental mid-phase restart loss** | A mid-phase orchestrator restart on the (removed) in-memory `MessageStore` backend | Could occur whenever backend selection landed on the in-memory store — silently, via the old `EGG_MESSAGE_STORE_BACKEND=auto` → memory fallback | **Retired surface.** [#3159](https://github.com/jwbron/egg/issues/3159) removed the in-memory backend; Redis Streams is the only backend and creation fails loudly rather than degrading. | - -The history of this distinction: slice 6 of #3077 added a **fail-loud -signal** (error-level marker log + health-visible degraded flag) for -the `auto` → memory fallback, per HITL Q3 deliberately without changing -the selection semantics. [#2662](https://github.com/jwbron/egg/issues/2662) -then deployed Redis in-cluster (`k8s/base/redis-deployment.yaml`) and -pinned the orchestrator to `EGG_MESSAGE_STORE_BACKEND=redis`, making -the fallback unreachable in production. -[#3159](https://github.com/jwbron/egg/issues/3159) completed the arc by -removing the in-memory backend (and the now-purposeless fail-loud -machinery) entirely: `redis` / unset selects Redis, the removed -`auto` / `memory` values raise at creation, and an unreachable Redis -raises instead of falling back. Deeper durability work stays in the -[#3070](https://github.com/jwbron/egg/issues/3070) lineage. The Redis -path's restart semantics are pinned by -`orchestrator/tests/test_redis_message_store.py`: mid-phase messages -survive a store re-instantiation against the same Redis (simulated -orchestrator restart), while the designed `_clear_concurrent_state()` -phase-boundary wipe still clears state. Both wipe semantics are named -explicitly in the test ids/docstrings so a future regression cannot -quietly trade one for the other. +The message store and the BRC consensus tracker are wiped at exactly **one** +designed point, and that wipe MUST NOT be conflated with a restart when +reasoning about durability: + +| Wipe | Where | When | Why it is safe | +|------|-------|------|----------------| +| **Designed phase-boundary wipe** | `_clear_concurrent_state()` in `orchestrator/routes/phases.py` (called at the phase transitions and from `routes/pipelines.py`) | At phase transitions, **after** the brc-history persistence has captured the transcript | The persisted history is the audit trail; the live message store is per-phase scratch space. This wipe is required behaviour. | + +Redis Streams is the only message-store backend: `redis` / unset selects +Redis, and an unreachable Redis raises at creation rather than falling back. +There is no in-memory backend to silently lose mid-phase state on an +orchestrator restart ([#3159](https://github.com/jwbron/egg/issues/3159) +removed it precisely to close that gap; deeper durability work lives in the +[#3070](https://github.com/jwbron/egg/issues/3070) lineage). The Redis path's +restart semantics are pinned by +`orchestrator/tests/test_redis_message_store.py`: mid-phase messages survive +a store re-instantiation against the same Redis (a simulated orchestrator +restart), while the designed `_clear_concurrent_state()` phase-boundary wipe +still clears state. Both wipe semantics are named explicitly in the test +ids/docstrings so a future regression cannot quietly trade one for the other. ## Mechanism map @@ -228,10 +198,9 @@ quietly trade one for the other. state. - [BRC Consensus Wrapper](orchestrator.md#brc-consensus-wrapper) — the orchestrator owns the wait and spawns the per-event agent - one-shot (the in-pod wait arm was retired by #3164); the wrapper - around each pod performs deterministic sync at spawn time. + one-shot; the wrapper around each pod performs deterministic sync at + spawn time. - [Reviewer Sync Guide](../../shared/prompts/REVIEWER-SYNC.md) — the - agent-facing reviewer contract; its delta-command row was rewritten - in #3077 slice 5 from `git fetch` + `git log` instructions to - served-reads wording so it no longer drifts back into prompt-prose - sync mechanics. + agent-facing reviewer contract; its delta-command row uses + served-reads wording rather than `git fetch` / `git log` instructions, + so it cannot drift back into prompt-prose sync mechanics. diff --git a/docs/architecture/gateway-auto-filter.md b/docs/architecture/gateway-auto-filter.md index 2b886dbc96..d1d2077be6 100644 --- a/docs/architecture/gateway-auto-filter.md +++ b/docs/architecture/gateway-auto-filter.md @@ -1,133 +1,206 @@ -# Gateway Auto-Filter and Commit-Authorship Registry +# Gateway Restricted-Path Rejection and Commit-Authorship Registry -> **Note:** [#2039](https://github.com/jwbron/egg/issues/2039) replaced the silent-strip auto-filter described below with a structured `403 restricted_path_modified` rejection. The gateway no longer rewrites pushes to remove blocked paths; it now rejects the push and points the agent at the conditional-ACK recovery pattern ([#1998](https://github.com/jwbron/egg/issues/1998)). The **commit-authorship registry** remains the source of truth for per-commit attribution and continues to back the rejection's own-vs-pulled partition. The "Push handler dispatch" and "Per-commit rewrite algorithm" sections below describe the historical #1882 design — they are preserved for context but the rewrite path and its support code (`gateway/filtered_push.py`) have been removed. The current behavior is summarized in the [gateway README "File-Level Access Restrictions"](../../gateway/README.md#file-level-access-restrictions) section. +The gateway enforces role-based file restrictions at push time. When an +agent push modifies a path the pushing role cannot write, the gateway +**rejects the push** with a structured `403 restricted_path_modified` +([#2039](https://github.com/jwbron/egg/issues/2039)) and points the agent +at the conditional-ACK recovery pattern +([#1998](https://github.com/jwbron/egg/issues/1998)). The +**commit-authorship registry** is the source of truth for per-commit +attribution and backs the rejection's own-authored-vs-pulled partition. -> Originally landed in [#1882](https://github.com/jwbron/egg/issues/1882). Revived the unmerged design from [#1470](https://github.com/jwbron/egg/issues/1470) and extended it to handle mixed-role pushes. +The current behavior is also summarized in the +[gateway README "File-Level Access Restrictions"](../../gateway/README.md#file-level-access-restrictions) +section. ## Problem -Role-based file restrictions ([#1494](https://github.com/jwbron/egg/issues/1494)) block a push if any file in the diff is outside the pushing role's allowed set. Before #1882, the gateway returned `403 Push denied` on any violation and relied on either agent self-recovery or the client-side workaround `egg-orch push --scope-filter` ([#1547](https://github.com/jwbron/egg/issues/1547)). Two problems: +Role-based file restrictions ([#1494](https://github.com/jwbron/egg/issues/1494)) +govern which files each role may push. The hard problem is **mixed-role +pushes**: once an agent pulls or merges in commits authored by another role +(cross-role handoff, rebase-on-main), a naive whole-diff file check sees +those peer-authored paths and blocks the entire push, trapping a +role-restricted producer whose branch inherited unrelated upstream commits. -1. **Agent tax**: every 403 cost tokens, required the agent to interpret the error correctly, and surfaced noise the orchestrator had to monitor. -2. **Mixed-role pushes were broken**: once an agent pulled / merged in commits authored by another role (cross-role handoff, rebase-on-main), the gateway's file check saw those paths and blocked the whole push. `--scope-filter` could not help — it would squash pulled history and drop legitimate work. - -## Outcome - -After #1882: - -- The gateway auto-filters disallowed files on push. Agents no longer see `403` for agent-role file violations. -- Pulled cross-role commits pass through **bitwise-unchanged** — tree, author, committer, message, trailers preserved — while own-authored commits with blocked paths are individually rewritten with an `Auto-Filtered: true` git trailer appended to the message. -- Push responses gain `pushed_commits` (SHAs actually pushed) and `pulled_commits: [{sha, author_role}, ...]` (cross-role commits observed). Filtered / short-circuit paths add `filtered`, `excluded_files`, `pushed_files`, and `nothing_to_push` as appropriate. -- `sandbox/egg_lib/cli_push.py --scope-filter`, its `_filter_files` helper, and the `EGG_AGENT_FILE_PATTERNS` env-var injection were deleted in the same PR. -- Phase / anchor / protected-file / branch-ownership / private-mode / concurrent-mode checks keep their `403` behavior — the auto-filter is scoped narrowly to agent-role file restrictions. -- `EGG_AGENT_RESTRICTIONS_ENFORCE=false` remains as an emergency kill switch; the auto-filter short-circuits to warn-only plain push, but the success response still carries `filtered: false`, `excluded_files: []`, `pushed_files`, and `pulled_commits` so downstream tooling sees a consistent schema in both enforce and warn-only modes. +The gateway therefore cannot reason about a push as a flat set of changed +files. It must know *who authored each commit* so it can check the pushing +role's write permissions against only that role's own-authored files and let +pulled cross-role commits through untouched. ## Why a commit-authorship registry? -Before the gateway can filter without destroying pulled work, it needs to know *who authored each commit*. The session-level git identity set by `sandbox/entrypoint.py` (`egg (coder) `) is metadata only and forgeable by a compromised sandbox. The gateway keeps a durable `{sha → role}` mapping, written **authoritatively** from the session token that created each commit. +Before the gateway can partition own-authored from pulled work, it needs to +know which role authored each commit. The session-level git identity set by +`sandbox/entrypoint.py` (`egg (coder) `) is metadata only +and forgeable by a compromised sandbox. The gateway keeps a durable +`{sha → role}` mapping, written **authoritatively** from the session token +that created each commit. ### Observation point: gateway-inline, not sandbox hook -HITL decision-1(d) originally phrased this as a sandbox-installed `post-commit` hook. That design is **infeasible** in this codebase: +The registry observes commits **inline in `git-execute`**, not via a sandbox +git hook. A sandbox-installed hook is infeasible here: -- Sandbox containers have no direct `.git` access — the gateway shadows the worktree's `.git` via tmpfs (`sandbox/entrypoint.py:728-750`), so a hook installed there would never fire. +- Sandbox containers have no direct `.git` access — the gateway shadows the + worktree's `.git` via tmpfs (`sandbox/entrypoint.py`), so a hook installed + there would never fire. - The gateway sets `core.hooksPath=/dev/null` globally. -- `git commit --no-verify` would bypass any hook we did manage to install. +- `git commit --no-verify` would bypass any hook that did get installed. -The gateway is already the sole commit-creation path via `POST /api/v1/git/execute`. #1882 therefore observes commits **inline in `git-execute`** — the handler snapshots `git rev-parse HEAD` before and after each inner git invocation, then POSTs every new SHA to the orchestrator's `/api/v1/commit-authorship/register` endpoint with the session's role. This catches every commit-creating subcommand (`commit`, `commit --amend`, `cherry-pick`, `revert`, `merge`, `rebase`) with **no sandbox-side code path** and no hook to suppress. - -The deviation from the decision-1(d) text was flagged explicitly in the plan for reviewer sign-off: the outcome (every agent commit registered with its role) is functionally identical, and the guarantees are strictly stronger. +The gateway is the sole commit-creation path via +`POST /api/v1/git/execute`. The handler snapshots `git rev-parse HEAD` +before and after each inner git invocation, then POSTs every new SHA to the +orchestrator's `/api/v1/commit-authorship/register` endpoint with the +session's role. This catches every commit-creating subcommand (`commit`, +`commit --amend`, `cherry-pick`, `revert`, `merge`, `rebase`) with no +sandbox-side code path and no hook to suppress. ### Registry semantics: first-wins The registry is append-only and idempotent per SHA: -- First `register(sha, role, pipeline_id, ...)` call is authoritative. +- The first `register(sha, role, pipeline_id, ...)` call is authoritative. - Re-register with the **same role** is a no-op. -- Re-register with a **different role** is rejected with a `collision` audit event; the original binding is preserved. This prevents an observer-suppression-then-rewrite attack where a malicious agent swallows the initial observer call and later re-registers the same SHA under a different role. +- Re-register with a **different role** is rejected with a `collision` audit + event; the original binding is preserved. This prevents an + observer-suppression-then-rewrite attack where a malicious agent swallows + the initial observer call and later re-registers the same SHA under a + different role. ### Storage -The registry is a sub-store on the existing `egg/pipeline-state` orphan branch (`orchestrator/state_store.py`), sharded by pipeline ID (`.egg-state/commit-authorship/.json`) with a `_orphan.json` fallback for commits registered before a pipeline ID is known. Reuses the existing `fcntl` + `RLock` + optimistic-versioning pattern — no net-new durable store (risk-analyst R-01). +The registry is a sub-store on the `egg/pipeline-state` orphan branch +(`orchestrator/state_store.py`), sharded by pipeline ID +(`.egg-state/commit-authorship/.json`) with a `_orphan.json` +fallback for commits registered before a pipeline ID is known. It reuses the +existing `fcntl` + `RLock` + optimistic-versioning pattern — no net-new +durable store. ### HTTP surface -- `POST /api/v1/commit-authorship/register` — accepts `{sha, role, pipeline_id, repo, branch, patch_id}`. Role is authoritative from the session's inter-pod shared-secret; body-supplied values are logged but not trusted. `patch_id` (optional, `git patch-id --stable`) is recorded at registration time so attribution can survive a later SHA rewrite (rebase) via content-based lookup (#2932). -- `POST /api/v1/commit-authorship/lookup` — bulk lookup by SHA and/or patch-id: `{shas: [...], patch_ids: [...]} → {attribution: {sha: role | null}, patch_attribution: {patch_id: role | null}}`. At least one of `shas`/`patch_ids` is required. The push handler uses `shas` for normal attribution and `patch_ids` as a content-based fallback for commits whose SHA a rebase rewrote (#2932). An ambiguous patch-id (identical patch from two distinct roles) resolves to `null`. - -Both endpoints require the existing gateway↔orchestrator shared-secret header. +- `POST /api/v1/commit-authorship/register` — accepts + `{sha, role, pipeline_id, repo, branch, patch_id}`. Role is authoritative + from the session's inter-pod shared-secret; body-supplied values are + logged but not trusted. `patch_id` (optional, `git patch-id --stable`) is + recorded at registration time so attribution can survive a later SHA + rewrite (rebase) via content-based lookup + ([#2932](https://github.com/jwbron/egg/issues/2932)). +- `POST /api/v1/commit-authorship/lookup` — bulk lookup by SHA and/or + patch-id: `{shas: [...], patch_ids: [...]} → {attribution: {sha: role | null}, patch_attribution: {patch_id: role | null}}`. + At least one of `shas`/`patch_ids` is required. The push handler uses + `shas` for normal attribution and `patch_ids` as a content-based fallback + for commits whose SHA a rebase rewrote. An ambiguous patch-id (identical + patch from two distinct roles) resolves to `null`. + +Both endpoints require the gateway↔orchestrator shared-secret header. ## Push handler dispatch -`gateway/gateway.py::git_push` replaces the single `check_agent_restrictions` 403 branch with a three-way dispatch driven by per-commit attribution: - -1. **Attribute files**: `get_attributed_changed_files_in_push` walks the unpushed range via the existing per-commit `diff-tree` loop, captures the emitting SHA for each file, and does one bulk `lookup_bulk` against the registry to tag each `AttributedFile` with `authored_by: str | None`. -2. **Partition**: files with `authored_by == push_role` **or** `authored_by is None` (fail-closed) are treated as own-authored and subject to restrictions. Files with `authored_by == ` are pulled and exempt. -3. **Dispatch**: - - All own-files allowed → plain push. Response adds `pulled_commits` if any commit in the range was cross-role. - - Mixed own-allowed + own-blocked → `gateway/filtered_push.py::execute_filtered_push` (see below). Response includes `filtered: true`, `excluded_files`, `pushed_files`, `pushed_commits`, `pulled_commits`. - - All own-files blocked → `200 nothing_to_push: true` with `excluded_files` and `pulled_commits`. No ref update, no remote push, worktree unchanged. - -### Attribution-fallback short-circuit - -When `get_attributed_changed_files_in_push` returns an error or an empty commit list (for example, a legacy test mocking only the old file-detection path, or a push with staged changes but no walkable commit range), the handler enters an **attribution-fallback** mode: every file is treated as own-authored-and-unregistered, and if any file is blocked the push is **unconditionally** short-circuited to `200 nothing_to_push: true`. The rewriter is **never** invoked on an empty commit list — that would push HEAD unchanged and leak blocked files to origin. The `push_all_blocked_no_op` audit event carries `attribution_fallback: true` in this path, and the success message reads `Push skipped: attribution unavailable and out-of-scope files detected (fail-closed).` - -Three distinct audit events are emitted: `push_auto_filtered`, `push_all_blocked_no_op`, and `push_authorship_unregistered_fallback` (the last fires whenever any commit in the range had `authored_by=None`). - -Non-agent sessions (no `g.session.agent_role`) skip attribution entirely and take today's plain-push path. - -## Per-commit rewrite algorithm - -`gateway/filtered_push.py::execute_filtered_push` walks the unpushed range in topological order (oldest first). For each commit: - -- **Pulled** (`authored_by` is a known other role): re-parent onto the previous loop's `new_sha` and reuse the commit verbatim. If the parent chain is unchanged, no new SHA is created. -- **Own** (`authored_by == push_role` or `None`): read the original tree via `git ls-tree -r `, remove blocked paths, `git write-tree` → `new_tree`. If `new_tree` equals the parent's tree (commit becomes empty after filtering), **drop the commit** and continue with the same `new_parent`. Otherwise `git commit-tree new_tree -p new_parent` with the original message plus an `Auto-Filtered: true` git trailer, reusing the original author / date; record the returned SHA as `new_sha`. - -After the walk: - -1. `git update-ref refs/heads/ ` locally. -2. `git push `. On failure, restore HEAD via `update-ref ` and `git reset --hard`; return `500` with the push error. -3. On success: `git read-tree --reset -u ` to sync index + worktree to the filtered state. -4. For each blocked file from the pre-rewrite tip, restore its blob into the worktree and index as a staged change via `git checkout-index --stage=0` with the old tree's blob — peer roles can pick them up without re-authoring. -5. Register each new own-commit SHA with the registry (`authored_by=push_role`) so a subsequent push by a different role attributes them correctly. - -Any error path restores HEAD and the worktree to exactly the pre-push state — the operation is atomic from the agent's perspective. +`gateway/gateway.py::git_push` drives agent-role enforcement from per-commit +attribution: + +1. **Attribute files**: `get_attributed_changed_files_in_push` walks the + unpushed range via the per-commit `diff-tree` loop, captures the emitting + SHA for each file, and does one bulk `lookup_bulk` against the registry to + tag each `AttributedFile` with `authored_by: str | None`. +2. **Partition**: files with `authored_by == push_role` **or** + `authored_by is None` (fail-closed) are treated as own-authored and + subject to restrictions. Files with `authored_by == ` + are pulled and exempt. +3. **Decide**: + - No own-authored file blocked → plain push. The response adds + `pulled_commits` if any commit in the range was cross-role. + - Any own-authored file blocked → **reject** with + `403 restricted_path_modified`. The response body carries `role`, + `blocked_paths`, `recommended_action`, `doc_ref` (`#1998`), + `pulled_commits`, and `attribution_fallback`. A category-specific + `hint` (e.g. "Documentation changes belong to the documenter role.") + is added when the blocked set matches a known category + ([#2355](https://github.com/jwbron/egg/issues/2355)). No ref update, + no remote push — the worktree is left exactly as the agent had it so it + can drop the edits and re-propose. + +The agent's sanctioned recovery is to drop the edits to the blocked paths +and re-propose with `--pre-merge-condition`, flagging a manual change for +the human reviewer per the conditional-ACK pattern +([#1998](https://github.com/jwbron/egg/issues/1998)). + +### Attribution-fallback (fail-closed) + +When `get_attributed_changed_files_in_push` returns an error or an empty +commit list (for example, a push with staged changes but no walkable commit +range), the handler enters **attribution-fallback** mode: every changed file +is treated as own-authored-and-unregistered. If any such file is blocked, +the push is rejected exactly as above with `attribution_fallback: true` on +both the audit event and the response. A blocked-file set can never reach +`git push` under agent credentials. + +Audit events: `push_denied_restricted_path_modified` fires on every +rejection; `push_authorship_unregistered_fallback` fires whenever any commit +in the range had `authored_by=None`. Non-agent sessions (no +`g.session.agent_role`) skip attribution entirely and take the plain-push +path. + +`EGG_AGENT_RESTRICTIONS_ENFORCE=false` is an emergency kill switch: the +filter short-circuits to warn-only plain push. ## Fail-closed invariant -Commits with no registry entry are treated as own-authored. The agent cannot suppress the observer (it is gateway-inline and runs before the response is returned), and even if somehow an unregistered commit reached the push handler, its files flow through the pushing role's restriction check. This preserves the security guarantee that a file a role cannot write cannot be pushed under that role's identity, even under an observer-gap scenario. +Commits with no registry entry are treated as own-authored. The agent cannot +suppress the observer (it is gateway-inline and runs before the response is +returned), and any unregistered commit that reaches the push handler still +flows through the pushing role's restriction check. A file a role cannot +write cannot be pushed under that role's identity, even under an observer-gap +or attribution-unavailable scenario — in both cases the push is rejected +rather than partially applied. -The attribution-fallback short-circuit above hardens this further: when the handler cannot compute a commit walk at all, it refuses to invoke the rewriter on an empty commit list and returns `nothing_to_push: true` for every blocked file. There is no code path that reaches `git push` with a blocked-file set under agent credentials. - -## Binary-safe re-staging - -After a filtered push, the gateway re-stages the blocked blobs into the agent's worktree so a peer role can pick them up without re-authoring. Blobs are read through `git show` **without** Python's `text=True` decoding and written to the worktree as raw bytes (`gateway/filtered_push.py::_git_raw` + `_restage_blocked_files`). This preserves non-UTF-8 payloads (PNG, PDF, compiled artefacts) bitwise — a previous iteration used text mode and silently corrupted binary files on re-stage. After writing the file, `git add ` (not `git add --intent-to-add`) stages the blob content, so the next role's `git commit` captures it in full. - -## What was removed - -- **`sandbox/egg_lib/cli_push.py`**: the `--scope-filter` argparse flag, its filtered-push implementation, the `_filter_files` helper, and the `EGG_AGENT_FILE_PATTERNS` env-var read. The file collapses to a passthrough around `git push`. -- **`orchestrator/concurrent_executor.py`**: the `EGG_AGENT_FILE_PATTERNS` env-var injection at container-spawn time. Nothing consumes it after the cli_push cleanup. -- **Docs and agent-config rules**: every `--scope-filter` mention in `docs/guides/agent-development.md`, `docs/reference/orchestrator-cli.md`, and `sandbox/agent-config/rules/push-recovery.md` was replaced with the auto-filter story. +Phase / anchor / protected-file / branch-ownership / private-mode / +concurrent-mode checks keep their own `403` behavior; the restricted-path +rejection is scoped narrowly to agent-role file restrictions. ## Deploy ordering -Ship the orchestrator image **first** so `/api/v1/commit-authorship/*` is live before any gateway starts POSTing to it. The observer is best-effort (logs a WARNING and continues on registry-unavailable), and push-time unregistered commits fall through to fail-closed — both acceptable transiently but avoidable by ordering. +Ship the orchestrator image **first** so `/api/v1/commit-authorship/*` is +live before any gateway starts POSTing to it. The observer is best-effort +(logs a WARNING and continues on registry-unavailable), and push-time +unregistered commits fall through to fail-closed — both acceptable +transiently but avoidable by ordering. -No database migration. The state store creates the `commit-authorship/` subdirectory on first write. Long-running sessions that predate the deploy continue to work; their commits fall through to fail-closed at push time, which matches today's behavior. +No database migration. The state store creates the `commit-authorship/` +subdirectory on first write. Long-running sessions that predate a deploy +continue to work; their commits fall through to fail-closed at push time. ## Monitoring -> **Updated for #2039.** The `push_auto_filtered` and `push_all_blocked_no_op` audit events no longer fire — they were tied to the silent-strip and all-blocked short-circuit arms that the rejection model replaced. The current events are: - -- `push_denied_restricted_path_modified` — fires whenever a push is rejected because the diff modifies a path the role cannot write. Inspect the event's `attribution_fallback` boolean: `false` is the normal blocked case (registry attribution was available); `true` means the handler could not compute a commit walk and fell back to the attribution-unavailable rejection — a sustained spike there is worth investigating (mocked tests leaking into production, or `git rev-list` disagreeing with the handler's view of the unpushed range). -- `push_authorship_unregistered_fallback` — a steady trickle is normal (long-running sessions that predate deploy). A **sustained spike** after the deploy suggests the git-execute observer is missing some commit-creating subcommand; investigate which subcommand is being missed. +- `push_denied_restricted_path_modified` — fires whenever a push is rejected + because the diff modifies a path the role cannot write. Inspect the + event's `attribution_fallback` boolean: `false` is the normal blocked case + (registry attribution was available); `true` means the handler could not + compute a commit walk and fell back to the attribution-unavailable + rejection. A sustained spike on the `true` branch is worth investigating + (mocked tests leaking into production, or `git rev-list` disagreeing with + the handler's view of the unpushed range). +- `push_authorship_unregistered_fallback` — a steady trickle is normal + (long-running sessions that predate a deploy). A **sustained spike** + suggests the git-execute observer is missing some commit-creating + subcommand; investigate which subcommand is being missed. ## Deployment prerequisites -The gateway's commit-authorship client calls the orchestrator's `/api/v1/commit-authorship/{register,lookup}` routes, which live behind `require_lifecycle_secret`. The gateway pod therefore needs `EGG_LIFECYCLE_SECRET` injected the same way `orchestrator-deployment.yaml` does — mounted from `gateway-secrets.lifecycle-secret`. Without the secret, every register and lookup 401s and the whole feature degrades to fail-closed own-authored on every push. This env var is set in `k8s/base/gateway-deployment.yaml`; local dev picks it up from `.env`. +The gateway's commit-authorship client calls the orchestrator's +`/api/v1/commit-authorship/{register,lookup}` routes, which live behind +`require_lifecycle_secret`. The gateway pod therefore needs +`EGG_LIFECYCLE_SECRET` injected the same way `orchestrator-deployment.yaml` +does — mounted from `gateway-secrets.lifecycle-secret`. Without the secret, +every register and lookup 401s and the whole feature degrades to fail-closed +own-authored on every push. This env var is set in +`k8s/base/gateway-deployment.yaml`; local dev picks it up from `.env`. ## Related documents -- [Git Isolation Architecture](git-isolation.md) — parent document covering the gateway's policy-enforcement model. -- [Agent Development Guide: Push Enforcement](../guides/agent-development.md#push-enforcement-and-cross-role-pushes) — operational view for agent authors. -- [`sandbox/agent-config/rules/push-recovery.md`](../../sandbox/agent-config/rules/push-recovery.md) — runtime rule surfaced to sandboxed agents. +- [Git Isolation Architecture](git-isolation.md) — parent document covering + the gateway's policy-enforcement model. +- [Agent Development Guide: Push Enforcement](../guides/agent-development.md#push-enforcement-and-cross-role-pushes) — + operational view for agent authors. +- [`sandbox/agent-config/rules/push-recovery.md`](../../sandbox/agent-config/rules/push-recovery.md) — + runtime rule surfaced to sandboxed agents. diff --git a/docs/architecture/slice-dag.md b/docs/architecture/slice-dag.md index 7b4ce513a5..26d81e7e8a 100644 --- a/docs/architecture/slice-dag.md +++ b/docs/architecture/slice-dag.md @@ -1,65 +1,60 @@ # Slice-DAG Implement Phase -> Status: shipped (#2137, HITL decision-20 opt-2). Per the operator's -> resolution of decision-20, the implement-phase run-loop wire-up landed -> in this PR rather than being deferred. The slice loop drives -> `SliceScheduler` waves, creates each slice's integration branch on -> origin via the gateway *before* agents spawn, runs the BRC consensus -> per slice, opens a per-slice PR on consensus reach, and runs the -> stacked-PR reconciler in a background thread. The reconciler's -> `list_open_prs` / `list_remote_branches` callables are bound to live -> gateway helpers — it is no longer a no-op. +The implement phase runs as a **forest of slices**. Each slice is an +independently-implementable unit of work with its own integration branch, +agent team, BRC consensus, and pull request; slice PRs stack along the DAG's +linear chains. The slice run loop drives `SliceScheduler` waves, creates each +slice's integration branch on origin via the gateway *before* agents spawn, +runs BRC consensus per slice, opens a per-slice PR on consensus reach, and +runs the stacked-PR reconciler in a background thread (bound to live gateway +helpers — `list_open_prs` / `list_remote_branches` / `rebase_onto`). + +The forest model exists because a single monolithic agent team on one branch +through one BRC consensus cannot hold a large ticket: tickets big enough to +fill the context window (empirically ~33K LOC / 41 files, +[#2105](https://github.com/jwbron/egg/issues/2105)) caused compaction and +quality drops. Slicing bounds each agent team's context to one unit of work. + +> **Known limitations** (tracked in +> [#2199](https://github.com/jwbron/egg/issues/2199), the per-slice MCP +> control-verbs follow-up): > -> **Two trade-offs scoped to #2199** (per-slice MCP control verbs -> follow-up): -> -> 1. The `EGG_PIPELINE_ID` override that scopes BRC `CONSENSUS_*` -> messages also currently scopes the agent-emitted `HEARTBEAT` and -> `OVERSEER_ALERT` traffic to the slice tracker. The hybrid scheme -> promised by decision-14 (cross-slice telemetry routes through the -> bare `pipeline_id`) is honoured *partially* — the orchestrator-side -> cascade emission and log line in `_run_implement_phase_slices` -> provide the always-on fallback so deadlocks remain visible at the -> pipeline level. Full fan-out requires a CLI-side message-type-aware -> router (#2199). -> 2. The `record_cycle` two-tier `max_cycles` accounting and the -> `hitl_escalator` hook on `SliceScheduler` are public API and unit- -> tested, but the slice run loop does not yet call `record_cycle` -> on each BRC re-proposal. The env knobs +> 1. The `EGG_PIPELINE_ID` override that scopes BRC `CONSENSUS_*` messages +> also scopes agent-emitted `HEARTBEAT` and `OVERSEER_ALERT` traffic to +> the slice tracker, so cross-slice telemetry does not route through the +> bare `pipeline_id`. The orchestrator-side cascade emission and log line +> in `_run_implement_phase_slices` are the always-on fallback that keeps +> deadlocks visible at the pipeline level; full fan-out requires a +> CLI-side message-type-aware router. +> 2. `SliceScheduler.record_cycle` (two-tier `max_cycles` accounting) and the +> `hitl_escalator` hook are public API and unit-tested, but the slice run +> loop does not call `record_cycle` on each BRC re-proposal. The env knobs > `EGG_ORCH_SLICE_LOCAL_MAX_CYCLES` / `EGG_ORCH_SLICE_GLOBAL_MAX_CYCLES` -> are read but not exercised today; #2199 wires the trip flag through -> the BRC re-proposal loop. - -The implement phase used to run as a single monolithic agent team on one -branch through one BRC consensus. Tickets large enough to fill the context -window (empirically ~33K LOC / 41 files per #2105) caused compaction and -quality drops. The slice-DAG model replaces that single-team flow with a -**forest of slices** — each slice has its own integration branch, agent -team, BRC consensus, and pull request. Slice PRs stack along the DAG's -linear chains. +> are read but not exercised. ## Vocabulary | Term | Meaning | |------|---------| -| **Slice** | One independently-implementable unit of work. Renamed from `Phase` in #2137. Each slice has tasks, dependencies, an integration branch, an agent team, and (eventually) a PR. | +| **Slice** | One independently-implementable unit of work. Each slice has tasks, dependencies, an integration branch, an agent team, and (eventually) a PR. | | **Forest** | The slice DAG must be a forest: every slice has **at most one** DAG parent. Multi-parent slices are rejected at plan ingestion. | | **Wave** | A set of slices whose dependencies are all satisfied. Slices in the same wave can run concurrently. | | **Stacked PR** | A child slice's PR targets the parent slice's integration branch (not main). Reviewers land slices incrementally as parents merge. | | **Cascade** | When a slice fails, after a grace window its transitive descendants are marked `BLOCKED_ON_FAILED_DEPENDENCY` rather than running pointlessly. | -## Contract Schema (Phase → Slice) +## Contract Schema (slices, with `phases` back-compat) -The contract field `phases[]` was renamed to `slices[]`. Backwards -compatibility is preserved at every layer: +The canonical contract field is `slices[]`. Backwards compatibility with the +legacy `phases[]` name is preserved at every layer so older on-disk contracts +keep loading: -- **`Phase = Slice`** — class alias. Old imports keep working. +- **`Phase = Slice`** — class alias. Legacy imports keep working. - **`PhaseStatus = SliceStatus`** — enum alias. Status values - (`pending` / `in_progress` / `complete` / `blocked`) are unchanged so + (`pending` / `in_progress` / `complete` / `blocked`) are identical so on-disk JSON loads without translation. - **`Contract.phases`** — read/write property proxy to `Contract.slices`. Reading and assigning both work. -- **Load-time migration shim** — when a pre-#2137 contract JSON containing +- **Load-time migration shim** — when a legacy contract JSON containing `phases: [...]` is loaded, a Pydantic `model_validator(mode="wrap")` rewrites `phases[]` → `slices[]` and each item's `phase-N` ID → `slice-N`, including dependency references. The original payload is stashed on the @@ -75,7 +70,7 @@ pass either. | Field | Type | Default | Purpose | |-------|------|---------|---------| | `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) slice-4 TASK-4-2, cq-9), 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 (TASK-4-3) against the dependency-derived parent before routing onto `pipeline_branch`. | +| `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. | ## Plan Parser & Forest Validation @@ -221,9 +216,9 @@ exists to provide. ## DependencyGraph generification -`shared/egg_contracts/dependency_graph.py` was generified in #2137. `DependencyNode`, `ExecutionWave`, `ExecutionPlan`, and `DependencyGraph` -are now generic over the node-key type. The classes use **PEP 695 generic +in `shared/egg_contracts/dependency_graph.py` are generic over the node-key +type. The classes use **PEP 695 generic class syntax** (`class DependencyGraph[NodeT: Hashable]: ...`) — matching `pyproject.toml`'s `target-version = "py314"` — rather than the older `Generic[NodeT]` + `TypeVar` shape. Existing agent-role-keyed callers @@ -311,15 +306,15 @@ otherwise serialise every other scheduler operation; a >180 s round trip would also trip the orchestrator's stuck-phase-transition timeout. (Same pattern as #2012 for the BRC tracker.) -> **Status: deferred to #2199.** The `record_cycle` invocation point is -> not yet wired into the slice run loop. `_run_implement_phase_slices` -> tracks per-slice exit codes but does not call `record_cycle` on each -> BRC re-proposal; the env knobs are read at constructor time but the -> trip path is dead code today. The hook itself is public, unit-tested, -> and lock-safe — #2199 (per-slice MCP control verbs follow-up) closes -> the loop on the BRC re-proposal counter and wires the -> `hitl_escalator` argument through to the orchestrator's HITL -> escalation surface. +> **Not yet wired** (tracked in +> [#2199](https://github.com/jwbron/egg/issues/2199)). The `record_cycle` +> invocation point is not called from the slice run loop: +> `_run_implement_phase_slices` tracks per-slice exit codes but does not +> call `record_cycle` on each BRC re-proposal, so the env knobs are read at +> constructor time but the trip path is unreached. The hook itself is +> public, unit-tested, and lock-safe; closing the loop on the BRC +> re-proposal counter and wiring the `hitl_escalator` argument through to +> the orchestrator's HITL escalation surface is the open follow-up. ### Failure cascade @@ -336,8 +331,7 @@ emitted with the full subtree. not by the scheduler. After every `iter_ready` pass, `_run_implement_phase_slices` calls `scheduler.poll_cascades()`, logs each event, and pushes a structured `OVERSEER_ALERT` directly into -the in-process `message_store` keyed on the bare `pipeline_id` (TASK-3-4 -emission path): +the in-process `message_store` keyed on the bare `pipeline_id`: ```python { @@ -352,10 +346,10 @@ emission path): } ``` -This is the always-on safety net: under the v4/v5/v6 `EGG_PIPELINE_ID` +This is the always-on safety net: under the `EGG_PIPELINE_ID` override, agent-emitted `OVERSEER_ALERT` traffic routes through the -slice tracker rather than the pipeline tracker (the trade-off scoped to -#2199 — see status callout). The orchestrator-side emission keeps +slice tracker rather than the pipeline tracker (see Known limitations). +The orchestrator-side emission keeps cascade visibility flowing through `pipeline_id` regardless, so the human operator's overseer surface still sees the deadlock even if every agent in the failed subtree has already shut down. @@ -373,16 +367,16 @@ one-way trip.) `ConcurrentPhaseExecutor.get_worktree_branch(role, *, slice_id=None)` is slice-aware. **In slice mode, every agent in a slice shares the slice's -integration branch.** This was a deliberate v6 design correction: an -earlier per-role suffix shape (`egg/issue-N/slice-M/{role}/work`) caused -the per-slice PR's diff to render empty, because the integration branch -opened on origin pointed at the parent's tip while every agent commit -lived on a per-role sibling branch GitHub does not see in the PR. +integration branch.** The branch is shared per-slice rather than +per-role because a per-role suffix shape (`egg/issue-N/slice-M/{role}/work`) +would make the per-slice PR's diff render empty: the integration branch +opened on origin points at the parent's tip while every agent commit would +live on a per-role sibling branch GitHub does not see in the PR. | Mode | `slice_id` | Result | |------|------------|--------| -| Pipeline mode (pre-#2137 / non-slice phases) | `None` (default) | `pipeline.branch` or `egg/issue-N/work` (tip pushed to `/work` since #2399). | -| Slice mode (post-v6) | `"slice-2"` or `"2"` | `egg/issue-N/slice-2` — **shared by every role in the slice**. | +| Pipeline mode (non-slice phases) | `None` (default) | `pipeline.branch` or `egg/issue-N/work` (tip pushed to `/work` since [#2399](https://github.com/jwbron/egg/issues/2399)). | +| Slice mode | `"slice-2"` or `"2"` | `egg/issue-N/slice-2` — **shared by every role in the slice**. | > **The slice is the unit of isolation, not the role within the slice.** > Cross-slice isolation is preserved by the per-slice integration branch; @@ -420,11 +414,11 @@ The slice run loop creates each slice's integration branch on origin the parent ref so the commit object is locally reachable, (2) resolves the parent branch to a SHA on origin via `git ls-remote`, then (3) pushes `:refs/heads/` through the existing -per-agent `/api/v1/git/push` allowlist (no new privileged endpoint; -decision-15 invariant preserved). Pushing by SHA rather than ref name -avoids local-ref resolution failures in the orchestrator's per-pipeline -worktree, which is checked out on `/work` and carries no local -ref matching `` (#2393). On creation failure the run +per-agent `/api/v1/git/push` allowlist (no new privileged endpoint is +introduced). Pushing by SHA rather than ref name avoids local-ref +resolution failures in the orchestrator's per-pipeline worktree, which is +checked out on `/work` and carries no local ref matching +`` ([#2393](https://github.com/jwbron/egg/issues/2393)). On creation failure the run loop calls `record_failure(slice_id)` and returns early — agents are not spawned against a missing integration branch. When the branch already exists (e.g. after an orchestrator-pod restart or a `restart_phase` that @@ -438,14 +432,13 @@ rather than failing with a non-fast-forward rejection (#3245). The BRC tracker layer (`orchestrator/peer_consensus.py`) was extended so `create/get/remove_peer_consensus_tracker(pipeline_id, slice_id=None)` keys the registry under the composite key `{pipeline_id}/{slice_id}`. Per-slice -`CONSENSUS_*` state is naturally isolated. Refine-phase decision-14 -called for `HEARTBEAT` / `OVERSEER_ALERT` to keep flowing through the -bare `pipeline_id`; in practice the `EGG_PIPELINE_ID` override route on -the agent CLI sends *every* outbound signal through the slice tracker -today (see status callout — full hybrid fan-out is scoped to #2199). -The orchestrator-side cascade emission and run-loop log lines are the -always-on `pipeline_id`-scoped fallback so deadlocks remain visible at -the pipeline level regardless. +`CONSENSUS_*` state is naturally isolated. `HEARTBEAT` / `OVERSEER_ALERT` +are intended to flow through the bare `pipeline_id`, but the +`EGG_PIPELINE_ID` override route on the agent CLI currently sends *every* +outbound signal through the slice tracker (see Known limitations). The +orchestrator-side cascade emission and run-loop log lines are the always-on +`pipeline_id`-scoped fallback so deadlocks remain visible at the pipeline +level regardless. ## Implement-phase run loop @@ -476,9 +469,9 @@ shape: persists `Slice.parent_branch_at_creation` AND flips `SliceStatus.PENDING → IN_PROGRESS` in the same contract write under the per-pipeline state lock ([#2777](https://github.com/jwbron/egg/issues/2777) - slice-4 TASK-4-2, cq-9 — gives Layer-C bootstrap a single - signal that distinguishes a fresh slice from an interrupted - one), creates the integration branch via the gateway, calls + — gives Layer-C bootstrap a single signal that distinguishes a + fresh slice from an interrupted one), creates the integration + branch via the gateway, calls `_run_concurrent_phase(slice_id=...)` to spawn the slice's agent team, awaits BRC consensus, and on consensus reach calls `GatewayClient.create_slice_pr` with `base` resolved from the @@ -494,7 +487,7 @@ shape: 5. **Tear down** the reconciler thread and aggregate per-slice exit codes into the run-loop's return value. -The run loop runs a **bootstrap reconciliation pass** before the first wave begins. Layer A reconciles slices the contract already marks `COMPLETE`; Layer B reconciles the open-PR side. Layer C ([#2777](https://github.com/jwbron/egg/issues/2777) slice-4 TASK-4-4, bundles [#2409](https://github.com/jwbron/egg/issues/2409)) reconciles non-`COMPLETE` slices that did real work before an orchestrator-pod recycle interrupted the prior run. The Layer-C classifier (`_classify_non_complete_slice`) reads `SliceStatus`, queries the gateway for the integration branch's origin commit count, and looks up the slice's consensus tracker, then applies a 5-way decision: (1) `IN_PROGRESS` with no commits → no-op (scheduler re-yields `READY`); (2) `IN_PROGRESS` + commits + no consensus → mark spawned; (3) `IN_PROGRESS` + commits + consensus reached → mark `COMPLETE`; (4) `BLOCKED` without a pending HITL → escalate to HITL; (5) corrupt / unclassifiable → escalate to HITL. Cases 4/5 create unresolved `Decision` objects on the contract via `_escalate_layer_c_hitl` rather than silently re-yielding `READY` (silent classification error is worse than an operator pause). The classifier's gateway-probe failure default is "fresh, re-yield `READY`" — the safer direction for the scheduler — which is deliberately the opposite of `_resolve_slice_base_branch`'s probe-failure default ("derived parent" — the safer direction for the next push). See [`Slice/phase restart hardening`](orchestrator.md#slicephase-restart-hardening-2777-slice-4-bundles-2409) for the full restart-hardening picture, including the slice-aware `restart_phase` clear and the per-slice consensus tracker reconstruction at startup. +The run loop runs a **bootstrap reconciliation pass** before the first wave begins. Layer A reconciles slices the contract already marks `COMPLETE`; Layer B reconciles the open-PR side. Layer C ([#2777](https://github.com/jwbron/egg/issues/2777), bundles [#2409](https://github.com/jwbron/egg/issues/2409)) reconciles non-`COMPLETE` slices that did real work before an orchestrator-pod recycle interrupted the prior run. The Layer-C classifier (`_classify_non_complete_slice`) reads `SliceStatus`, queries the gateway for the integration branch's origin commit count, and looks up the slice's consensus tracker, then applies a 5-way decision: (1) `IN_PROGRESS` with no commits → no-op (scheduler re-yields `READY`); (2) `IN_PROGRESS` + commits + no consensus → mark spawned; (3) `IN_PROGRESS` + commits + consensus reached → mark `COMPLETE`; (4) `BLOCKED` without a pending HITL → escalate to HITL; (5) corrupt / unclassifiable → escalate to HITL. Cases 4/5 create unresolved `Decision` objects on the contract via `_escalate_layer_c_hitl` rather than silently re-yielding `READY` (silent classification error is worse than an operator pause). The classifier's gateway-probe failure default is "fresh, re-yield `READY`" — the safer direction for the scheduler — which is deliberately the opposite of `_resolve_slice_base_branch`'s probe-failure default ("derived parent" — the safer direction for the next push). See [`Slice/phase restart hardening`](orchestrator.md#slicephase-restart-hardening-2777-slice-4-bundles-2409) for the full restart-hardening picture, including the slice-aware `restart_phase` clear and the per-slice consensus tracker reconstruction at startup. Per-slice agent teams are spawned via the existing `ConcurrentPhaseExecutor` machinery with `slice_id` plumbed through: @@ -502,8 +495,8 @@ Per-slice agent teams are spawned via the existing - `spawn_all` registers the BRC tracker under the nested `{pipeline_id}/{slice_id}` key. - `_spawn_agent` resolves the head ref via - `get_worktree_branch(role, slice_id=...)` (the v6 shared-branch - shape). + `get_worktree_branch(role, slice_id=...)`, which returns the + shared per-slice integration branch all roles on a slice push to. - `check_consensus` looks up the slice-scoped tracker first. `_run_concurrent_phase` mutates a shallow copy of the sandbox env to set @@ -537,9 +530,8 @@ live on that context PR (no longer on a "terminal slice umbrella"), so every slice PR is purely slice-scoped. - **Title.** `[][] `, capped at 70 - chars; over-long titles truncate at a word boundary (#3115), not the - pre-#3115 hard `title[:67] + "..."` cut that produced mid-word - fragments. `program-slug` is derived from `pipeline_id`: `issue-` + chars; titles over that length truncate at a word boundary (#3115). + `program-slug` is derived from `pipeline_id`: `issue-` pipelines collapse to `issue-` (version suffix dropped); `pipeline-` pipelines keep a truncated prefix. `position` is `slice-N/M` and `subject` is the slice name; the legacy `merge-gate` @@ -582,14 +574,12 @@ every slice PR is purely slice-scoped. backstop; the stack is non-mergeable until the operator reconciles the missing context PR. -The `## Stack` block is the body's footer. The legacy plain-text line -(``Slice of pipeline . Stacked on top of -``.``) that used to follow it was dropped in #3115 — a repo-wide -search found no consumer parsing it. +The `## Stack` block is the body's footer; nothing follows it. (No +machine-readable trailing stack line is emitted — a repo-wide search found +no consumer parsing one.) -Task bullets carry full descriptions (the pre-#2745 300-char -truncation is removed) and a nested `Acceptance criteria:` line when -`task.acceptance_criteria` is set; since #3115 the whole task list +Task bullets carry full descriptions and a nested `Acceptance criteria:` +line when `task.acceptance_criteria` is set; the whole task list renders inside a collapsed `
` block so traceability does not crowd out the reviewer-facing summary. @@ -725,18 +715,17 @@ reconciler is fully functional, not a no-op: `list_remote_branches` and `rebase_onto` flow through the existing per-agent allowlists. `list_open_prs` uses the dedicated control-plane route `/api/v1/gh/list_open_prs` with launcher auth rather than a -synthetic agent session (refine-phase decision-15 intent preserved: no -general-purpose privileged gh-command surface is introduced — the route -accepts only `repo`/`limit` and constructs the fixed read-only argv -server-side). +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. -## Architect, planner & plan-reviewer prompt updates +## Architect, planner & plan-reviewer prompts -The dynamic prompt builders for `task_planner` and `reviewer_plan` were -extended to teach the agents the new schema and constraints: +The dynamic prompt builders for `task_planner` and `reviewer_plan` teach +the agents the slice schema and constraints: - **Architect (`architect`)** — sole authority for slice composition - (#2809, inverting HITL decision-6 opt-2). The architect prompt + ([#2809](https://github.com/jwbron/egg/issues/2809)). The architect prompt declares this authority explicitly and the architect emits a binding `architect-slices.yaml` scaffold alongside its analysis JSON (`{identifier}-architect-slices.yaml` under `.egg-state/agent-outputs/`). @@ -763,19 +752,20 @@ extended to teach the agents the new schema and constraints: and populating `serialized_chain_order` on the downstream slice. The fallback heuristic (`files_affected` Jaccard >0.3, then descending fan-out) is documented; the architect's own - ordering is the source of truth (HITL decision-17). The - planner preserves the field verbatim from the scaffold. + ordering is the source of truth. The planner preserves the field + verbatim from the scaffold. 4. The yaml-block key swap: `slices:` is canonical, `phases:` is backward-compat. -- **Plan reviewer (`reviewer_plan`)** — two new prompt sections: +- **Plan reviewer (`reviewer_plan`)** — two prompt sections: 1. *Forest-violation NACK*: when the populator left a "Plan ingestion REJECTED" block on `plan_review_feedback` (or a `forest_violation` log discriminator is present), the reviewer NACKs the **architect** - (not the planner — slice scaffold ownership moved to architect in - #2809) with the structured errors verbatim and instructs re-emission - of the scaffold with `serialized_chain_order` populated. - 2. *Slice-sizing NACK* (hard, judgment-based; #2809 inverts HITL - decision-6 opt-2). The reviewer is empowered AND required to + (slice scaffold ownership belongs to the architect, + [#2809](https://github.com/jwbron/egg/issues/2809)) with the structured + errors verbatim and instructs re-emission of the scaffold with + `serialized_chain_order` populated. + 2. *Slice-sizing NACK* (hard, judgment-based). The reviewer is empowered + AND required to hard-NACK the architect on `slice_size` when a slice is oversized for one BRC cycle. There is no fixed LOC budget — the rubric is judgment-based: NACK when a slice bundles >~3 distinct @@ -800,7 +790,7 @@ on parse failure. |---------|------|---------|----------| | `EGG_ORCH_MAX_PARALLEL_SLICES` | int | 1 | **Per-pipeline** wave slice spawn concurrency cap (fallback default). Enforced via `iter_ready` and mirrored on the wave's `ThreadPoolExecutor.max_workers`. Overridden per-pipeline by `PipelineConfig.max_parallel_slices` (set at pipeline creation), which takes precedence when non-null. | | `EGG_ORCH_GLOBAL_MAX_PARALLEL_SLICES` | int | 4 | **Process-wide** slice cap across ALL running pipelines (#2241 gap 1). Enforced by `orchestrator.global_slice_admit.try_admit()` in the run loop; deferred slices stay READY and re-yield next tick. | -| `EGG_ORCH_SLICE_LOCAL_MAX_CYCLES` | int | 3 | Per-slice BRC re-proposal ceiling before HITL escalation. *Currently inert — #2199 wires the trip flag through the BRC re-proposal loop.* | +| `EGG_ORCH_SLICE_LOCAL_MAX_CYCLES` | int | 3 | Per-slice BRC re-proposal ceiling before HITL escalation. *Currently inert — the run loop does not call `record_cycle`; see Known limitations.* | | `EGG_ORCH_SLICE_GLOBAL_MAX_CYCLES` | int | 10 | Pipeline-wide summed slice-cycle cap. *Currently inert — see local cycles row.* | | `EGG_ORCH_SLICE_FAILURE_GRACE_SECONDS` | float | 60.0 | Grace window before a failure cascade marks the downstream subtree `BLOCKED_ON_FAILED_DEPENDENCY`. | | `EGG_ORCH_STACKED_PR_RECONCILER_INTERVAL_SECONDS` | float | 30.0 | Reconciler polling cadence for orphaned child PRs. | @@ -832,66 +822,52 @@ The current global admit-state is exposed on operators can see when slices are queued behind the cap rather than wedged. -## Resolved design decisions (from refine phase) - -The slicing design was driven by 18 HITL decisions plus a feedback round -during refine. The most consequential are referenced inline above: - -- **decision-5** — concurrency: unbounded per wave; `max_parallel_slices` - is an operator-tunable soft cap. Initial cap was 5; lowered to 2 in - #2466 to constrain container/gateway resource pressure during the - implement phase. -- **decision-7** — schema rename ships with a one-version load-time - migration; legacy `phases[]` JSON keeps loading. -- **decision-9** — two-tier `max_cycles` (local 3, global 10). -- **decision-10** — failure-cascade hybrid (60 s grace + downstream-only - block). -- **decision-13** — lens reviewers run per-slice (cross-slice coherence - trade-off accepted). -- **decision-14** — BRC tracker keying: hybrid (`pipeline_id` for - cross-slice telemetry, nested `pipeline_id/slice_id` for `CONSENSUS_*`). -- **decision-15** — no general-purpose privileged gh-command surface; - reconciler reads via narrow read-only routes (per-agent allowlists for - `ls-remote`/rebase; control-plane fixed-argv `/api/v1/gh/list_open_prs` - with launcher auth, post [#2925](https://github.com/jwbron/egg/issues/2925)). -- **decision-16** — stacked-PR rebase: GitHub auto-retarget primary path, - reconciler safety net. -- **decision-17** — auto-serialization for would-be multi-parent slices: - architect-supplied `serialized_chain_order` is the source of truth (#2809). -- **decision-18** — forest constraint enforced at plan ingestion only; - multi-parent slices NACK the architect (#2809). -- **decision-20** — implement-phase run-loop wire-up (TASK-4-2, - TASK-4-4, TASK-5-1 invocation, TASK-5-3 scheduling). Operator chose - **opt-2** ("require the wire-up to land here before consensus"); the - run loop, slice-aware `ConcurrentPhaseExecutor`, integration-branch - creation, per-slice PR opening, and the reconciler thread all shipped - in this PR (commits `36d34da9612`, `7f4203469`, `97de1061d` plus - v1–v3 follow-ups). - -## Out of scope (#2137) +## Design rationale + +Why the model is shaped the way it is: + +- **Concurrency is an operator-tunable soft cap, not unbounded.** + `max_parallel_slices` bounds wave spawn concurrency; the default is low + ([#2466](https://github.com/jwbron/egg/issues/2466)) to constrain + container/gateway resource pressure during the implement phase. +- **The schema rename carries a one-version load-time migration** so legacy + `phases[]` JSON keeps loading rather than breaking on the field swap. +- **`max_cycles` is two-tier** (local per-slice + pipeline-global) so a + single thrashing slice and a pipeline-wide cycle budget can both trip HITL + escalation independently. +- **The failure cascade is hybrid** — a grace window before blocking, and + only the failed slice's downstream subtree is blocked — so a transient + failure can recover and siblings are unaffected. +- **Lens reviewers run per-slice**, accepting the cross-slice-coherence + trade-off in exchange for bounding each review's context to one slice. +- **BRC tracker keying is hybrid**: the bare `pipeline_id` is intended for + cross-slice telemetry and the nested `pipeline_id/slice_id` key isolates + `CONSENSUS_*` state (see Known limitations for the part not yet wired). +- **No general-purpose privileged gh-command surface.** The reconciler reads + via narrow read-only routes (per-agent allowlists for `ls-remote`/rebase; + the control-plane fixed-argv `/api/v1/gh/list_open_prs` with launcher + auth, [#2925](https://github.com/jwbron/egg/issues/2925)). +- **Stacked-PR rebase** relies on GitHub auto-retarget as the primary path, + with the reconciler as a safety net for paths that don't trigger it. +- **The forest constraint is enforced at plan ingestion** and would-be + multi-parent slices are serialised via architect-supplied + `serialized_chain_order`, which is the source of truth + ([#2809](https://github.com/jwbron/egg/issues/2809)). + +## Out of scope - **Per-slice MCP control verbs** (`restart_slice`, `get_slice_status`, - `list_slices`) — tracked in #2199. The slice-addressable hooks the - verbs will wrap are public on `SliceScheduler` already + `list_slices`) — tracked in + [#2199](https://github.com/jwbron/egg/issues/2199). The slice-addressable + hooks the verbs will wrap are public on `SliceScheduler` already (`teardown_slice`, `respawn_slice`, `get_slice_status`, plus the implicit `list_slices` view via the scheduler's contract reference). - Note: `restart_agent` with `slice_id` landed in #2399/#2410 — the - REST endpoint (`POST /api/v1/pipelines//agents//restart`) - now accepts `?slice_id=slice-N` (or `"slice_id"` in the body). -- **`record_cycle` two-tier wiring (#2199)** — `SliceScheduler`'s - `record_cycle` API and `hitl_escalator` hook are public and unit- - tested but the slice run loop does not call `record_cycle` on each - BRC re-proposal yet; the `EGG_ORCH_SLICE_LOCAL_MAX_CYCLES` / - `EGG_ORCH_SLICE_GLOBAL_MAX_CYCLES` env knobs are read but not - exercised today. -- **`EGG_PIPELINE_ID` cross-slice telemetry hybrid (#2199)** — the - agent CLI's `EGG_PIPELINE_ID` override scopes *every* outbound - message (CONSENSUS_*, HEARTBEAT, OVERSEER_ALERT) to the slice - tracker. Decision-14's hybrid scheme (cross-slice telemetry on the - bare pipeline tracker) requires a CLI-side message-type-aware router - to fully honour. Today the orchestrator-side cascade emission and - log lines provide the always-on `pipeline_id`-scoped fallback for - cascade visibility. + `restart_agent` already accepts a `slice_id`: the REST endpoint + (`POST /api/v1/pipelines//agents//restart`) takes + `?slice_id=slice-N` (or `"slice_id"` in the body). +- **The `record_cycle` cycle-cap wiring and the `EGG_PIPELINE_ID` + cross-slice telemetry hybrid** are both unfinished — see Known + limitations at the top of this page. - **Cross-slice architectural review** — `reviewer_code_holistic` runs per-slice; cross-slice cohesion is not re-checked once slices land.