Skip to content

[issue-3077][slice-4/6] Gateway artifact-read endpoint (strict... - #3143

Merged
jwbron merged 8 commits into
mainfrom
egg/issue-3077/slice-4
Jun 12, 2026
Merged

[issue-3077][slice-4/6] Gateway artifact-read endpoint (strict...#3143
jwbron merged 8 commits into
mainfrom
egg/issue-3077/slice-4

Conversation

@james-in-a-box

Copy link
Copy Markdown
Contributor

POST /api/v1/artifact/get serves committed artifact content by spec-registered name + hex-validated ref via git show on the authoritative repo (gateway blueprint forwarding to a new orchestrator route), with a sandbox-side helper. Unblocks #3002.

Base PR: #3139

What's in this PR

Commits (3):

.egg-state/brc-history/3077-implement-slice-4.json | 29822 +++++++++++++++++++++++++++++++++++++++
 .egg-state/brc-history/3077-implement-slice-4.md   | 24547 ++++++++++++++++++++++++++++++++
 gateway/artifact_api.py                            |   302 +
 gateway/gateway.py                                 |    11 +
 gateway/tests/conftest.py                          |    18 +
 gateway/tests/test_artifact_api.py                 |   535 +
 orchestrator/api.py                                |     4 +
 orchestrator/routes/artifacts.py                   |   448 +
 orchestrator/tests/test_artifact_routes.py         |   655 +
 sandbox/scripts/egg-artifact                       |   272 +
 10 files changed, 56614 insertions(+)

This slice

Gateway artifact-read endpoint (strict, name-resolving) + sandbox helper

Files affected:

  • orchestrator/routes/artifacts.py
  • gateway/artifact_api.py
  • gateway/gateway.py
  • sandbox/scripts/egg-artifact
  • orchestrator/tests/test_artifact_routes.py
  • gateway/tests/test_artifact_api.py
Tasks (4) + acceptance criteria
  • task-4-1: Orchestrator artifact-read route: add orchestrator/routes/artifacts.py (NEW), registered per the existing orchestrator/routes/ convention. Request: artifact name (spec-registered), ref (hex-validated commit SHA), and the issue/pipeline identifier. Resolves the path via artifact_spec.resolve_artifact_path and returns the content of git show <ref>:<path> from the authoritative repo, applying the existing output caps with a truncated flag. STRICT per HITL Q2: the schema has NO path field; an unregistered name returns 400 listing registered names; a non-hex ref returns 400; an unresolvable ref or path-absent-at-ref returns a structured 4xx, never a 500. Cross-link Deploy egg on GKE (minimal first deploy): Terraform-provisioned cluster + GKE overlay + registry pull path #3002 in the module docstring.
    • Acceptance criteria: - Registered name + valid ref returns committed content byte-identical to the blob; oversized content is capped with truncated: true. - Unregistered name → 400 listing registered names; non-hex ref → 400; absent-at-ref → structured 4xx. - No request field accepts a repo path (schema-level). - Works with no shared object store between agent worktree and orchestrator repo (server-side git show only).
  • task-4-2: Gateway blueprint: add gateway/artifact_api.py (NEW) implementing POST /api/v1/artifact/get, modeled on gateway/contract_api.py — session auth, role taken from the session (never the request body), forward to the orchestrator route from TASK-4-1, propagate structured 4xx errors and the truncated flag verbatim. Wire the blueprint into the gateway app in gateway/gateway.py alongside the contract API registration.
    • Acceptance criteria: - Authenticated session can fetch a registered artifact through the gateway; response matches the orchestrator route's. - Unauthenticated/unknown-session requests are rejected the same way contract_api rejects them. - Orchestrator 4xx bodies pass through unaltered (no 500 wrapping).
  • task-4-3: Sandbox-side read helper sandbox/scripts/egg-artifact (NEW): a thin CLI wrapping POST /api/v1/artifact/get following the existing sandbox script conventions (gateway base URL/session wiring as in sandbox/scripts/jira), with egg-artifact get <name> --ref <sha> [--identifier <id>] semantics: content to stdout, structured errors to stderr, non-zero exit on failure, an explicit notice when truncated is set. This is the single verb prompt templates reference after slice 5 deletes path prose.
    • Acceptance criteria: - egg-artifact get plan-draft --ref <sha> prints served content and exits 0. - Gateway 4xx surfaces as a clear stderr message and non-zero exit (no stack trace); truncated responses print a notice. - The helper performs no git invocation (served read only).
  • task-4-4: Endpoint tests: new orchestrator/tests/test_artifact_routes.py (happy path byte-equality, unregistered-name 400 listing registered names, non-hex ref 400, absent-at-ref structured 4xx, cap + truncated flag at the boundary) and new gateway/tests/test_artifact_api.py following the test_contract_api.py style (session auth, forwarding, 4xx passthrough, no path field accepted).
    • Acceptance criteria: - All strict-resolution rejection branches covered on both sides. - Happy path asserts byte-equality with the committed blob. - Cap behavior asserted at the boundary (content at/over the cap, truncated flag set).

Stack

egg and others added 3 commits June 12, 2026 00:15
… + sandbox)

Land the strict, spec-driven artifact-read endpoint and its sandbox
helper so reviewers can fetch a peer's coordination artifact by
spec-registered name + commit SHA — no shared object store, no
prompt-prose fetch instructions, no raw path on the wire.

* TASK-4-1: orchestrator/routes/artifacts.py — new POST
  /api/v1/artifacts/get route. Resolves the path via
  egg_contracts.artifact_spec.resolve_artifact_path (slice-2),
  identifier via routes.pipelines._pipeline_identifier (lazy import,
  same convention propose-time validation uses), worktree via
  contract_store.resolve_pipeline_worktree with a fallback to the
  main repo, then runs ``git show <ref>:<path>`` against the
  authoritative repo (no shared-object-store assumption). Output is
  capped at 256 KiB with the ``truncated`` flag, mirroring the
  event_prompt git-log cap. STRICT per HITL Q2: the schema has no
  ``path`` field; unknown name → 400 listing registered names; non-hex
  ref → 400; ``invalid object name`` / ``unknown revision`` → 422;
  path-absent-at-ref → 404. Subprocess timeouts and OSErrors land as
  503 — never 500. Registered in orchestrator/api.py alongside the
  other route blueprints.
* TASK-4-2: gateway/artifact_api.py — gateway blueprint modeled on
  contract_api.py. Session auth, role from session metadata (never the
  request body), pipeline_id falls back to the session id. Same wire-
  level path-field rejection so a misbehaving client can't reach the
  orchestrator's schema check, plus pre-flight non-hex-ref rejection
  to avoid the round-trip. Orchestrator 4xx bodies pass through
  verbatim (no 500-wrap); connection failures map to 502.
  Registered in gateway/gateway.py next to contract_bp and phase_bp.
* TASK-4-3: sandbox/scripts/egg-artifact — bash CLI following the
  sandbox/scripts/jira conventions (GATEWAY_URL + EGG_SESSION_TOKEN
  fail-closed, gateway health probe, RETURN-trap tmpfile cleanup).
  ``egg-artifact get <name> --ref <sha> [--identifier <id>] [--repo
  <hint>]`` writes the served content verbatim to stdout; the
  ``truncated`` flag becomes a stderr notice; structured errors print
  ``ERROR: <message>`` plus the registered-names detail on the
  unknown-name 400. ``--path`` is rejected locally before the request
  fires.

Cross-links:
- #3002 (this endpoint is the blocking prerequisite for replacing the
  shared-object-store / per-agent-worktree coordination channel).
- #3077 slice-2 (artifact_spec registry — the single source of name →
  path resolution this route serves).
- #3077 slice-3 (the propose-time presence validator that runs the
  same ``git show`` against the same registry).
… contract (#3077 TASK-4-4)

Adds the slice-4 tester suite for #3077: byte-equality + strict-mode
rejection contract on the orchestrator's new /api/v1/artifacts/get
route, and the session-auth + verbatim 4xx passthrough contract on the
gateway's /api/v1/artifact/get forwarder.

orchestrator/tests/test_artifact_routes.py (new):

* Happy path: registered name + 40-hex ref returns the git-show stdout
  byte-identical; envelope carries name/ref/path/content/truncated;
  the spawned git command targets the worktree the route resolved
  (not a per-agent worktree) — pins the authoritative-repo invariant.
* Qualified pipeline_id (issue-3077-replan) resolves to the qualified
  .egg-state/drafts/issue-3077-replan-plan.md path through
  routes.pipelines._pipeline_identifier; pins the slice-2 collision
  fix for concurrent runs on the same issue.
* Non-UTF-8 blob round-trips through errors='replace' instead of
  500ing — defense against a binary-ish committed artifact.
* Strict rejections (HITL Q2):
  - unregistered name => 400 listing every registered alternative
    (analysis-draft, plan-draft, architect-output, architect-slices,
    risk-analyst-output), so egg-artifact can render a usable hint;
  - 8 non-hex / malformed refs (branch name, HEAD, shell metachar
    injection, path traversal, too-short, empty, non-hex 40-chars)
    => 400 without ever spawning git show — pins the pre-flight that
    keeps shell metachars off the wire;
  - absent-at-ref / unresolvable-ref => structured 4xx with
    success=false (404 vs 422 left to the coder, never 500);
  - body 'path' field => 400 + git show never invoked;
  - TimeoutExpired => 503 (not 500), so the gateway's 502 wrap stays
    reserved for "orchestrator unreachable".
* Cap boundary: monkeypatch _ARTIFACT_MAX_BYTES to 16 to exercise the
  at-cap (truncated=false) and over-cap (truncated=true, content is
  exactly the cap-sized head slice) branches without materialising a
  ~256 KiB response. raising=True so a future rename of the constant
  ratchets both the test and any docs that reference it.
* Schema: missing body => 400; each required field individually
  required and the rejection message names the missing field;
  PipelineNotFoundError from get_state_store_for_pipeline => 4xx.

A composite _ArtifactRouteSeams context manager patches the three
lazy seams the route uses — routes.get_state_store_for_pipeline,
contract_store.resolve_pipeline_worktree, and
routes.artifacts.subprocess.run — so test bodies read as one-liner
spec declarations and the lazy-import contract is not broken.

gateway/tests/test_artifact_api.py (new):

* Forwarding: POST /api/v1/artifact/get reaches /api/v1/artifacts/get
  on the orchestrator with X-Egg-Role set from the session
  (reviewer_code -> reviewer) — NEVER from the request body, the same
  anti-forgery rule contract_api.mutate_contract enforces.
* No path field: body-level 'path' is rejected at the gateway BEFORE
  forwarding; urlopen is asserted never called so the malicious body
  never lands in the orchestrator audit log.
* Session auth: unauthenticated request => 401/403 and the forwarder
  is never invoked — tighter than test_contract_api.py.
* Local schema: missing 'ref' => 400 from the gateway, no upstream
  round-trip; saves orchestrator load for missing-field cases.
* 4xx passthrough: orchestrator strict-mode bodies for unregistered
  name (with registered-names list), non-hex ref, and absent-at-ref
  are forwarded verbatim — egg-artifact reads .message directly to
  print the user-facing stderr line, so re-encoding would silently
  drop the registered-names hint.
* Orchestrator unreachable => 502 (distinguishes "down" from
  "absent").
* truncated=true passes through unchanged to the sandbox helper.
* URL-prefix ratchet: /api/v1/artifact/get is the only registered
  URL on the gateway — the plural /api/v1/artifacts/get returns
  Flask 404 (a future "let's align the prefixes" rewrite has to
  confront this ratchet first).

gateway/tests/conftest.py:

* Pre-load gateway/artifact_api.py through the existing
  _load_module_with_replaced_imports bootstrap, guarded with
  Path.exists() so the conftest stays runnable on tester branches
  where the producer hasn't landed yet (BRC parallel mode). Threads
  the 'from .artifact_api import' replacement into the gateway
  module loader so gateway.py's blueprint registration resolves
  against the pre-loaded module.

Note on test execution: the sandbox cannot run pytest (pip install
fails on uv -> pygments TLS verification, same as slice-3's notes),
so checks were run as ``ruff check`` + ``ruff format --check`` only.
Reviewers run the suite in their own environment via the
consensus_wrapper merge of the slice-4 coder + tester branches.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@james-in-a-box

This comment has been minimized.

@james-in-a-box

james-in-a-box Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor Author
Autofix tracking
{"Lint/Python": 1, "Test/Unit Tests": 1}

@james-in-a-box

This comment has been minimized.

@james-in-a-box

This comment has been minimized.

…ision

Python's logging module reserves 'name' (the logger name) as a LogRecord
attribute. Passing it via extra={} raises KeyError on _log/makeRecord,
which 500'd every artifact_get hit and broke 5 test_artifact_routes
happy-path / cap-boundary tests.
@james-in-a-box

This comment has been minimized.

@james-in-a-box james-in-a-box Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Contract Verification — PR #3143 (issue-3077 slice-4/6)

Verdict: Approve (contract-compliant). Posted as a comment (self-authored PR). All four tasks are fully implemented and every acceptance criterion is objectively satisfied by code + tests. Two minor, non-blocking advisory notes at the end.

Note on criterion marking: the orchestrator is unreachable from this session, so egg-contract verify-criterion cannot persist. The contract's top-level acceptance_criteria array is also empty ([]) — criteria are stored as task-level strings, so there are no ac-N identifiers to mark. Verification is recorded per-task below.

TASK-4-1 — Orchestrator artifact-read route (orchestrator/routes/artifacts.py) ✅

  • Byte-identical content + truncated flag_decode_with_cap returns the blob unchanged below _ARTIFACT_MAX_BYTES (256 KiB) and a capped head slice with truncated: true above it. Pinned by test_happy_path_byte_equality and the two TestArtifactGetCap boundary tests (at-cap → false, over-cap → true).
  • Strict rejections — unregistered name → 400 with registered_names list (spec_by_name KeyError, line 386-393); non-hex ref → 400 via _HEX_REF_RE before any git invocation (line 379); absent-at-ref → 404, unresolvable-ref → 422 (_run_git_show classifies git stderr). Timeout/OSError → 503, never 500. All covered by TestArtifactGetRejections (incl. shell-metachar/traversal refs) + test_subprocess_timeout_returns_503_not_500.
  • No path field (schema-level)if "path" in body → 400 at line 360, asserted by test_path_field_is_rejected_400.
  • No shared object store — server-side git show -C <worktree> only (list-form, no shell); worktree resolved via resolve_pipeline_worktree with a main-repo fallback. The happy-path test pins the authoritative-repo invariant by asserting the git -C <worktree> show argv.
  • #3002 cross-linked in the module docstring. ✓

TASK-4-2 — Gateway blueprint (gateway/artifact_api.py, gateway/gateway.py) ✅

  • Authenticated fetch + response parity@require_session_auth, forwards to orchestrator /api/v1/artifacts/get via _proxy_post, relays the payload verbatim (test_forwards_to_orchestrator_with_role_header).
  • Role from session, never body_role_from_context reads session metadata (X-Egg-Role only behind EGG_ENABLE_TEST_ROLE_HEADER), the request body's role is never consulted; forwarded as X-Egg-Role. Path field is stripped at the gateway before forwarding (test_forwarded_body_strips_path_field asserts urlopen is never called).
  • Auth rejection paritytest_requires_session_auth confirms unauthenticated requests are rejected and the forwarder is never invoked.
  • 4xx passthrough, no 500-wrap_proxy_post forwards HTTPError.code + body verbatim; only true connection failure maps to 502. Pinned by test_orchestrator_4xx_relayed_verbatim and test_orchestrator_unreachable_502. Blueprint registered alongside contract_bp/phase_bp (gateway.py:508-517). ✓

TASK-4-3 — Sandbox helper (sandbox/scripts/egg-artifact) ✅

  • get <name> --ref <sha> [--identifier <id>] [--repo <hint>] writes content to stdout with end='' (newline-preserving), exits 0 on success.
  • Gateway 4xx → clean ERROR: <message> on stderr (+ registered names: line for unknown-name), non-zero exit, no stack trace; truncated → explicit stderr notice.
  • No git invocation — served read only; --path rejected locally before the request fires. Fail-closed on missing GATEWAY_URL/EGG_SESSION_TOKEN + health probe, matching sandbox/scripts/jira conventions. ✓

TASK-4-4 — Endpoint tests ✅

  • orchestrator/tests/test_artifact_routes.py (16 tests) and gateway/tests/test_artifact_api.py (11 tests) cover every strict-rejection branch on both sides, byte-equality on the happy path, and cap behavior at/over the boundary. Gateway tests assert role-from-session, path-strip-before-forward, verbatim 4xx passthrough, 502-on-unreachable, and the /api/v1/artifact/get (singular) vs orchestrator /api/v1/artifacts/get (plural) URL ratchet. ✓

Cross-cutting checks

  • Logger-collision fix (final commit d852982) is complete: the reserved LogRecord key name was renamed to artifact_name in the extra={} dict (line 430). The remaining keys (pipeline_id, ref, path, bytes, truncated, spec_phase, spec_producer) do not collide with any reserved LogRecord attribute.
  • No injection surface: ref is hex-validated and rel_path is spec-derived (resolve_artifact_path); git show uses list-form argv with no shell.
  • No orphaned code / scope creep: all changes map to the four tasks (the two .egg-state/brc-history/* files are pipeline-generated, and the test_consensus_wrapper.py delta is the automated-formatting fixup commit).

Non-blocking advisory notes

  1. orchestrator/routes/artifacts.py decodes git-show output with errors="replace", so a genuinely non-UTF-8 blob is not byte-identical (U+FFFD substitution). This is a documented, deliberate tradeoff and correct for the registered artifacts, which are all markdown/JSON text drafts — flagging only so a future binary artifact type doesn't silently inherit lossy reads.
  2. sandbox/scripts/egg-artifact: under set -euo pipefail, a hard curl failure on http_code=$(curl …) would exit via set -e before the explicit if [ $curl_exit -ne 0 ] handler runs, making that branch partly unreachable. The preceding gateway health probe covers the common case, and curl -s returns 0 for HTTP 4xx (routed through the Python parser), so the user-facing behavior is correct; the dead-ish branch is cosmetic.

Neither note blocks merge.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@egg-reviewer egg-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: PR #3143 — Gateway artifact-read endpoint (#3077 slice-4)

Thorough pass over all 9 code files (the .egg-state/brc-history/* artifacts are skipped per review conventions). No blocking issues. The strict "no-path" design is enforced at three layers and the error taxonomy is correct. A few non-blocking suggestions below.

What I verified

Security / strict-resolution (the core invariant) — holds.

  • ref is gated by ^[0-9a-fA-F]{7,40}$ on both the gateway (artifact_api.py:99,260) and orchestrator (artifacts.py:104,379) before any subprocess runs. This blocks shell metacharacters, symbolic refs (HEAD, branch names), and traversal-style refs. subprocess.run uses list-form argv with no shell, so even a hypothetical bad ref can't inject.
  • The forbidden path field is rejected at the gateway wire boundary (artifact_api.py:247) and re-rejected at the orchestrator (artifacts.py:360) — defense in depth, and the orchestrator rejection lists registered names.
  • Role is taken from the session (_role_from_context), never the request body — matches contract_api.py. The body's role is never consulted.
  • I empirically confirmed git show <ref>:<path> does not normalize .. (it treats .. as a literal, non-existent tree entry), so the agent-controlled identifier interpolated into the path template cannot traverse out of .egg-state/drafts/ / .egg-state/agent-outputs/. The strict guarantee survives even the identifier vector.

Correctness / robustness — correct.

  • Error taxonomy is right: path-absent → 404, unresolvable ref → 422, timeout/OSError → 503, and crucially never a 500 (which the gateway would 502-wrap as "unreachable" — wrong category). test_subprocess_timeout_returns_503_not_500 and test_absent_at_ref_returns_structured_4xx_not_500 pin this.
  • Non-UTF-8 blobs decode with errors="replace" so a binary-ish artifact can't 500 the route (_decode_with_cap, artifacts.py:327).
  • Cap boundary is a clean <= (intact, truncated:false) vs > (head slice, truncated:true), re-decoded at the byte boundary. Pinned at the boundary by the cap tests.
  • Worktree resolution prefers the shared pipeline worktree (same seam slice-3's propose-time validator reads from) and falls back to the main repo post-pruning. End-to-end path is consistent with the established slice-3 git show mechanism, so the served read is functional in its normal path.
  • The nameartifact_name logger-extra rename (final commit) correctly avoids the LogRecord collision.

Tests — exercise the production path.

  • Both suites drive the real Flask blueprints; seams (subprocess.run, get_state_store_for_pipeline, resolve_pipeline_worktree, urlopen) are mocked but the route/forwarder logic runs. The happy-path byte-equality blob is hand-authored, not regenerated from the implementation — not a self-seeding golden. Given git init is blocked in the sandbox, mocking git show output is the right call.
  • test_consensus_wrapper.py changes are pure auto-formatting (line reflow), no logic change.

Non-blocking suggestions

  1. egg-artifact: the curl_exit error branch is unreachable under set -euo pipefail. At sandbox/scripts/egg-artifact:121, http_code=$(curl ...) is a plain assignment; when curl fails at the connection level (exit 7/28), set -e exits the script before line 129–134, so the "Failed to connect to gateway" stderr message never prints — the user gets a silent non-zero exit. The health check pre-empts most cases, but a transient mid-request drop hits this. Consider http_code=$(curl ...) || curl_exit=$? (or a local set +e window) so the diagnostic actually surfaces. The non-zero exit contract still holds; only the message is lost.

  2. identifier is interpolated into the git path without a shape check (defense-in-depth). Git's non-normalization of .. neutralizes traversal today, so this is not a vulnerability — but the explicit identifier still lets a caller influence the resolved path (e.g. a subdirectory) despite the deliberately strict no-path design. A cheap schema-level guard on the explicit-identifier branch (digits, or an issue-…-shaped safe charset; reject / and ..) would make the strict guarantee hold at the wire rather than relying on git show's pathspec semantics. artifacts.py:158-165 is the spot.

  3. Gateway forwards X-Egg-Role but the orchestrator route ignores it. Unlike contracts.py (which role-validates mutations via _role_from_request), artifacts.get_artifact never reads the role — reads are intentionally not role-gated. The forwarding is harmless future-proofing, but a one-line comment on the orchestrator side noting "reads are not role-gated; role forwarded for audit/future use" would prevent a future reader from assuming the consumer_roles allow-list is enforced here.

Solid, well-documented slice. Ship-ready modulo the suggestions above (all non-blocking).

— Authored by egg

@james-in-a-box

This comment has been minimized.

@james-in-a-box

This comment has been minimized.

… test_consensus_wrapper.py

Took slice-3's deletion of the unused 'repo, _ = ...' assignment in
_run_harness (dead code; repo is never used in the function) over
slice-4's automated single-line reformatting of the same lines.
@james-in-a-box

Copy link
Copy Markdown
Contributor Author

Conflict Resolution Summary

Resolved merge conflicts with egg/issue-3077/slice-3 (merged in via merge commit, not rebase):

File Category Resolution
orchestrator/tests/test_consensus_wrapper.py Formatting vs. deletion Took slice-3's deletion of the unused repo, _ = ... assignment in _run_harness

Detail

The single conflict was in _run_harness. Tracing the merge base (a6785bf):

  • Merge base had a multi-line dead-code assignment repo, _ = (None, None) if not (tmp_path / "repo").exists() else (tmp_path / "repo", None)repo is never used in _run_harness (the next line uses str(tmp_path / "repo") directly).
  • This PR (slice-4) collapsed that assignment onto one line (an automated formatting fix).
  • slice-3 (base) deleted the dead-code line entirely.

Since the line is genuinely unused, slice-3's deletion is the cleaner, semantically-correct resolution and supersedes a pure-formatting change of dead code. No functional behavior is lost.

Verification

  • ruff check / ruff format — pass on the resolved file and across the tree.
  • python3 -m py_compile — pass.
  • make lint surfaced 3 pre-existing mypy errors in shared/egg_agent/client.py, which is byte-identical across the merge base, both parents, and the merge commit — baseline noise unrelated to this resolution.
  • The 4 git-using tests in TestSyncOutcomesAndBanner cannot run in this sandbox (the gateway blocks git init); they fail at _init_repo before the edited _run_harness code is reached. CI will exercise them on the host.

Please review: the resolution removes one line of unused test scaffolding — low risk, but confirm you're comfortable that repo was indeed unused in _run_harness.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@egg-reviewer egg-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review: PR #3143 — merge of slice-3 into slice-4 (#3077 slice-4)

Re-reviewed the delta since my prior review at d85298206. The only new work is the merge commit f3cbf922 (slice-3 → slice-4) and its single conflict resolution. No new blocking issues; the resolution is correct.

Verified

Slice-4 feature code is untouched by the merge. git diff d85298206..HEAD shows zero changes to gateway/artifact_api.py, orchestrator/routes/artifacts.py, orchestrator/routes/artifacts.py tests, sandbox/scripts/egg-artifact, or gateway/tests/test_artifact_api.py — byte-identical to what I reviewed before. The merge only pulled in slice-3 base content plus one conflict fix, so my earlier comment-verdict assessment of the feature still stands.

Conflict resolution is correct (confirming the author's ask). The sole conflict was in _run_harness (test_consensus_wrapper.py). I independently confirmed the deleted line —
repo, _ = (None, None) if not (tmp_path / "repo").exists() else (tmp_path / "repo", None)
— was dead: _run_harness uses str(tmp_path / "repo") directly and never references the repo local (the repo in _init_repo is a separate scope, unaffected). Taking slice-3's deletion over slice-4's reformat-of-dead-code loses no behavior.

The del monkeypatch change is internally consistent. I checked the apparent contradiction — the resolved _script drops monkeypatch.setenv("EGG_BRC_EVENT_PUMP", "true") while ~30 sibling tests still set it. TestEventPumpTemplateSelection pins that production build_consensus_wrapped_command no longer reads the flag (it's inert post task-4-2). So the dropped setenv was a no-op, correctly removed; the remaining setenv calls are harmless no-ops, several of which exist precisely to assert inertness.

Banner-wording update applied consistently. Slice-3's reworded "use the rendered git log / git show fallback commands" banner landed in both the production SYNC_FAILURE_BANNERS append sites (consensus_wrapper.py:553,571) and the test assertions/regex that pin them (test_consensus_wrapper.py:1555-1601) — no code/test drift.

No conflict markers anywhere in the changed tree.

Carried-over non-blocking suggestions (unchanged, not addressed by this merge)

These were advisory in my prior review and remain so — none block merge:

  1. egg-artifact:121curl_exit diagnostic branch is unreachable under set -euo pipefail; non-zero exit contract still holds, only the message is lost.
  2. artifacts.py:158-165 — a shape check on the explicit identifier would harden the strict no-path guarantee at the wire (git's .. non-normalization neutralizes it today; not a vuln).
  3. artifacts.get_artifact — a one-line comment noting reads are intentionally not role-gated (role forwarded for audit/future use).

Clean merge. Ship-ready modulo the long-standing non-blocking notes.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@james-in-a-box james-in-a-box Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Contract Re-Verification — PR #3143 (slice-4/6)

Verdict: Approve (clean re-review). Incremental verification against my prior review at d85298206a9b17474d0a2aa1fb8286c1acefd57a. The only PR-authored change since then is the merge commit f3cbf922 ("Merge origin/egg/issue-3077/slice-3 into slice-4"). No slice-4 feature regression; all previously-verified acceptance criteria still hold.

What changed since last review

git log d852..HEAD --not origin/egg/issue-3077/slice-3 yields exactly one commit: the slice-3→slice-4 merge. The diff splits cleanly:

  • vs slice-3 parent (71f3872d): the slice-4 feature files appear as pure additions (gateway/artifact_api.py, gateway/gateway.py, orchestrator/routes/artifacts.py, orchestrator/api.py, sandbox/scripts/egg-artifact, and both test files) — the merge preserved slice-4's work intact.
  • vs slice-4 parent (d852): only slice-3's own independent changes landed (event_prompt.py, pipelines.py, signals.py, coordination-state.md, REVIEWER-SYNC.md, slice-3 tests), plus the documented conflict resolution.

Verification of the merge

  1. slice-4 feature files are byte-identical to d852git diff d852..HEAD reports 0 changed lines for every TASK-4-1…4-4 deliverable. The implementation I verified previously (orchestrator artifact route, gateway blueprint, egg-artifact helper, endpoint tests) is unchanged, so AC for task-4-1 through task-4-4 remain satisfied.
  2. Conflict resolution is soundorchestrator/consensus_wrapper.py resolved to slice-3's version (identical to parent 71f3872d), differing from slice-4 only in non-functional WARNING-banner prose (slice-3's text is more accurate: "use the rendered git log / git show fallback commands" vs "the git show commands below"). Matches the commit message's stated intent (took slice-3's deletion of the unused repo, _ = … over slice-4's reformatting).
  3. test_artifact_spec.py refactor came wholesale from slice-3 (merge result identical to slice-3 parent) — delegates the all_specs fixture to egg_contracts.artifact_spec.all_specs (registered_specs). Verified the symbol exists (shared/egg_contracts/artifact_spec.py:188), alongside spec_by_name, specs_for, resolve_artifact_path — all imports resolve.
  4. No regressions to slice-4 dependencies_get_draft_path still present (orchestrator/routes/pipelines.py:5250); slice-3's pipelines.py change only swaps bare filenames for resolve_artifact_path spec-name lookups (the slice-2 single-source-of-truth ratchet), which does not affect the artifact-read endpoint.
  5. No leftover conflict markers anywhere in the merged delta.

Note on criterion marking

The orchestrator was unreachable during this review (egg-orch health → Status: UNREACHABLE; gateway healthy), so egg-contract verify-criterion could not write. Because the slice-4 feature is byte-identical to the state I verified at d852, no criteria changed status and no re-marking is required — the prior verifications stand. If the orchestrator's contract needs the verifications re-applied, they can be replayed once it is reachable; no code change is needed.

No contract violations found. The merge is mechanical and correctly preserves the slice-4 contract.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@james-in-a-box

This comment has been minimized.

- egg-artifact: capture curl exit with '|| curl_exit=$?' so a hard
  connection failure surfaces the diagnostic instead of being swallowed
  by set -e before the error branch runs.
- artifacts route: add a defense-in-depth shape check on the explicit
  'identifier' (safe slug, no '/' or '..') so the strict no-path
  guarantee holds at the wire rather than relying on git-show pathspec
  semantics. Reject unsafe identifiers with a structured 400.
- artifacts route: document that reads are intentionally not role-gated
  (role forwarded for audit/future use).
- Add parametrized test covering unsafe-identifier rejection.
@james-in-a-box

Copy link
Copy Markdown
Contributor Author

Review feedback — dispositions

Thanks for the thorough passes. Both bots approved with non-blocking advisory notes; I've addressed all three actionable suggestions in-PR (commit 89daad6).

1. egg-artifact:121curl_exit diagnostic branch unreachable under set -euo pipefail (egg-reviewer #1, james-in-a-box advisory #2)
fixed-in-PR (commit 89daad6). The plain http_code=$(curl …) assignment now ends in || curl_exit=$?, so a hard connection failure (exit 7/28) no longer trips set -e before the handler runs — the "Failed to connect to gateway" message and curl stderr now surface as intended. The non-zero exit contract is unchanged.

2. artifacts.py:158-165 — shape check on the explicit identifier (egg-reviewer #2)
fixed-in-PR (commit 89daad6). Added _SAFE_IDENTIFIER_RE (^(?!.*\.\.)[A-Za-z0-9][A-Za-z0-9._-]*$): an explicit string identifier must be a safe slug — alphanumeric start, no /, no .. substring. Unsafe identifiers are rejected with a structured 400 before any subprocess runs, so the strict no-path guarantee holds at the wire rather than relying on git show's .. non-normalization. Digit and issue-3077-style identifiers still pass. Pinned by a new parametrized test (test_unsafe_explicit_identifier_400, 8 cases).

3. artifacts.get_artifact — note that reads are not role-gated (egg-reviewer #3)
fixed-in-PR (commit 89daad6). Added a comment at the top of the handler: reads are intentionally not role-gated; the gateway forwards X-Egg-Role for audit/future use, and the strict no-path design — not a per-role allow-list — is the access boundary here.

4. Non-UTF-8 blobs decode with errors="replace" (james-in-a-box advisory #1)
disagree (documented, deliberate tradeoff — not a defect). As the note itself states, this is correct for the registered artifacts (all markdown/JSON text) and was flagged only as a forward-looking caution for a hypothetical future binary artifact type. No such type exists today, and adding binary handling now would be speculative. The existing _decode_with_cap behavior is the right call for the current spec set.

Verified locally: ruff check, ruff format --check, shellcheck, and the targeted test_artifact_routes.py suite all pass.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@egg-reviewer egg-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review: PR #3143 — slice-4 review-feedback hardening (#3077 slice-4)

Re-reviewed the delta since my prior review at f3cbf922. The only new work is commit 89daad6 ("Address review feedback"), which addresses all three of my prior non-blocking suggestions. No blocking issues — the changes are correct.

Verified — all three suggestions correctly addressed

1. egg-artifact curl_exit unreachable branch — fixed correctly. The plain assignment http_code=$(curl …) now ends in || curl_exit=$? (sandbox/scripts/egg-artifact:130). I confirmed the root cause: with set -euo pipefail (line 24) and the local http_code declaration separate from the assignment, the assignment was a plain command whose non-zero exit (curl 7/28) tripped set -e and aborted the script before local curl_exit=$? ran — the "Failed to connect to gateway" branch was genuinely dead. The || curl_exit=$? short-circuits set -e and captures curl's real exit; on success curl_exit stays 0. No pipefail interaction (no pipe in the curl line). Correct fix.

2. Explicit-identifier shape check — correct, exercises the production path. _SAFE_IDENTIFIER_RE = ^(?!.*\.\.)[A-Za-z0-9][A-Za-z0-9._-]*$ (artifacts.py:115) runs in _resolve_identifier before the .isdigit() coercion and before any subprocess. I verified the regex against every test case and a few of my own:

  • All 8 rejection cases (../../../etc/passwd, .., issue/../3077, a/b, issue 3077, issue;rm, "", .hidden) → rejected.

  • Valid ids (3077, issue-3077, plan.draft) → accepted.

  • Ordering bonus: because the regex (ASCII-only charset) runs first, the str.isdigit() Unicode-digit gotcha ('٣'.isdigit() == True) can never reach int() — non-ASCII fails the charset.

    The new test_unsafe_explicit_identifier_400 (8 parametrized cases) drives the real wire path (client.postget_artifact_resolve_identifier) and asserts subprocess_mock is not called — it exercises production code, not a hand-built fixture. Single caller of _resolve_identifier (the route), so the early-return is self-contained.

3. Role-gating comment — accurate. The new comment at get_artifact (artifacts.py:375) correctly documents that reads are intentionally not role-gated; X-Egg-Role is forwarded for audit only and the route never consults it. Matches actual behavior (no role read anywhere in the handler).

Non-blocking (one new minor nit)

  • artifacts.py:175 — use re.fullmatch (or \Z) instead of re.match. Python's $ matches before a single trailing newline, so _SAFE_IDENTIFIER_RE.match("issue-3077\n") and …match("3077\n") both succeed. The impact is benign — a trailing-newline identifier still can't introduce .. or / (multi-line abc\n..\n is correctly rejected because . doesn't cross newlines), so the worst case is a non-existent interpolated path → truthful 404. Since this whole check is defense-in-depth, tightening it to fullmatch/\Z closes the last gap at no cost. Not blocking.

Clean, well-tested follow-up. Ship-ready modulo the optional fullmatch tightening.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@james-in-a-box james-in-a-box Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Contract Re-Verification — PR #3143 (slice-4)

Verdict: Approve (no new contract violations; the delta strengthens previously-verified criteria).

I previously verified this PR at f3cbf922. After fetching and deepening, the only PR-authored change since then is a single commit:

  • 89daad6cAddress review feedback: harden egg-artifact + identifier shape check

git log f3cbf922..HEAD --not origin/egg/issue-3077/slice-3 -p shows three files touched, all mapping cleanly to existing slice-4 tasks. No new tasks introduced; the change is additive hardening.

Delta review

1. orchestrator/routes/artifacts.py (task-4-1) — identifier shape check + role-gate doc

  • New _SAFE_IDENTIFIER_RE = ^(?!.*\.\.)[A-Za-z0-9][A-Za-z0-9._-]*$ rejects any explicit identifier containing .., /, whitespace, or shell metachars with a structured 400, applied in _resolve_identifier (artifacts.py:175) before resolve_artifact_path (artifacts.py:437) interpolates it into the path template. Correctly placed — the rejection happens at the wire, before any subprocess.
  • This is pure defense-in-depth for the "No request field accepts a repo path (schema-level)" criterion (task-4-1): it makes the strict no-path guarantee hold independent of git show pathspec semantics. It does not weaken any prior behavior — valid identifiers (3077, issue-3077) still match and the all-digit→int coercion is preserved.
  • Added a docstring on get_artifact documenting that reads are intentionally not role-gated (X-Egg-Role forwarded for audit/future use only). Accurate to the code — the route never consults the role, consistent with the slice design where the no-path boundary is the access control.

2. sandbox/scripts/egg-artifact (task-4-3) — curl exit capture

  • http_code=$(curl …) || curl_exit=$? replaces the prior local curl_exit=$? that followed the assignment. This fixes a real bug: under set -e, a hard connection failure (curl exit 7/28) made the http_code=$(…) assignment abort the script before the diagnostic branch ran, silently losing the "Failed to connect to gateway" message. The || curl_exit=$? idiom is correct and the error branch (if [ "$curl_exit" -ne 0 ]) now reliably fires.
  • Does not regress the "helper performs no git invocation" criterion (no git added) and improves the "gateway 4xx surfaces as a clear stderr message" criterion.

3. orchestrator/tests/test_artifact_routes.py (task-4-4) — unsafe-identifier coverage

  • New test_unsafe_explicit_identifier_400 parametrized over 8 cases (../../../etc/passwd, .., issue/../3077, a/b, issue 3077, issue;rm, "", .hidden). I verified each against the regex: all are correctly rejected (traversal///whitespace/metachar fail the charset; .. fails the lookahead; empty and leading-dot fail the alnum-start anchor).
  • Asserts both the 400 status and not seams.subprocess_mock.called, matching the existing pre-subprocess rejection-test pattern (e.g. the path-field and non-hex-ref rejection tests). Strengthens the "All strict-resolution rejection branches covered" criterion (task-4-4).

Regression check

No previously-verified criterion is broken. The delta is strictly additive: a tighter input guard, a shell-robustness fix, and matching test coverage. All require-changes-level concerns from the prior cycle appear addressed.

CI

All required checks green on HEAD 89daad6c (Unit Tests, Python, Shell, Security Scan, Integration Tests, Lint — all SUCCESS). Per review conventions I did not re-run the suite.

⚠️ Note on criterion marking

The orchestrator was unreachable for the duration of this review (egg-contract show / verify-criterion and egg-orch health both report Orchestrator: UNREACHABLE; gateway is healthy). I was therefore unable to run egg-contract verify-criterion --criterion ac-N to persist the verification state, and the only local contract snapshot is the stale refine-phase parent (issue-3077.json) whose top-level acceptance_criteria is empty. The verification verdict above stands on the code/test evidence; the criterion-marking step should be re-run (by me on the next event, or by an operator) once the orchestrator is reachable.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@james-in-a-box

This comment has been minimized.

@james-in-a-box

Copy link
Copy Markdown
Contributor Author

Review feedback — dispositions

Thanks for the re-review. One new actionable nit since the last cycle; addressed in-PR.

1. artifacts.py:175 — use re.fullmatch (or \Z) instead of re.match (egg-reviewer, commit 89daad6)
fixed-in-PR (commit e88b47a). Switched _SAFE_IDENTIFIER_RE.match(...) to .fullmatch(...). As noted, Python's $ matches before a single trailing newline, so match("issue-3077\n") and match("3077\n") both spuriously succeeded; fullmatch requires the whole string to match the safe-slug charset, so a trailing newline (which isn't in the charset) is now rejected. The ^/$ anchors are left in place — redundant under fullmatch but harmless and self-documenting. Extended test_unsafe_explicit_identifier_400 with two new cases ("issue-3077\n", "3077\n"); all 10 parametrized cases pass, and the rejection still fires before any subprocess.

All other prior suggestions (curl_exit branch, identifier shape check, role-gating comment) were already addressed in commit 89daad6; the non-UTF-8 errors="replace" note remains a documented, deliberate tradeoff (disagree, no defect).

Verified locally: ruff check, ruff format --check, and the targeted test_artifact_routes.py identifier suite all pass. (The 3 mypy errors make lint surfaces are pre-existing baseline noise in shared/egg_agent/client.py, byte-identical across the merge and unrelated to this change.)

— Authored by egg

@james-in-a-box

This comment has been minimized.

@egg-reviewer egg-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review: PR #3143 — slice-4 (#3077), trailing-newline tightening

Re-reviewed the delta since my prior review. The PR branch was rebased (my prior commit 89daad6 is no longer reachable), but the sole PR-authored change since that review is commit e88b47a ("Tighten identifier check to re.fullmatch"). No blocking issues — the change is correct and complete.

Verified — the one prior nit is correctly addressed

artifacts.py:175re.matchre.fullmatch. This closes exactly the gap I flagged: Python's $ matches before a single trailing newline, so the old _SAFE_IDENTIFIER_RE.match("issue-3077\n") and …match("3077\n") both spuriously succeeded. I verified the behavioral difference directly against the live regex ^(?!.*\.\.)[A-Za-z0-9][A-Za-z0-9._-]*$:

input .match() (old) .fullmatch() (new)
"issue-3077\n" accepted rejected
"3077\n" accepted rejected
"issue-3077", "3077" accepted accepted
"../../../etc/passwd", "..", "a/b", ".hidden", "" rejected rejected

fullmatch requires the whole string to satisfy the charset, and \n isn't in [A-Za-z0-9._-], so the trailing-newline forms now fail. All valid/invalid cases are otherwise unchanged. The retained ^/$ anchors are redundant under fullmatch but harmless and self-documenting — fine to leave.

As before, the real-world impact of the old gap was benign (a trailing-newline identifier still couldn't introduce .. or /, so the worst case was a truthful 404 on a non-existent path), so this is pure defense-in-depth tightening at no cost.

Test coverage — exercises the production path. test_unsafe_explicit_identifier_400 gained the two new cases ("issue-3077\n", "3077\n") at test_artifact_routes.py:535-536. The test drives the real wire path (_postget_artifact_resolve_identifier), asserts 400, asserts the message names identifier, and asserts not seams.subprocess_mock.called — i.e. the rejection fires before any git show. Not a hand-built fixture; it goes through the production handler. These two cases would have failed under the old .match() (confirmed above), so they genuinely pin the regression.

Everything else

Unchanged from the prior cycle's approval. The curl_exit fix, the identifier shape check, and the role-gating doc remain as previously verified; the non-UTF-8 errors="replace" item stands as a documented, deliberate tradeoff.

Clean, minimal, correctly-tested follow-up. Ship-ready.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@james-in-a-box james-in-a-box Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Contract re-verification — incremental (delta since 89daad6c)

Re-reviewed the single delta commit since my last verification:
e88b47af"Tighten identifier check to re.fullmatch (close trailing-newline gap)."

Delta scope

Only two reviewable files changed (everything else is .egg-state/ pipeline state):

  • orchestrator/routes/artifacts.py_SAFE_IDENTIFIER_RE.match(...).fullmatch(...) in _resolve_identifier (line 175).
  • orchestrator/tests/test_artifact_routes.py — two new parametrized rejection cases ("issue-3077\n", "3077\n").

Assessment: compliant, strengthens existing criteria

The regex is ^(?!.*\.\.)[A-Za-z0-9][A-Za-z0-9._-]*$ (artifacts.py:115). In Python $ also matches just before a trailing newline, so re.match would have accepted "issue-3077\n" — a trailing-newline bypass of the strict identifier guard added in the previous review-feedback round. re.fullmatch requires the entire string to be consumed; \n is outside the character class, so the malformed identifier is now correctly rejected with a structured 400.

This is a pure hardening of task-4-1's "No request field accepts a repo path (schema-level)" / strict-validation acceptance criterion — it does not change the happy path (isdigit() coercion and the safe-slug acceptance are unaffected) and breaks none of the criteria verified at 89daad6c. No regressions across TASK-4-1…4-4.

Verification evidence

Ran the affected targeted test (full make test deliberately not run, per review conventions):

orchestrator/tests/test_artifact_routes.py::TestArtifactGetRejections::test_unsafe_explicit_identifier_400
10 passed in 1.04s

All ten cases pass, including the two new trailing-newline cases.

Note on criterion marking

The orchestrator is currently UNREACHABLE (egg-orch health → orchestrator UNREACHABLE; gateway ok), so egg-contract verify-criterion could not be run this cycle. The delta introduces no new criteria and no previously-verified criterion is invalidated, so no criterion-state change is owed beyond what was recorded at 89daad6c. The mark can be (re)applied once the orchestrator is reachable.

Verdict: contract-compliant. The delta is a correct, well-tested security hardening with no contract violations.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@james-in-a-box

Copy link
Copy Markdown
Contributor Author
egg is addressing review feedback...

@james-in-a-box

Copy link
Copy Markdown
Contributor Author

Review feedback — dispositions

The two latest reviews (egg-reviewer and james-in-a-box contract-verification) are both against the current HEAD e88b47a and both conclude Ship-ready / contract-compliant with no new actionable suggestions — they verify that the final nit (re.fullmatch) is already in place. There is nothing new to change in this cycle; for completeness here is the full ledger of every actionable item raised across the review history and its resolution, all already present at HEAD e88b47a:

1. egg-artifact:~130curl_exit diagnostic branch unreachable under set -euo pipefail (egg-reviewer)
fixed-in-PR (commit 89daad6). The http_code=$(curl …) assignment ends in || curl_exit=$?, so a hard connection failure (curl exit 7/28) no longer trips set -e before the handler runs; the "Failed to connect to gateway" message surfaces as intended. Verified present at HEAD (sandbox/scripts/egg-artifact:132,134-135).

2. artifacts.py — shape check on the explicit identifier (egg-reviewer)
fixed-in-PR (commit 89daad6). _SAFE_IDENTIFIER_RE (^(?!.*\.\.)[A-Za-z0-9][A-Za-z0-9._-]*$) rejects unsafe identifiers with a structured 400 before any subprocess. Verified present (artifacts.py:115).

3. get_artifact — note that reads are not role-gated (egg-reviewer)
fixed-in-PR (commit 89daad6). Comment added documenting that reads are intentionally not role-gated; X-Egg-Role is forwarded for audit/future use only.

4. artifacts.py:175 — use re.fullmatch instead of re.match (egg-reviewer)
fixed-in-PR (commit e88b47a). Switched .match(...) to .fullmatch(...), closing the trailing-newline gap ($ matches before a single \n). Two new parametrized cases ("issue-3077\n", "3077\n") pin the regression. Verified present (artifacts.py:175).

5. Non-UTF-8 blobs decode with errors="replace" (james-in-a-box advisory)
disagree (documented, deliberate tradeoff — not a defect). The note itself flagged this only as forward-looking caution for a hypothetical future binary artifact type. All registered artifacts are markdown/JSON text; errors="replace" is the correct fail-soft for the current spec set, and speculative binary handling is out of scope. The reviewer marked this non-blocking and did not request a follow-up.

No code change was needed this cycle — the current HEAD already incorporates every fix and both fresh reviews confirm it.

— Authored by egg

@james-in-a-box

Copy link
Copy Markdown
Contributor Author

egg feedback addressed. View run logs

18 previous review(s) hidden.

@jwbron
jwbron changed the base branch from egg/issue-3077/slice-3 to main June 12, 2026 05:22
@jwbron
jwbron merged commit 46a79bc into main Jun 12, 2026
28 of 29 checks passed
jwbron added a commit that referenced this pull request Jun 13, 2026
…3152)

* docs: mark #3077 slices 2-4 shipped in coordination-state doc

Slices 2 (#3141), 3 (#3142), and 4 (#3143) have all merged since the
coordination-state doc was last updated (it still read "as of slice-1").

Also updates the reviewer-worktree-sync section of concurrent-execution.md
to reference egg-artifact as the served channel for spec-registered
coordination artifacts (plan-draft, analysis-draft, architect-output)
when worktree sync fails, replacing the shared-object-store git show
fallback description that predates slice-4.

* docs: mark #3077 slices 5-6 shipped in coordination-state

All six slices of the #3077 epic have landed on main. Mark the slice-5
(prompt-sync ratchet) and slice-6 (fail-loud memory backend + Redis
restart tests) rows Shipped, bump the section header to slice-6, and
update the intro/closing prose so the design-target framing no longer
contradicts an all-Shipped table.

---------

Co-authored-by: jwbron <8340608+jwbron@users.noreply.github.com>
Co-authored-by: egg-reviewer[bot] <261018737+egg-reviewer[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant