From c6b8972606e7f2b1400f95fed6916242da243726 Mon Sep 17 00:00:00 2001 From: SoundMindsAI Date: Thu, 28 May 2026 17:36:03 -0400 Subject: [PATCH 1/3] chore(docs): move planned_features under 00_overview as sibling to implemented_features MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sibling lifecycle: planned_features (in-flight) + implemented_features (dated archive) now both live under docs/00_overview/. /pipeline finalization becomes a single-parent git mv. 35 git-mv renames + Pass 1 (global text-replace 02_product/planned_features → 00_overview/planned_features across all docs) + Pass 2 (scoped depth collapse inside moved tree, ../../../00_overview/ → ../../) + 7 skill SKILL.md updates + build_mvp1_dashboard.py constant + cmd-gen updates + docs/02_product/README.md + mvp1-user-stories.md sibling-ref rewrites + 2 pre-existing wrong-depth refs fixed + 1 docstring line-break to respect ruff's 100-char limit after the path-prefix lengthening. Verified: dashboard regen still works (145 features across 2 releases), zero remaining 02_product/planned_features refs in active docs, all 7 skills clean, spot-checked moved-tree relative links resolve. Co-Authored-By: Claude Opus 4.7 (1M context) Signed-off-by: SoundMindsAI --- .claude/skills/bug-fix/SKILL.md | 6 +-- .claude/skills/guide-gen/SKILL.md | 4 +- .claude/skills/idea-preflight/SKILL.md | 8 +-- .claude/skills/impl-execute/SKILL.md | 12 ++--- .claude/skills/impl-plan-gen/SKILL.md | 8 +-- .claude/skills/pipeline/SKILL.md | 18 +++---- .claude/skills/spec-gen/SKILL.md | 10 ++-- .github/workflows/pr.yml | 4 +- .github/workflows/secrets-defense.yml | 4 +- .pre-commit-config.yaml | 2 +- CLAUDE.md | 18 +++---- README.md | 2 +- architecture.md | 4 +- backend/app/api/v1/schemas.py | 2 +- backend/app/domain/study/baseline_resolver.py | 2 +- backend/app/domain/study/confidence.py | 3 +- backend/app/domain/study/followups.py | 2 +- backend/app/domain/study/template_swap.py | 2 +- backend/app/main.py | 2 +- backend/app/services/study_preflight.py | 2 +- backend/tests/integration/conftest.py | 2 +- .../test_judgments_resume_sweep.py | 2 +- .../unit/domain/test_search_space_defaults.py | 2 +- .../test_dashboard_expand_transitive_deps.py | 2 +- ..._dashboard_path_rewrite_and_idempotency.py | 10 ++-- .../test_dashboard_release_classifier.py | 2 +- .../unit/scripts/test_dashboard_truncation.py | 2 +- backend/tests/unit/test_main_lifespan.py | 2 +- .../unit/workers/test_poll_cron_kwargs.py | 2 +- docs/00_overview/DASHBOARD.md | 2 +- docs/00_overview/MVP1_5_DASHBOARD.md | 4 +- docs/00_overview/MVP1_DASHBOARD.md | 46 ++++++++-------- docs/00_overview/MVP2_DASHBOARD.md | 12 ++--- docs/00_overview/adjacent-tools.md | 2 +- .../implementation_plan.md | 2 +- .../pipeline_status.md | 8 +-- .../implementation_plan.md | 6 +-- .../phase2_idea.md | 2 +- .../phase2_implementation_plan.md | 2 +- .../pipeline_status.md | 2 +- .../implementation_plan.md | 6 +-- .../feature_spec.md | 6 +-- .../implementation_plan.md | 4 +- .../pipeline_status.md | 2 +- .../pipeline_status.md | 2 +- .../pipeline_status.md | 2 +- .../idea.md | 2 +- .../implementation_plan.md | 4 +- .../pipeline_status.md | 2 +- .../pipeline_status.md | 2 +- .../implementation_plan.md | 6 +-- .../pipeline_status.md | 2 +- .../implementation_plan.md | 2 +- .../implementation_plan.md | 2 +- .../idea.md | 6 +-- .../feature_spec.md | 2 +- .../feature_spec.md | 2 +- .../implementation_plan.md | 4 +- .../feature_spec.md | 2 +- .../idea.md | 2 +- .../implementation_plan.md | 4 +- .../idea.md | 2 +- .../idea.md | 2 +- .../implementation_plan.md | 2 +- .../feature_spec.md | 2 +- .../implementation_plan.md | 2 +- .../feature_spec.md | 2 +- .../implementation_plan.md | 2 +- .../feature_spec.md | 2 +- .../feature_spec.md | 2 +- .../feature_spec.md | 2 +- .../implementation_plan.md | 2 +- .../feature_spec.md | 4 +- .../implementation_plan.md | 2 +- .../feature_spec.md | 4 +- .../implementation_plan.md | 8 +-- .../feature_spec.md | 2 +- .../implementation_plan.md | 2 +- .../feature_spec.md | 14 ++--- .../idea.md | 4 +- .../implementation_plan.md | 8 +-- .../pipeline_status.md | 6 +-- .../feature_spec.md | 10 ++-- .../idea.md | 2 +- .../implementation_plan.md | 4 +- .../pipeline_status.md | 2 +- .../implementation_plan.md | 2 +- .../feature_spec.md | 2 +- .../implementation_plan.md | 14 ++--- .../feature_spec.md | 18 +++---- .../idea.md | 4 +- .../implementation_plan.md | 24 ++++----- .../feature_spec.md | 4 +- .../implementation_plan.md | 2 +- .../feature_spec.md | 2 +- .../implementation_plan.md | 4 +- docs/00_overview/mvp1_5_dashboard.html | 4 +- docs/00_overview/mvp1_dashboard.html | 46 ++++++++-------- docs/00_overview/mvp2_dashboard.html | 12 ++--- .../idea.md | 12 ++--- .../idea.md | 0 .../bug_fix.md | 2 +- .../idea.md | 4 +- .../idea.md | 2 +- .../idea.md | 0 .../idea.md | 0 .../idea.md | 0 .../idea.md | 0 .../idea.md | 2 +- .../idea.md | 0 .../idea.md | 2 +- .../chore_drop_demo_seed_from_ci/idea.md | 0 .../chore_drop_fusion_scope/idea.md | 2 +- .../idea.md | 0 .../chore_e2e_seed_acme_helper_dead/idea.md | 0 .../chore_oss_public_launch_punchlist/idea.md | 0 .../chore_state_md_size_compression/idea.md | 2 +- .../idea.md | 0 .../chore_template_library_expansion/idea.md | 0 .../feat_chat_last_message_preview/idea.md | 6 +-- .../feat_fts_rank_ordering_mvp2/idea.md | 0 .../feat_ubi_judgments/idea.md | 6 +-- .../feature_templates/README.md | 2 +- .../feature-spec-template.md | 0 .../feature_templates/idea-template.md | 0 .../implementation-plan-template.md | 0 .../infra_adapter_solr/idea.md | 0 .../feature_spec.md | 6 +-- .../idea.md | 0 .../implementation_plan.md | 20 +++---- .../phase3_idea.md | 0 .../pipeline_status.md | 0 .../infra_arq_subprocess_test_mvp2/idea.md | 6 +-- .../infra_smoke_job_chronic_flake/idea.md | 0 docs/00_overview/relyloop-spec.md | 18 +++---- docs/01_architecture/adapters.md | 8 +-- docs/01_architecture/agent-tools.md | 2 +- docs/01_architecture/api-conventions.md | 2 +- docs/01_architecture/apply-path.md | 6 +-- docs/01_architecture/cluster-lifecycle.md | 2 +- docs/01_architecture/data-model.md | 2 +- docs/01_architecture/llm-orchestration.md | 6 +-- docs/01_architecture/mvp1-overview.md | 6 +-- docs/01_architecture/optimization.md | 8 +-- docs/01_architecture/tech-stack.md | 4 +- docs/01_architecture/ui-architecture.md | 12 ++--- docs/02_product/README.md | 5 +- docs/02_product/mvp1-user-stories.md | 6 +-- docs/03_runbooks/auto-followup-debugging.md | 4 +- docs/03_runbooks/cluster-registration.md | 2 +- docs/03_runbooks/demo-reseed-debugging.md | 2 +- docs/03_runbooks/local-dev.md | 8 +-- docs/03_runbooks/optuna-debugging.md | 4 +- docs/03_runbooks/release-checklist.md | 2 +- docs/03_runbooks/ui-debugging.md | 2 +- .../github-branch-protection.md | 2 +- docs/README.md | 8 +-- scripts/build_mvp1_dashboard.py | 24 ++++----- scripts/ci/check-no-env-files.sh | 2 +- state.md | 54 +++++++++---------- ui/src/lib/narrow-bounds.ts | 2 +- ui/src/lib/types.ts | 2 +- 162 files changed, 404 insertions(+), 402 deletions(-) rename docs/{02_product => 00_overview}/planned_features/backlog_feat_digest_template_edit_followups/idea.md (71%) rename docs/{02_product => 00_overview}/planned_features/bug_ceiling_badge_assumes_maximize_direction/idea.md (100%) rename docs/{02_product => 00_overview}/planned_features/bug_chat_long_conversation_truncation_mvp2/bug_fix.md (98%) rename docs/{02_product => 00_overview}/planned_features/bug_chat_long_conversation_truncation_mvp2/idea.md (96%) rename docs/{02_product => 00_overview}/planned_features/bug_demo_reseed_button_silent_enqueue_failure/idea.md (97%) rename docs/{02_product => 00_overview}/planned_features/bug_smoke_seed_es_unavailable_shards_race/idea.md (100%) rename docs/{02_product => 00_overview}/planned_features/bug_smoke_studies_data_table_search_flake/idea.md (100%) rename docs/{02_product => 00_overview}/planned_features/bug_starlette_request_poisons_fastapi_depends_tests/idea.md (100%) rename docs/{02_product => 00_overview}/planned_features/bug_webhook_concurrent_merge_race_timing_sensitive/idea.md (100%) rename docs/{02_product => 00_overview}/planned_features/chore_auto_followup_parent_advisory_lock/idea.md (92%) rename docs/{02_product => 00_overview}/planned_features/chore_ci_perf_buildx_artifact_image_cache_xdist/idea.md (100%) rename docs/{02_product => 00_overview}/planned_features/chore_demo_seeding_integration_tests_rewrite/idea.md (96%) rename docs/{02_product => 00_overview}/planned_features/chore_drop_demo_seed_from_ci/idea.md (100%) rename docs/{02_product => 00_overview}/planned_features/chore_drop_fusion_scope/idea.md (97%) rename docs/{02_product => 00_overview}/planned_features/chore_e2e_api_base_url_construction/idea.md (100%) rename docs/{02_product => 00_overview}/planned_features/chore_e2e_seed_acme_helper_dead/idea.md (100%) rename docs/{02_product => 00_overview}/planned_features/chore_oss_public_launch_punchlist/idea.md (100%) rename docs/{02_product => 00_overview}/planned_features/chore_state_md_size_compression/idea.md (98%) rename docs/{02_product => 00_overview}/planned_features/chore_studies_post_arq_spy_fixture/idea.md (100%) rename docs/{02_product => 00_overview}/planned_features/chore_template_library_expansion/idea.md (100%) rename docs/{02_product => 00_overview}/planned_features/feat_chat_last_message_preview/idea.md (95%) rename docs/{02_product => 00_overview}/planned_features/feat_fts_rank_ordering_mvp2/idea.md (100%) rename docs/{02_product => 00_overview}/planned_features/feat_ubi_judgments/idea.md (92%) rename docs/{02_product => 00_overview}/planned_features/feature_templates/README.md (98%) rename docs/{02_product => 00_overview}/planned_features/feature_templates/feature-spec-template.md (100%) rename docs/{02_product => 00_overview}/planned_features/feature_templates/idea-template.md (100%) rename docs/{02_product => 00_overview}/planned_features/feature_templates/implementation-plan-template.md (100%) rename docs/{02_product => 00_overview}/planned_features/infra_adapter_solr/idea.md (100%) rename docs/{02_product => 00_overview}/planned_features/infra_agent_sibling_worktree_isolation/feature_spec.md (99%) rename docs/{02_product => 00_overview}/planned_features/infra_agent_sibling_worktree_isolation/idea.md (100%) rename docs/{02_product => 00_overview}/planned_features/infra_agent_sibling_worktree_isolation/implementation_plan.md (96%) rename docs/{02_product => 00_overview}/planned_features/infra_agent_sibling_worktree_isolation/phase3_idea.md (100%) rename docs/{02_product => 00_overview}/planned_features/infra_agent_sibling_worktree_isolation/pipeline_status.md (100%) rename docs/{02_product => 00_overview}/planned_features/infra_arq_subprocess_test_mvp2/idea.md (86%) rename docs/{02_product => 00_overview}/planned_features/infra_smoke_job_chronic_flake/idea.md (100%) diff --git a/.claude/skills/bug-fix/SKILL.md b/.claude/skills/bug-fix/SKILL.md index 8b5b32b5..d55d99c5 100644 --- a/.claude/skills/bug-fix/SKILL.md +++ b/.claude/skills/bug-fix/SKILL.md @@ -12,7 +12,7 @@ user-invocable: true # Bug Fix — drive a medium-sized bug from repro to committed fix You take a bug that's been captured as a `bug_*` folder under -`docs/02_product/planned_features/` and drive it through CLAUDE.md's +`docs/00_overview/planned_features/` and drive it through CLAUDE.md's **Bug Fix Protocol**. The skill produces two artifacts: a focused `bug_fix.md` design doc that lives next to the original `idea.md`, AND the actual code change committed on a feature branch. The next step @@ -44,7 +44,7 @@ running `/pipeline`. ## Inputs -- **Path to the bug folder** (preferred — `docs/02_product/planned_features/bug_/`) or the `idea.md` inside it. +- **Path to the bug folder** (preferred — `docs/00_overview/planned_features/bug_/`) or the `idea.md` inside it. - **Project context (always read first):** `CLAUDE.md`, `architecture.md`, `state.md`. - **Bug Fix Protocol section** of `CLAUDE.md` — the 6-step protocol is the spine of this skill's workflow. - **The /idea-preflight output** if it was run on this bug recently — the patches it landed are the starting point for the `bug_fix.md`. @@ -187,7 +187,7 @@ Now you write the artifact and the code. The artifact and the implementation reference each other; write them in parallel, not the artifact first then the code. -1. **Write `bug_fix.md`** at `docs/02_product/planned_features//bug_fix.md` +1. **Write `bug_fix.md`** at `docs/00_overview/planned_features//bug_fix.md` using the template below. Keep it tight — the target is ~80 lines. Anything that would push past 150 lines is a signal the bug should have been a feature; escalate to /pipeline. **The 150-line cap diff --git a/.claude/skills/guide-gen/SKILL.md b/.claude/skills/guide-gen/SKILL.md index 7099d906..f74763a3 100644 --- a/.claude/skills/guide-gen/SKILL.md +++ b/.claude/skills/guide-gen/SKILL.md @@ -238,7 +238,7 @@ Classify all findings from both passes (including completeness and route relevan **For UI Bugs (code is right, UI is wrong):** Create a bug tracking file: ``` -docs/02_product/planned_features/bug_/idea.md +docs/00_overview/planned_features/bug_/idea.md ``` Following the idea template with: - **Status:** `Bug — identified by guide-gen visual audit` @@ -250,7 +250,7 @@ Following the idea template with: **For Missing Features (intentional gap, not a bug):** Create a feature tracking file: ``` -docs/02_product/planned_features//idea.md +docs/00_overview/planned_features//idea.md ``` **For Spec Issues:** diff --git a/.claude/skills/idea-preflight/SKILL.md b/.claude/skills/idea-preflight/SKILL.md index 9bcef06d..c5a7d8d8 100644 --- a/.claude/skills/idea-preflight/SKILL.md +++ b/.claude/skills/idea-preflight/SKILL.md @@ -36,7 +36,7 @@ The skill never commits or opens a PR — that's the user's call. Patches land a - **`$ARGUMENTS`** — path to a single `idea.md`. If omitted, fall back to the file the user most recently opened in the IDE (`` context tag), or ask. - **Project context (always read first):** `CLAUDE.md`, `architecture.md`, `state.md`. -- **Convention reference:** `docs/02_product/planned_features/feature_templates/README.md` for folder-prefix taxonomy. +- **Convention reference:** `docs/00_overview/planned_features/feature_templates/README.md` for folder-prefix taxonomy. ## Workflow @@ -87,7 +87,7 @@ If a rename is recommended, propose the new name with one-line rationale and 2 ### Step 6 — Cross-check siblings + recently implemented features -1. `ls docs/02_product/planned_features/` — for any sibling whose name overlaps the idea's domain, check whether the two coordinate or conflict. The idea's "Relationship to other work" section should mention each sibling that touches the same table/service/UI surface. +1. `ls docs/00_overview/planned_features/` — for any sibling whose name overlaps the idea's domain, check whether the two coordinate or conflict. The idea's "Relationship to other work" section should mention each sibling that touches the same table/service/UI surface. 2. `git log --since="" --oneline -- docs/00_overview/implemented_features/` — list features that shipped after the idea was written. For each, ask: does its ship retire any deferral rationale in this idea? Does it create any new precedent the idea should reference (e.g. scheduler 5-touch-point checklist after the worker-runtime work shipped)? 3. Note any sibling that the idea should explicitly coordinate with for ordering (not blocking — coordinate-only is the common case). @@ -95,7 +95,7 @@ If a rename is recommended, propose the new name with one-line rationale and 2 For each claim in the idea, mentally walk the project's "Absolute Rules — Never Violate" section. RelyLoop's CLAUDE.md (when it exists per `infra_foundation`) will codify these; until then, derive from the umbrella spec + `docs/01_architecture/`. Common rules to check: -- Adding a migration? → reversible downgrade + idempotency guards mentioned (per [`infra_foundation/feature_spec.md`](../../docs/02_product/planned_features/infra_foundation/feature_spec.md) FR-5). +- Adding a migration? → reversible downgrade + idempotency guards mentioned (per [`infra_foundation/feature_spec.md`](../../docs/00_overview/planned_features/infra_foundation/feature_spec.md) FR-5). - Adding a webhook handler (e.g., `/webhooks/github`)? → signature verification on raw body + idempotency. - Adding a state-machine transition (e.g., `studies.status`)? → routed through the centralized service-layer guard (`backend/services/study_state.py`); direct ORM writes raise. - Adding a `` for page-level controls). The 7-PR day drained the operational-friction surfaces and the last `/bug-fix`-shaped item. **MVP1 alpha + 17 features shipped (all 3 phases of feat_contextual_help on 2026-05-15) + 26 backlog items drained.** Manual maintainer steps still pending from [`release-checklist.md`](docs/03_runbooks/release-checklist.md): §4 fresh-VM hosted-OpenAI walkthrough, §5 local-LLM walkthrough, §8 feedback Discussion + design-partner channel shares. **Remaining MVP1 backlog: zero actionable items** — the MVP1 dashboard now reads `Path to MVP1: 0`. Two ideas held for MVP2 (visible on the new `MVP2_DASHBOARD.md`): `infra_arq_subprocess_test_mvp2` (folder renamed 2026-05-14 to mirror the `_mvp2` precedent — trigger-locked: arq pin bump, 3rd cron, or MVP3 hardening opt-in) and `bug_chat_long_conversation_truncation_mvp2` (paired with the just-shipped chat preview but kept on MVP2 hold per its preflight — needs `/pipeline` for migration + new LLM round-trip). Plus 4 keep-deferred items by operator decision + `infra_dashboard_regen_pre_commit_conflict §3` follow-up (runbook addendum, MVP1-eligible but low-leverage). - **Alembic head:** `0020_studies_baseline_trial` (added by `feat_study_baseline_trial` PR #245 squash `53be6c63` — adds `studies.baseline_trial_id VARCHAR(36) NULL` + `trials.is_baseline BOOLEAN NOT NULL DEFAULT FALSE` + partial unique index `uq_trials_study_baseline_complete ON trials(study_id) WHERE is_baseline = TRUE AND status = 'complete'`). Prior head was `0019_digests_suggested_followups_jsonb` (added by `feat_digest_executable_followups` Story 3.3 — `digests.suggested_followups` column-type change from `ARRAY(Text)` to `JSONB` via PL/pgSQL helper functions; legacy rows wrap to `{kind:'text', rationale:, search_space:null}` on upgrade, downgrade collapses structured items to rationale strings). Story 3.1 also added `0018_studies_parent_proposal` (paired `studies.parent_proposal_id` + `parent_proposal_followup_index` columns + CHECK + partial index + BEFORE DELETE trigger that atomically NULLs the lineage pair on parent-proposal hard-delete). Prior head was `0017_proposals_last_polled_at`. @@ -21,26 +21,26 @@ ## Most recent meaningful changes (newest first) -- **2026-05-26 — `infra_test_worktree_missing_integration_envs` merged into `main` as PR #257 squash `4ffc83a5`.** Infra-script fix that propagates `POSTGRES_PASSWORD_FILE` (required, fail-loud exit 5 — mirrors the existing DB-secret check shape at `scripts/run-tests-in-worktree.sh:100-115`) and `CLUSTER_CREDENTIALS_FILE` (optional, mount-if-present via `[[ -r && -s ]]` probe with a `--dry-run`-only stderr hint when skipped) from the main worktree's `secrets/` into the one-shot test container that `scripts/run-tests-in-worktree.sh` builds for `make test-worktree`. Closes the silent-skip footgun where every Postgres-touching integration test invoked via `make test-worktree CMD="pytest backend/tests/integration -v"` reported `SKIPPED [..] Postgres not reachable` because `postgres_reachable()` (`backend/tests/conftest.py:50-72`) gates on BOTH env vars being present, but Phase 2 of `infra_agent_sibling_worktree_isolation` shipped the script with only `DATABASE_URL_FILE` propagation. **Operator-path verification** from a sibling worktree at `/private/tmp/relyloop-verify-test-worktree`: `make test-worktree CMD="pytest backend/tests/integration/test_studies_api.py"` ran 43 tests to completion (43 passed, 0 skipped); pre-PR baseline was all 43 skipping. Smoke-test coverage in [`backend/tests/unit/scripts/test_run_tests_in_worktree.py`](backend/tests/unit/scripts/test_run_tests_in_worktree.py) extended from 6 → 9 tests (the parametrized `test_cluster_credentials_skipped_when_host_file_absent_or_empty` covers absent/empty/unreadable + skips the unreadable subcase when running as root or on Windows since `chmod 0o000` doesn't restrict reads in either). Docs synced: CLAUDE.md §"Running tests against a sibling worktree (one-shot container recipe)" uses a bash-safe `CLUSTER_CREDS_ARGS=()` array splice rather than the spec's literal `# Optional` inline-comment shape (plan-stage GPT-5.5 cycle 1 caught that bash line-continuation would join the `\` with the next line, swallowing the comment marker and orphaning the subsequent `-e CLUSTER_CREDENTIALS_FILE=...` line as a standalone failing command); [`docs/03_runbooks/parallel-worktrees.md`](docs/03_runbooks/parallel-worktrees.md) gains a new "Adding a new `*_FILE` env var to the Compose stack" subsection documenting the durable three-place update contract (compose → script → CLAUDE.md). **Cross-model review (11 cycles, 18 findings, 16/18 accepted):** spec GPT-5.5 3 cycles (5 → 2 → 2, all accepted, severity descent M+L → M+L → L); plan GPT-5.5 3 cycles (4 → 1 → 0, all accepted, including one High catching the broken-bash trap); Gemini Code Assist 3 Medium accepted (Windows portability of the unreadable-mode chmod check + inverted docker-compose service↔line mapping in feature_spec.md §2 + same mapping inverted in idea.md table — service blocks: postgres@21, migrate@53, api@80, worker@135, so line 69 is migrate / 96 is api / 154 is worker / 102 is api / 160 is worker; the api service DOES mount cluster_credentials, contrary to spec's draft assumption); final GPT-5.5 cycle 1 caught the same mapping bug in a third file (CLAUDE.md CLUSTER_CREDS_ARGS comment); cycle 2 returned 2 hallucinations claiming script + runbook diffs were missing from the input when both were demonstrably present (49060-char diff sent with 120000-char slice cap; both files in `git diff main..HEAD --name-only`). **Two tangential discoveries captured during the work:** [`chore_db_session_skip_reason_disambiguation/idea.md`](docs/02_product/planned_features/chore_db_session_skip_reason_disambiguation/idea.md) (the `db_session` fixture's `"Postgres not reachable"` skip reason misleads when the actual cause is the env-var presence check failing — same misattribution that surfaced this whole feature; ~10 LOC refactor of `postgres_reachable()` to return a discriminator + ~50 LOC unit test) and [`bug_smoke_followup_clone_e2e_flakes/idea.md`](docs/02_product/planned_features/bug_smoke_followup_clone_e2e_flakes/idea.md) (PR #257's `smoke` CI job red on 3 followup_run + study-clone E2E tests; cross-checked against main commit `9928d763`, same 6 failures pre-existed, so not a PR #257 regression; the dashboard subset is already captured as `bug_dashboard_banner_dismiss_persistence_flake` + `bug_smoke_dashboard_demo_state_locator_missing`). **CI:** 6/7 non-smoke checks green (backend unit fast lane + backend lint+typecheck+tests+coverage + frontend + docker buildx + gitleaks + secrets-files-guard); `smoke` red on the documented pre-existing flakes. **No migration; no Alembic head movement; no production code change** (`git diff main^..main -- backend/app/ migrations/ ui/` empty). Story 1.1 + Story 1.2 = 2/2 complete in single PR. 42nd MVP1-era artifact. +- **2026-05-26 — `infra_test_worktree_missing_integration_envs` merged into `main` as PR #257 squash `4ffc83a5`.** Infra-script fix that propagates `POSTGRES_PASSWORD_FILE` (required, fail-loud exit 5 — mirrors the existing DB-secret check shape at `scripts/run-tests-in-worktree.sh:100-115`) and `CLUSTER_CREDENTIALS_FILE` (optional, mount-if-present via `[[ -r && -s ]]` probe with a `--dry-run`-only stderr hint when skipped) from the main worktree's `secrets/` into the one-shot test container that `scripts/run-tests-in-worktree.sh` builds for `make test-worktree`. Closes the silent-skip footgun where every Postgres-touching integration test invoked via `make test-worktree CMD="pytest backend/tests/integration -v"` reported `SKIPPED [..] Postgres not reachable` because `postgres_reachable()` (`backend/tests/conftest.py:50-72`) gates on BOTH env vars being present, but Phase 2 of `infra_agent_sibling_worktree_isolation` shipped the script with only `DATABASE_URL_FILE` propagation. **Operator-path verification** from a sibling worktree at `/private/tmp/relyloop-verify-test-worktree`: `make test-worktree CMD="pytest backend/tests/integration/test_studies_api.py"` ran 43 tests to completion (43 passed, 0 skipped); pre-PR baseline was all 43 skipping. Smoke-test coverage in [`backend/tests/unit/scripts/test_run_tests_in_worktree.py`](backend/tests/unit/scripts/test_run_tests_in_worktree.py) extended from 6 → 9 tests (the parametrized `test_cluster_credentials_skipped_when_host_file_absent_or_empty` covers absent/empty/unreadable + skips the unreadable subcase when running as root or on Windows since `chmod 0o000` doesn't restrict reads in either). Docs synced: CLAUDE.md §"Running tests against a sibling worktree (one-shot container recipe)" uses a bash-safe `CLUSTER_CREDS_ARGS=()` array splice rather than the spec's literal `# Optional` inline-comment shape (plan-stage GPT-5.5 cycle 1 caught that bash line-continuation would join the `\` with the next line, swallowing the comment marker and orphaning the subsequent `-e CLUSTER_CREDENTIALS_FILE=...` line as a standalone failing command); [`docs/03_runbooks/parallel-worktrees.md`](docs/03_runbooks/parallel-worktrees.md) gains a new "Adding a new `*_FILE` env var to the Compose stack" subsection documenting the durable three-place update contract (compose → script → CLAUDE.md). **Cross-model review (11 cycles, 18 findings, 16/18 accepted):** spec GPT-5.5 3 cycles (5 → 2 → 2, all accepted, severity descent M+L → M+L → L); plan GPT-5.5 3 cycles (4 → 1 → 0, all accepted, including one High catching the broken-bash trap); Gemini Code Assist 3 Medium accepted (Windows portability of the unreadable-mode chmod check + inverted docker-compose service↔line mapping in feature_spec.md §2 + same mapping inverted in idea.md table — service blocks: postgres@21, migrate@53, api@80, worker@135, so line 69 is migrate / 96 is api / 154 is worker / 102 is api / 160 is worker; the api service DOES mount cluster_credentials, contrary to spec's draft assumption); final GPT-5.5 cycle 1 caught the same mapping bug in a third file (CLAUDE.md CLUSTER_CREDS_ARGS comment); cycle 2 returned 2 hallucinations claiming script + runbook diffs were missing from the input when both were demonstrably present (49060-char diff sent with 120000-char slice cap; both files in `git diff main..HEAD --name-only`). **Two tangential discoveries captured during the work:** [`chore_db_session_skip_reason_disambiguation/idea.md`](docs/00_overview/planned_features/chore_db_session_skip_reason_disambiguation/idea.md) (the `db_session` fixture's `"Postgres not reachable"` skip reason misleads when the actual cause is the env-var presence check failing — same misattribution that surfaced this whole feature; ~10 LOC refactor of `postgres_reachable()` to return a discriminator + ~50 LOC unit test) and [`bug_smoke_followup_clone_e2e_flakes/idea.md`](docs/00_overview/planned_features/bug_smoke_followup_clone_e2e_flakes/idea.md) (PR #257's `smoke` CI job red on 3 followup_run + study-clone E2E tests; cross-checked against main commit `9928d763`, same 6 failures pre-existed, so not a PR #257 regression; the dashboard subset is already captured as `bug_dashboard_banner_dismiss_persistence_flake` + `bug_smoke_dashboard_demo_state_locator_missing`). **CI:** 6/7 non-smoke checks green (backend unit fast lane + backend lint+typecheck+tests+coverage + frontend + docker buildx + gitleaks + secrets-files-guard); `smoke` red on the documented pre-existing flakes. **No migration; no Alembic head movement; no production code change** (`git diff main^..main -- backend/app/ migrations/ ui/` empty). Story 1.1 + Story 1.2 = 2/2 complete in single PR. 42nd MVP1-era artifact. -- **2026-05-25 — `infra_agent_sibling_worktree_isolation` Phase 1 + Phase 2 admin-merged into `main` as PR #249 squash `22f878f`.** Two-phase agent-operating guidance for the parallel-worktree workflow surfaced by PR #216's reconciler-agent leak. **Phase 1** adds a new `## Working in sibling worktrees` section to [`CLAUDE.md`](CLAUDE.md) (between `## Common Pitfalls` and `## Bug Fix Protocol`) documenting (a) the bind-mount-anchors-to-main pitfall, (b) the **Compose-anchored host paths catalog** in a Markdown table with writability column distinguishing writable mounts that silently propagate writes to the operator's main worktree (`./migrations/`, `./data/postgres/`, `./data/redis/`, `./data/repo-clones/`) from `:ro` mounts that fail loudly with `EROFS` (`./alembic.ini`, `./samples/`), (c) the operation-mode-based safe-paths rule (sibling-worktree direct edits via Edit/Write/git are always safe even for sibling-local `migrations/`; container-mediated writes via `docker cp` / `docker exec` / `docker compose cp` / `docker compose exec` are hazardous regardless of command form), (d) a one-shot `docker run` recipe with the `*_FILE`-mounted-secret pattern (Rule #2) + `--user root` + `PYTHONDONTWRITEBYTECODE=1` Dockerfile-bug workarounds, (e) cross-reference to `impl-execute` Step 0a / 6b / 9.3 for worktree lifecycle, (f) forward-pointer to the remaining deferred capability. **Phase 1 regression suite** at [`backend/tests/unit/docs/test_claude_md_sections.py`](backend/tests/unit/docs/test_claude_md_sections.py) — 5 tests locking historically-observed failure modes: section existence, ordering between Common Pitfalls and Bug Fix Protocol, absence of bare `DATABASE_URL=postgresql://`, catalog-row attribution (no `worker` for migrations/alembic/samples rows; `worker` present in `data/repo-clones` row), exactly-one fenced `bash` block. 8 mutation tests verified each test fails on its targeted regression. **Phase 2** mechanizes the recipe: [`scripts/run-tests-in-worktree.sh`](scripts/run-tests-in-worktree.sh) (191 LOC bash; auto-detects main + sibling worktree paths via `git worktree list | awk '{print $1; exit}'`, validates `secrets/database_url`, builds canonical docker run argv with 11 bind mounts, joins `relyloop_default` network, dispatches via `uv run ` so dev deps install on-demand; supports `--dry-run` for argv preview with `docker` prefix, `--cmd ""` for Makefile path, and `-- positional` for quoted args like `pytest -k 'foo bar'`; `if docker; then RC=0; else RC=$?; fi` guard preserves the exit-line on failures); [`make test-worktree`](Makefile) target with optional `CMD=` override; [`backend/tests/unit/scripts/test_run_tests_in_worktree.py`](backend/tests/unit/scripts/test_run_tests_in_worktree.py) 8 hermetic smoke tests (fake `$MAIN_REPO` tempdir, never invokes real docker, module-level `pytestmark = pytest.mark.skipif(RELYLOOP_IN_WORKTREE_CONTAINER=1)` so the smoke skips cleanly when nested inside `make test-worktree` itself); [`docs/03_runbooks/parallel-worktrees.md`](docs/03_runbooks/parallel-worktrees.md) human-facing operating procedure documenting the residual root-file risk from the Dockerfile workaround and how to mitigate. **Cross-model review (11 cycles, 39 findings, 39/39 accepted):** spec GPT-5.5 3 cycles (11 → 5 → 1); plan GPT-5.5 3 cycles (10 → 2 → 0); Phase 1 Gemini 2 findings (dynamic `MAIN_REPO`, `line.strip()` for CommonMark-compliant fence detection); Phase 2 GPT-5.5 1 cycle (6 findings: hermetic smoke + `docker` prefix in dry-run + `--` positional path + set -e exit-line + root-file residual risk in runbook + tightened assertions); final GPT-5.5 3 cycles (2 → 0 catching the in-container script-self-test recursion seam). **Operator-path verification:** `make test-worktree` end-to-end against the live Compose stack → exit 0, `git status` pre == post (zero leak). **Tangentials captured during the work:** [`chore_state_md_size_compression`](docs/02_product/planned_features/chore_state_md_size_compression/idea.md) (this file is 360 KB / ~92K tokens, exceeding the Read tool's cap and contradicting CLAUDE.md's "fast-path context" claim — needs operator-judgment-shaped active/archived split, deferred to a separate PR) and [`bug_dockerfile_venv_root_owned_after_user_switch`](docs/02_product/planned_features/bug_dockerfile_venv_root_owned_after_user_switch/idea.md) (`/app/.venv` package metadata is root-owned because Dockerfile:107 runs `uv sync` before `USER relyloop` on line 109; `--user root` + `PYTHONDONTWRITEBYTECODE=1` workarounds in the script become deletable once the 1-line Dockerfile fix lands). **Deferred work:** [`phase3_idea.md`](docs/02_product/planned_features/infra_agent_sibling_worktree_isolation/phase3_idea.md) — per-worktree `DATABASE_URL_FILE` override following the `*_FILE`-mounted-secret pattern (D-2 pre-locked at spec time); Backlog priority, picked up when a migration-collision incident between concurrent worktrees motivates it. **Folder stays in `planned_features/`** per the `/pipeline` PARTIAL-state convention. **CI:** all non-smoke checks green (backend lint+typecheck+unit+contract+coverage, frontend lint+typecheck+tests+build, docker buildx, gitleaks, secrets-files-guard); pre-existing `smoke (operator-path tutorial flow)` failure on `main` for 5+ consecutive PR-runs (tracked by `bug_smoke_dashboard_demo_state_locator_missing`) bypassed via admin-merge route (a). **No migration; Alembic head unchanged at `0020_studies_baseline_trial_id_trials_is_baseline`.** 38th MVP1-era artifact. 1419 backend unit tests pass + 13 new (5 doc-section + 8 script smoke) = 1432 unit tests on `main` after this merge. +- **2026-05-25 — `infra_agent_sibling_worktree_isolation` Phase 1 + Phase 2 admin-merged into `main` as PR #249 squash `22f878f`.** Two-phase agent-operating guidance for the parallel-worktree workflow surfaced by PR #216's reconciler-agent leak. **Phase 1** adds a new `## Working in sibling worktrees` section to [`CLAUDE.md`](CLAUDE.md) (between `## Common Pitfalls` and `## Bug Fix Protocol`) documenting (a) the bind-mount-anchors-to-main pitfall, (b) the **Compose-anchored host paths catalog** in a Markdown table with writability column distinguishing writable mounts that silently propagate writes to the operator's main worktree (`./migrations/`, `./data/postgres/`, `./data/redis/`, `./data/repo-clones/`) from `:ro` mounts that fail loudly with `EROFS` (`./alembic.ini`, `./samples/`), (c) the operation-mode-based safe-paths rule (sibling-worktree direct edits via Edit/Write/git are always safe even for sibling-local `migrations/`; container-mediated writes via `docker cp` / `docker exec` / `docker compose cp` / `docker compose exec` are hazardous regardless of command form), (d) a one-shot `docker run` recipe with the `*_FILE`-mounted-secret pattern (Rule #2) + `--user root` + `PYTHONDONTWRITEBYTECODE=1` Dockerfile-bug workarounds, (e) cross-reference to `impl-execute` Step 0a / 6b / 9.3 for worktree lifecycle, (f) forward-pointer to the remaining deferred capability. **Phase 1 regression suite** at [`backend/tests/unit/docs/test_claude_md_sections.py`](backend/tests/unit/docs/test_claude_md_sections.py) — 5 tests locking historically-observed failure modes: section existence, ordering between Common Pitfalls and Bug Fix Protocol, absence of bare `DATABASE_URL=postgresql://`, catalog-row attribution (no `worker` for migrations/alembic/samples rows; `worker` present in `data/repo-clones` row), exactly-one fenced `bash` block. 8 mutation tests verified each test fails on its targeted regression. **Phase 2** mechanizes the recipe: [`scripts/run-tests-in-worktree.sh`](scripts/run-tests-in-worktree.sh) (191 LOC bash; auto-detects main + sibling worktree paths via `git worktree list | awk '{print $1; exit}'`, validates `secrets/database_url`, builds canonical docker run argv with 11 bind mounts, joins `relyloop_default` network, dispatches via `uv run ` so dev deps install on-demand; supports `--dry-run` for argv preview with `docker` prefix, `--cmd ""` for Makefile path, and `-- positional` for quoted args like `pytest -k 'foo bar'`; `if docker; then RC=0; else RC=$?; fi` guard preserves the exit-line on failures); [`make test-worktree`](Makefile) target with optional `CMD=` override; [`backend/tests/unit/scripts/test_run_tests_in_worktree.py`](backend/tests/unit/scripts/test_run_tests_in_worktree.py) 8 hermetic smoke tests (fake `$MAIN_REPO` tempdir, never invokes real docker, module-level `pytestmark = pytest.mark.skipif(RELYLOOP_IN_WORKTREE_CONTAINER=1)` so the smoke skips cleanly when nested inside `make test-worktree` itself); [`docs/03_runbooks/parallel-worktrees.md`](docs/03_runbooks/parallel-worktrees.md) human-facing operating procedure documenting the residual root-file risk from the Dockerfile workaround and how to mitigate. **Cross-model review (11 cycles, 39 findings, 39/39 accepted):** spec GPT-5.5 3 cycles (11 → 5 → 1); plan GPT-5.5 3 cycles (10 → 2 → 0); Phase 1 Gemini 2 findings (dynamic `MAIN_REPO`, `line.strip()` for CommonMark-compliant fence detection); Phase 2 GPT-5.5 1 cycle (6 findings: hermetic smoke + `docker` prefix in dry-run + `--` positional path + set -e exit-line + root-file residual risk in runbook + tightened assertions); final GPT-5.5 3 cycles (2 → 0 catching the in-container script-self-test recursion seam). **Operator-path verification:** `make test-worktree` end-to-end against the live Compose stack → exit 0, `git status` pre == post (zero leak). **Tangentials captured during the work:** [`chore_state_md_size_compression`](docs/00_overview/planned_features/chore_state_md_size_compression/idea.md) (this file is 360 KB / ~92K tokens, exceeding the Read tool's cap and contradicting CLAUDE.md's "fast-path context" claim — needs operator-judgment-shaped active/archived split, deferred to a separate PR) and [`bug_dockerfile_venv_root_owned_after_user_switch`](docs/00_overview/planned_features/bug_dockerfile_venv_root_owned_after_user_switch/idea.md) (`/app/.venv` package metadata is root-owned because Dockerfile:107 runs `uv sync` before `USER relyloop` on line 109; `--user root` + `PYTHONDONTWRITEBYTECODE=1` workarounds in the script become deletable once the 1-line Dockerfile fix lands). **Deferred work:** [`phase3_idea.md`](docs/00_overview/planned_features/infra_agent_sibling_worktree_isolation/phase3_idea.md) — per-worktree `DATABASE_URL_FILE` override following the `*_FILE`-mounted-secret pattern (D-2 pre-locked at spec time); Backlog priority, picked up when a migration-collision incident between concurrent worktrees motivates it. **Folder stays in `planned_features/`** per the `/pipeline` PARTIAL-state convention. **CI:** all non-smoke checks green (backend lint+typecheck+unit+contract+coverage, frontend lint+typecheck+tests+build, docker buildx, gitleaks, secrets-files-guard); pre-existing `smoke (operator-path tutorial flow)` failure on `main` for 5+ consecutive PR-runs (tracked by `bug_smoke_dashboard_demo_state_locator_missing`) bypassed via admin-merge route (a). **No migration; Alembic head unchanged at `0020_studies_baseline_trial_id_trials_is_baseline`.** 38th MVP1-era artifact. 1419 backend unit tests pass + 13 new (5 doc-section + 8 script smoke) = 1432 unit tests on `main` after this merge. -- **2026-05-25 — `feat_study_clone_narrow_bounds` admin-merged into `main` as PR #247 squash `8b58d3d9`.** Step-4 opt-in checkbox in `CreateStudyModal` rewrites the cloned `search_space` so each numeric `low`/`high` clamps to ±20% around the source study's winning param values (read from `recommended_config` via the digest endpoint). **Pure frontend, no backend / migration / new endpoints.** 4 stories across 1 epic. **Helper** at [`ui/src/lib/narrow-bounds.ts`](ui/src/lib/narrow-bounds.ts) — pure `narrowBoundsAroundWinner(spaceJson, winnerParams, percent=20): NarrowBoundsResult`; mirrors every `SearchSpace.model_validate` constraint (FloatParam `low < high`, log-uniform `low > 0`, IntParam `low <= high`, categorical untouched); negative-safe min/max for the ±p% target (D-10); explicit `winner === 0` skip for both float AND int (D-10 consistency — Gemini-flagged); defensive guard against `null` / non-object parsed inputs (Gemini-flagged); skip reasons surface per param (categorical / missing_winner / non_numeric_winner / degenerate_intersection / log_uniform_zero_floor). **Hook** — `useStudyDigest` widened to `(studyId: string | undefined, opts?: { enabled?: boolean })` (D-12 additive, backward-compat; mirrors `useStudy(id, { enabled })`). **Modal** ([`create-study-modal.tsx`](ui/src/components/studies/create-study-modal.tsx)) — Step 4 inserts a bordered card between the Study-name input and `` with the checkbox + native `
` reference panel; FR-1 visibility gate (`cloneSource` + digest success + non-empty `recommended_config`) hides entirely otherwise (D-1 hide-vs-disable); capture-on-true / clear-on-false ref invariant for restore-on-uncheck; §4 anti-pattern precondition guard skips the helper when `searchSpaceError !== null`; D-11 no-op preserves textarea bytes when `narrowed.length === 0`; defensive `?.` on `recommended_config` access (Gemini-flagged). **Glossary** — new `study.narrow_bounds_checkbox` entry with both short (≤140 chars) and long (≤800 chars) forms; long form documents float/int/categorical handling, skip reasons, and discard-on-uncheck for manual post-rewrite edits. **Tests:** 18 helper-unit (every algorithm branch — positive/negative/zero/out-of-bounds winners, log-uniform floor + zero-floor skip, int rounding + single-value valid + degenerate, categorical/missing/non-numeric skips, multi-param mix, all-skipped, custom percent, malformed JSON, null-parses, object-without-params no-op, int-winner-0 skip) + 4 hook tests + 8 modal component tests (gate visibility × 4 paths, check/uncheck/restore × 3, no-op toast, reference panel sorted rows + banner stability) + 1 real-backend Playwright E2E (clone → Step 4 → check → assert textarea narrowed to [2.0, 3.0] → uncheck → restore to [0.5, 5.0]; full submit round-trip omitted due to pre-existing fixture mismatch captured separately) + updates to 2 v1-clone test files (adding default `/digest → 404` MSW stub so the FR-1 gate stays closed). **Cross-model review (4 cycles):** spec GPT-5.5 cycle 1 verdict clean (9 Medium/Low all applied); plan GPT-5.5 cycle 1 verdict minor (6 Medium/Low all applied, no High); Gemini Code Assist 4 Medium (3 accepted + 3 new unit tests, 1 rejected with cited counter-evidence — no shadcn `Checkbox` primitive exists in this codebase; native `` is the established pattern across `row-log-toggle.tsx`, `data-table.tsx`, `data-table-column-visibility.tsx`); GPT-5.5 final review verdict clean (2 Low polish fixes applied — unused `API_BASE` constant + glossary 'rounded outward' → 'tightens inward' wording correction). **Docs:** new 'Step-4 derived-value toggles' subsection in [`ui-architecture.md`](docs/01_architecture/ui-architecture.md) (FR-14) documenting the capture-on-true / clear-on-false ref invariant + widened `useStudyDigest({ enabled })` signature; forward-pointer line added to the v1 clone spec's 'Out of scope' bullet. **Tangential bug captured:** [`bug_clone_e2e_seed_template_params_mismatch/idea.md`](docs/02_product/planned_features/bug_clone_e2e_seed_template_params_mismatch/idea.md) — `seedTemplate` declares `boost` while `seed_study_completed_with_digest` writes `title.boost` into `search_space.params`; both seeded via repo bypassing `validate_against_template`; blocks Step-4 → Step-5 transition in clone E2E specs (also blocks v1 `study-clone.spec.ts`'s same path); 5-LOC fix. **CI:** 4/5 lanes green (backend / frontend / docker / backend-coverage); smoke E2E lane failure pre-existing on main matching `bug_smoke_dashboard_demo_state_locator_missing` + the newly-captured `bug_clone_e2e_seed_template_params_mismatch`. **Admin-merged** because smoke is still red on those orthogonal pre-existing regressions; `ui/tests/e2e/dashboard-reseed.spec.ts`, `dashboard.spec.ts`, `followup_run.spec.ts` are untouched by this branch. **No migration; Alembic head unchanged at `0020_studies_baseline_trial_id_trials_is_baseline`.** 37th MVP1-era artifact. +- **2026-05-25 — `feat_study_clone_narrow_bounds` admin-merged into `main` as PR #247 squash `8b58d3d9`.** Step-4 opt-in checkbox in `CreateStudyModal` rewrites the cloned `search_space` so each numeric `low`/`high` clamps to ±20% around the source study's winning param values (read from `recommended_config` via the digest endpoint). **Pure frontend, no backend / migration / new endpoints.** 4 stories across 1 epic. **Helper** at [`ui/src/lib/narrow-bounds.ts`](ui/src/lib/narrow-bounds.ts) — pure `narrowBoundsAroundWinner(spaceJson, winnerParams, percent=20): NarrowBoundsResult`; mirrors every `SearchSpace.model_validate` constraint (FloatParam `low < high`, log-uniform `low > 0`, IntParam `low <= high`, categorical untouched); negative-safe min/max for the ±p% target (D-10); explicit `winner === 0` skip for both float AND int (D-10 consistency — Gemini-flagged); defensive guard against `null` / non-object parsed inputs (Gemini-flagged); skip reasons surface per param (categorical / missing_winner / non_numeric_winner / degenerate_intersection / log_uniform_zero_floor). **Hook** — `useStudyDigest` widened to `(studyId: string | undefined, opts?: { enabled?: boolean })` (D-12 additive, backward-compat; mirrors `useStudy(id, { enabled })`). **Modal** ([`create-study-modal.tsx`](ui/src/components/studies/create-study-modal.tsx)) — Step 4 inserts a bordered card between the Study-name input and `` with the checkbox + native `
` reference panel; FR-1 visibility gate (`cloneSource` + digest success + non-empty `recommended_config`) hides entirely otherwise (D-1 hide-vs-disable); capture-on-true / clear-on-false ref invariant for restore-on-uncheck; §4 anti-pattern precondition guard skips the helper when `searchSpaceError !== null`; D-11 no-op preserves textarea bytes when `narrowed.length === 0`; defensive `?.` on `recommended_config` access (Gemini-flagged). **Glossary** — new `study.narrow_bounds_checkbox` entry with both short (≤140 chars) and long (≤800 chars) forms; long form documents float/int/categorical handling, skip reasons, and discard-on-uncheck for manual post-rewrite edits. **Tests:** 18 helper-unit (every algorithm branch — positive/negative/zero/out-of-bounds winners, log-uniform floor + zero-floor skip, int rounding + single-value valid + degenerate, categorical/missing/non-numeric skips, multi-param mix, all-skipped, custom percent, malformed JSON, null-parses, object-without-params no-op, int-winner-0 skip) + 4 hook tests + 8 modal component tests (gate visibility × 4 paths, check/uncheck/restore × 3, no-op toast, reference panel sorted rows + banner stability) + 1 real-backend Playwright E2E (clone → Step 4 → check → assert textarea narrowed to [2.0, 3.0] → uncheck → restore to [0.5, 5.0]; full submit round-trip omitted due to pre-existing fixture mismatch captured separately) + updates to 2 v1-clone test files (adding default `/digest → 404` MSW stub so the FR-1 gate stays closed). **Cross-model review (4 cycles):** spec GPT-5.5 cycle 1 verdict clean (9 Medium/Low all applied); plan GPT-5.5 cycle 1 verdict minor (6 Medium/Low all applied, no High); Gemini Code Assist 4 Medium (3 accepted + 3 new unit tests, 1 rejected with cited counter-evidence — no shadcn `Checkbox` primitive exists in this codebase; native `` is the established pattern across `row-log-toggle.tsx`, `data-table.tsx`, `data-table-column-visibility.tsx`); GPT-5.5 final review verdict clean (2 Low polish fixes applied — unused `API_BASE` constant + glossary 'rounded outward' → 'tightens inward' wording correction). **Docs:** new 'Step-4 derived-value toggles' subsection in [`ui-architecture.md`](docs/01_architecture/ui-architecture.md) (FR-14) documenting the capture-on-true / clear-on-false ref invariant + widened `useStudyDigest({ enabled })` signature; forward-pointer line added to the v1 clone spec's 'Out of scope' bullet. **Tangential bug captured:** [`bug_clone_e2e_seed_template_params_mismatch/idea.md`](docs/00_overview/planned_features/bug_clone_e2e_seed_template_params_mismatch/idea.md) — `seedTemplate` declares `boost` while `seed_study_completed_with_digest` writes `title.boost` into `search_space.params`; both seeded via repo bypassing `validate_against_template`; blocks Step-4 → Step-5 transition in clone E2E specs (also blocks v1 `study-clone.spec.ts`'s same path); 5-LOC fix. **CI:** 4/5 lanes green (backend / frontend / docker / backend-coverage); smoke E2E lane failure pre-existing on main matching `bug_smoke_dashboard_demo_state_locator_missing` + the newly-captured `bug_clone_e2e_seed_template_params_mismatch`. **Admin-merged** because smoke is still red on those orthogonal pre-existing regressions; `ui/tests/e2e/dashboard-reseed.spec.ts`, `dashboard.spec.ts`, `followup_run.spec.ts` are untouched by this branch. **No migration; Alembic head unchanged at `0020_studies_baseline_trial_id_trials_is_baseline`.** 37th MVP1-era artifact. - **2026-05-25 — `feat_study_baseline_trial` admin-merged into `main` as PR #245 squash `53be6c63`.** 36th MVP1-era artifact. Implements the deferred Phase 2 of `feat_pr_metric_confidence` (PR #180, 2026-05-21): the orchestrator runs a single non-Optuna baseline trial BEFORE Optuna starts, persists it as a real `Trial` row with `is_baseline=TRUE` + `optuna_trial_number=-1` (NOT-NULL sentinel filler, NOT a discriminator — the canonical flag is `is_baseline`), stamps `studies.baseline_trial_id` + `baseline_metric` via the new `services.study_state.stamp_baseline_trial` service-layer chokepoint (FR-12), and every data-driven downstream consumer flips automatically from "vs runner-up" to "vs baseline" — `compute_study_confidence`'s per-query outcomes branch (FR-4), `evaluate_chain_gate`'s lift computation (FR-5, now also direction-aware so minimize objectives stop mis-classifying "better than baseline" — closes a latent bug in `feat_auto_followup_studies` as a side effect), digest narrative framing (FR-7), PR body, ConfidencePanel label, `_compute_metric_delta` (auto-populates `delta_pct` in the PR body's `## Metric delta` section). New trials-table "Show baseline trial" UI toggle (FR-9). **Migration 0020:** adds `studies.baseline_trial_id VARCHAR(36) NULL` (denormalized FK, not enforced — same pattern as `best_trial_id`) + `trials.is_baseline BOOLEAN NOT NULL DEFAULT FALSE` + partial unique index `uq_trials_study_baseline_complete ON trials (study_id) WHERE is_baseline = TRUE AND status = 'complete'` — the partial index is defense layer 2 of the 3-layer resume-race guard (D-16: Arq `_job_id` dedupe + partial unique index + FR-12 stamping helper's `WHERE baseline_trial_id IS NULL` predicate). **4-tier params resolver** at `backend/app/domain/study/baseline_resolver.py`: parent_proposal config → parent_study winner → operator-supplied `config.baseline_params` → template-defaults middle-of-range (float arithmetic mean or log geometric mean; int integer-division midpoint; categorical lower-midpoint `choices[(len-1)//2]`). **3-cycle spec + 3-cycle plan + 1 CI-fix-round.** CI-fix root cause: `test_study_cancel` hung 8min in backend-full because `_wait_for_baseline_trial_by_id`/`_wait_for_baseline_trial_by_study` didn't check `study.status` for cancel — production correctness bug, not just test infra. Without the cancel-aware bail-out, an operator cancel mid-baseline would wait the full `_BASELINE_WAIT_FLOOR_S` (60s) before the orchestrator's polling loop noticed. Fix: new `_study_cancelled()` helper polled every tick in both wait helpers + extending `_InProcessPool.enqueue_job` test fixture to dispatch `run_baseline_trial` inline (mirroring the existing `run_trial` branch) + monkeypatching `_BASELINE_WAIT_FLOOR_S` = 2.0 in `_running_orchestrator` so cancel-mid-baseline tests don't wait 60s for production-default timeouts. Spec converged 3-cycle GPT-5.5 (26 findings: 14 + 9 + 1, 25 accepted + 1 rejected with cited counter-evidence at `backend/app/db/models/trial.py:48-51`); plan converged 3-cycle (12 findings: 10 + 2 + cycle-3 clean, all accepted). **Gemini Code Assist** PR #245 review: 4 findings, ALL rejected with cited counter-evidence — 3 High duplicates were false positives on `repo.create_trial`'s `**fields: object` signature ([trial.py:53](backend/app/db/repo/trial.py#L53)) which accepts any column-named kwarg via `Trial(**fields)`, and 1 Medium was a redundant `low > 0` defensive check already enforced by `FloatParam.model_validator` at the Pydantic boundary ([search_space.py:49-50](backend/app/domain/study/search_space.py#L49-L50)). **Admin-merged** because smoke is still red on the orthogonal pre-existing dashboard-banner E2E (`dashboard.spec.ts` + `dashboard-reseed.spec.ts` failures, same as PR #232/#234/#236); `ui/tests/e2e/` is untouched by this branch. 1414 backend unit + 791 frontend vitest pass. Only this finalization docs PR remains. -- **2026-05-25 — `feat_study_clone_from_previous` admin-merged into `main` as PR #243 squash `34118ade`.** Operators can now clone an existing study from the study-detail page into the create-study modal, with the source's settings pre-filled and lineage (`studies.parent_study_id`) persisted on the new row. **Backend (3 stories):** additive `CreateStudyRequest.parent_study_id` Pydantic field (length-bounded to 36 chars matching the studies-table id shape), early-placement validation in `_create_study` (`PARENT_STUDY_NOT_FOUND` 404 / `PARENT_STUDY_WRONG_CLUSTER` 422 — placed BEFORE template/qs/jl FK resolution per D-9 so wrong-cluster clones surface the parent-clone error rather than the downstream judgment-cluster mismatch), and `repo.create_study(..., parent_study_id=...)` via the existing variadic kwargs (no repo signature change). No migration — the `parent_study_id` column on `studies` was already shipped in `0003_study_lifecycle_schema.py` and is co-owned with `feat_auto_followup_studies`. 7 integration cases (happy clone, missing parent, wrong cluster, both-axes round-trip, validation-order, cascade-of-clone via API, auto_followup self-suppression matching AC-12 lifecycle) + 2 contract envelope-shape cases + 2 runtime-422 length-bound cases. **Frontend (3 stories):** `PrefillValues` widened with `parent_study_id?: string` + UI-only `cloneSource?: {id, name}` (the existing `parent` field became optional so the clone path can omit it); new pure helper `buildPrefillFromStudy(source: StudyDetail): PrefillValues` at `ui/src/components/studies/prefill-from-study.ts` (truncates source name to 200 chars + appends " (clone)" suffix, NO ellipsis); "Clone study" button on `StudyActionBar` (`variant="outline"`, visible on every status per FR-1, sits to the LEFT of Cancel) with running-source confirmation `AlertDialog` (`data-testid="clone-running-confirm"`) per FR-11 — terminal-state sources (queued/completed/failed/cancelled) navigate directly to `/studies?clone_from=`; cloned-from banner in `CreateStudyModal` (`data-testid="cloned-from-banner"` with `` to source) renders above Step 1 when `initialValues.cloneSource` is set, visible across all 5 wizard steps per the IA placement decision; modal-submit serializer hygiene assembles the POST body field-by-field with new doc comment forbidding `...initialValues` spreads — `cloneSource` is UI-only per D-12 and MUST NOT reach the wire; deep-link `?clone_from=` reader on `/studies` page lives inside `StudiesPageInner` (under the `` boundary required by Next 16 `useSearchParams`) with one-shot `useRef` guard that re-arms on `cloneFromId` change to handle in-app navigation between two source studies (Gemini PR #243 finding #1 — `b74b165a`); invalid/empty/404 all converge on `toast.error + router.replace('/studies') + open empty modal`. **2 new glossary entries** (`study.clone_button`, `study.cloned_from_banner`) — both `short` strings under the 140-char glossary bound. **E2E:** `ui/tests/e2e/study-clone.spec.ts` exercises the full happy path against a real backend (seed via `seedFullChain` + `seedStudyCompletedWithDigest`, navigate to study-detail, click Clone, walk the wizard with the prefilled name carrying the `(clone)` suffix, submit, assert POST response carries `parent_study_id == sourceId`, then GET the new study and confirm persistence). **Docs:** new `Deep-link ?clone_from=` paragraph under "Step-4 auto-fill" in `ui-architecture.md`. **Cross-model review (7 rounds total):** 3-cycle spec → 3-cycle plan → 1-cycle Epic-1 phase-gate (4 findings, 2 patched, 2 rejected with cited counter-evidence) → 1-cycle Epic-2 phase-gate (2 findings, both rejected with cited counter-evidence — length-only validation per spec §11; case (vi) already in branch at the time of the next-cycle review) → 1-cycle final-pass GPT-5.5 (4 findings, all rejected: 2 branch-behind artifacts resolved by the `6e23963a` merge from main bringing in PR #234 + PR #236, 1 wrong-file-path in plan, 1 diff-scope excluded docs/) → 1-cycle Gemini Code Assist (3 findings: 1 accepted+fixed in `b74b165a` with regression test case (vii), 1 deferred-as-non-regression because the same `as { ... }` type assertion pattern ships in `proposals/[id]/page.tsx:189-202`, 1 resolved-by-merge). **CI:** 6/7 green on the merge commit; the 1 failing check (smoke / operator-path tutorial flow) is the pre-existing dashboard demo-state-locator regression captured as `bug_smoke_dashboard_demo_state_locator_missing` and reproduces identically on main run #26397500888. **Two tangential bug ideas captured during this session:** [`bug_datatable_col_vis_density_localstorage_undefined_jsdom`](docs/02_product/planned_features/bug_datatable_col_vis_density_localstorage_undefined_jsdom/idea.md) (3 pre-existing vitest failures verified pre-existing via `git stash` — same class as the existing `bug_markdown_doc_localstorage_undefined_jsdom`) + [`bug_smoke_dashboard_demo_state_locator_missing`](docs/02_product/planned_features/bug_smoke_dashboard_demo_state_locator_missing/idea.md) (pre-existing smoke regression on `reset-demo-state-disclosure` + `demo-data-banner` testids). **Follow-up:** [`feat_study_clone_narrow_bounds`](docs/02_product/planned_features/feat_study_clone_narrow_bounds/idea.md) (smart-rewrite of search-space bounds around the source's winner trial — explicitly split from the v1 spec per D-3) remains in `planned_features/` for future scoping. 36th MVP1-era artifact. -- **2026-05-25 — `bug_demo_clusters_unreachable_in_healthz` admin-merged into `main` as PR #236 squash `70b2ae46`.** Closes the SECOND of the two pre-existing smoke-cascade `/healthz` observability bugs surfaced during PR #232 unblock; the FIRST (`bug_openai_capability_check_incapable_on_valid_key`) shipped as PR #234 on 2026-05-24. Both `/healthz` observability gaps from the cascade are now closed; the remaining smoke-gate red is the orthogonal dashboard banner E2E bug (per spec §19 D-6, NOT addressed here). 4 stories + 1 phase-gate-fix commit + 3 CI-fix-round commits = 8 commits across 1 epic. **Domain/service:** new module [`backend/app/services/cluster_health_warmup.py`](backend/app/services/cluster_health_warmup.py) with `run_cluster_health_warmup_background(db_factory, redis_client)` — pages through registered clusters in 200-row windows via SHORT-LIVED DB sessions (post-CI-round-1 per-page refactor: each page opens + closes its own session, so per-cluster HTTP probes don't hold asyncpg connections); pings Redis once at start with WARN-on-fail; per-cluster try/except (CancelledError re-raises); single INFO summary log on completion with `count` / `failures` / `duration_ms` (NO `cache_hits`/`probed` per spec D-8 — `get_or_probe_health: HealthStatus` exposes no source distinction). **FR-7 fix:** [`backend/app/services/cluster.py:202-214`](backend/app/services/cluster.py) `get_or_probe_health`'s `except CredentialsMissing` branch now writes the synthetic unreachable HealthStatus to cache before returning. Pre-fix: returned without caching → credentials-missing clusters re-probed (and re-failed) on every call AND `probe_registered_clusters` saw them as cache-miss-unreachable forever. **Lifespan wiring:** [`backend/app/main.py`](backend/app/main.py) spawns the warmup via `asyncio.create_task` immediately after the capability-check task; cancel/await/swallow on shutdown mirrors the capability-check pattern; **gated by env var** `RELYLOOP_DISABLE_STARTUP_WARMUP` (production: unset → spawns; integration tests: set to "1" via `backend/tests/integration/conftest.py` so the latent webhook merge-handler row-lock race in `test_ac7_concurrent_merges_serialize_via_row_lock` isn't perturbed by asyncio interleaving). **Doc:** new "Cluster health caching" subsection in [`docs/01_architecture/data-model.md`](docs/01_architecture/data-model.md) documenting all three cache-population paths (registration / lazy on-demand / startup warmup) + the `/healthz` race-window caveat. **Tests:** 16 new/updated cases across 4 files — 3 in `test_cluster_service.py` (AC-3 + AC-11 FR-7 cache-write guards) + 7 in `test_cluster_health_warmup.py` unit (AC-2/AC-4/AC-5/AC-6 + FR-5 log shape + FR-6 Redis-ping + FR-4/D-10 shutdown DB-session release) + 4 in `test_main_lifespan.py` (AC-1 + AC-7 + env-var gate + capability-check regression) + 3 in `test_cluster_health_warmup.py` integration (AC-8/AC-9/AC-10, skip outside service containers). Backend unit suite: **1349 pass** (+8 vs prior baseline). Phase-gate review caught 1 Medium (missing integration test file — added in `d3a63bf6`). Final cross-model review caught 4 (3 Medium + 1 Low — refactor to per-page session lifecycle + tighter AC-8/AC-9/AC-10 assertions). **CI-fix rounds:** round 1 surfaced `test_ac7_concurrent_merges_serialize_via_row_lock` failure → fixed by releasing DB session before probes (commit `545753ae`); round 2 same test still failed → root-caused as latent webhook merge-handler row-lock race exposed by ANY second lifespan task → env-var gate `RELYLOOP_DISABLE_STARTUP_WARMUP` (commit `5970f323`) + captured the underlying race as a separate bug idea at [`bug_webhook_concurrent_merge_race_timing_sensitive/idea.md`](docs/02_product/planned_features/bug_webhook_concurrent_merge_race_timing_sensitive/idea.md); round 3 surfaced env-var leakage into unit tests via pytest collection order → explicit `monkeypatch.delenv` in the lifespan-spawn test (commit `3ae05e8a`). **Spec converged at GPT-5.5 cycle 3 (18 findings, all accepted); plan converged at cycle 3 (11 findings, all accepted); phase-gate cycle 1 (1 accepted); final review cycle 1 (4 accepted).** Gemini Code Assist: clean review, zero line-level findings. **Alembic head unchanged at `0019_digests_suggested_followups_jsonb`.** +- **2026-05-25 — `feat_study_clone_from_previous` admin-merged into `main` as PR #243 squash `34118ade`.** Operators can now clone an existing study from the study-detail page into the create-study modal, with the source's settings pre-filled and lineage (`studies.parent_study_id`) persisted on the new row. **Backend (3 stories):** additive `CreateStudyRequest.parent_study_id` Pydantic field (length-bounded to 36 chars matching the studies-table id shape), early-placement validation in `_create_study` (`PARENT_STUDY_NOT_FOUND` 404 / `PARENT_STUDY_WRONG_CLUSTER` 422 — placed BEFORE template/qs/jl FK resolution per D-9 so wrong-cluster clones surface the parent-clone error rather than the downstream judgment-cluster mismatch), and `repo.create_study(..., parent_study_id=...)` via the existing variadic kwargs (no repo signature change). No migration — the `parent_study_id` column on `studies` was already shipped in `0003_study_lifecycle_schema.py` and is co-owned with `feat_auto_followup_studies`. 7 integration cases (happy clone, missing parent, wrong cluster, both-axes round-trip, validation-order, cascade-of-clone via API, auto_followup self-suppression matching AC-12 lifecycle) + 2 contract envelope-shape cases + 2 runtime-422 length-bound cases. **Frontend (3 stories):** `PrefillValues` widened with `parent_study_id?: string` + UI-only `cloneSource?: {id, name}` (the existing `parent` field became optional so the clone path can omit it); new pure helper `buildPrefillFromStudy(source: StudyDetail): PrefillValues` at `ui/src/components/studies/prefill-from-study.ts` (truncates source name to 200 chars + appends " (clone)" suffix, NO ellipsis); "Clone study" button on `StudyActionBar` (`variant="outline"`, visible on every status per FR-1, sits to the LEFT of Cancel) with running-source confirmation `AlertDialog` (`data-testid="clone-running-confirm"`) per FR-11 — terminal-state sources (queued/completed/failed/cancelled) navigate directly to `/studies?clone_from=`; cloned-from banner in `CreateStudyModal` (`data-testid="cloned-from-banner"` with `` to source) renders above Step 1 when `initialValues.cloneSource` is set, visible across all 5 wizard steps per the IA placement decision; modal-submit serializer hygiene assembles the POST body field-by-field with new doc comment forbidding `...initialValues` spreads — `cloneSource` is UI-only per D-12 and MUST NOT reach the wire; deep-link `?clone_from=` reader on `/studies` page lives inside `StudiesPageInner` (under the `` boundary required by Next 16 `useSearchParams`) with one-shot `useRef` guard that re-arms on `cloneFromId` change to handle in-app navigation between two source studies (Gemini PR #243 finding #1 — `b74b165a`); invalid/empty/404 all converge on `toast.error + router.replace('/studies') + open empty modal`. **2 new glossary entries** (`study.clone_button`, `study.cloned_from_banner`) — both `short` strings under the 140-char glossary bound. **E2E:** `ui/tests/e2e/study-clone.spec.ts` exercises the full happy path against a real backend (seed via `seedFullChain` + `seedStudyCompletedWithDigest`, navigate to study-detail, click Clone, walk the wizard with the prefilled name carrying the `(clone)` suffix, submit, assert POST response carries `parent_study_id == sourceId`, then GET the new study and confirm persistence). **Docs:** new `Deep-link ?clone_from=` paragraph under "Step-4 auto-fill" in `ui-architecture.md`. **Cross-model review (7 rounds total):** 3-cycle spec → 3-cycle plan → 1-cycle Epic-1 phase-gate (4 findings, 2 patched, 2 rejected with cited counter-evidence) → 1-cycle Epic-2 phase-gate (2 findings, both rejected with cited counter-evidence — length-only validation per spec §11; case (vi) already in branch at the time of the next-cycle review) → 1-cycle final-pass GPT-5.5 (4 findings, all rejected: 2 branch-behind artifacts resolved by the `6e23963a` merge from main bringing in PR #234 + PR #236, 1 wrong-file-path in plan, 1 diff-scope excluded docs/) → 1-cycle Gemini Code Assist (3 findings: 1 accepted+fixed in `b74b165a` with regression test case (vii), 1 deferred-as-non-regression because the same `as { ... }` type assertion pattern ships in `proposals/[id]/page.tsx:189-202`, 1 resolved-by-merge). **CI:** 6/7 green on the merge commit; the 1 failing check (smoke / operator-path tutorial flow) is the pre-existing dashboard demo-state-locator regression captured as `bug_smoke_dashboard_demo_state_locator_missing` and reproduces identically on main run #26397500888. **Two tangential bug ideas captured during this session:** [`bug_datatable_col_vis_density_localstorage_undefined_jsdom`](docs/00_overview/planned_features/bug_datatable_col_vis_density_localstorage_undefined_jsdom/idea.md) (3 pre-existing vitest failures verified pre-existing via `git stash` — same class as the existing `bug_markdown_doc_localstorage_undefined_jsdom`) + [`bug_smoke_dashboard_demo_state_locator_missing`](docs/00_overview/planned_features/bug_smoke_dashboard_demo_state_locator_missing/idea.md) (pre-existing smoke regression on `reset-demo-state-disclosure` + `demo-data-banner` testids). **Follow-up:** [`feat_study_clone_narrow_bounds`](docs/00_overview/planned_features/feat_study_clone_narrow_bounds/idea.md) (smart-rewrite of search-space bounds around the source's winner trial — explicitly split from the v1 spec per D-3) remains in `planned_features/` for future scoping. 36th MVP1-era artifact. +- **2026-05-25 — `bug_demo_clusters_unreachable_in_healthz` admin-merged into `main` as PR #236 squash `70b2ae46`.** Closes the SECOND of the two pre-existing smoke-cascade `/healthz` observability bugs surfaced during PR #232 unblock; the FIRST (`bug_openai_capability_check_incapable_on_valid_key`) shipped as PR #234 on 2026-05-24. Both `/healthz` observability gaps from the cascade are now closed; the remaining smoke-gate red is the orthogonal dashboard banner E2E bug (per spec §19 D-6, NOT addressed here). 4 stories + 1 phase-gate-fix commit + 3 CI-fix-round commits = 8 commits across 1 epic. **Domain/service:** new module [`backend/app/services/cluster_health_warmup.py`](backend/app/services/cluster_health_warmup.py) with `run_cluster_health_warmup_background(db_factory, redis_client)` — pages through registered clusters in 200-row windows via SHORT-LIVED DB sessions (post-CI-round-1 per-page refactor: each page opens + closes its own session, so per-cluster HTTP probes don't hold asyncpg connections); pings Redis once at start with WARN-on-fail; per-cluster try/except (CancelledError re-raises); single INFO summary log on completion with `count` / `failures` / `duration_ms` (NO `cache_hits`/`probed` per spec D-8 — `get_or_probe_health: HealthStatus` exposes no source distinction). **FR-7 fix:** [`backend/app/services/cluster.py:202-214`](backend/app/services/cluster.py) `get_or_probe_health`'s `except CredentialsMissing` branch now writes the synthetic unreachable HealthStatus to cache before returning. Pre-fix: returned without caching → credentials-missing clusters re-probed (and re-failed) on every call AND `probe_registered_clusters` saw them as cache-miss-unreachable forever. **Lifespan wiring:** [`backend/app/main.py`](backend/app/main.py) spawns the warmup via `asyncio.create_task` immediately after the capability-check task; cancel/await/swallow on shutdown mirrors the capability-check pattern; **gated by env var** `RELYLOOP_DISABLE_STARTUP_WARMUP` (production: unset → spawns; integration tests: set to "1" via `backend/tests/integration/conftest.py` so the latent webhook merge-handler row-lock race in `test_ac7_concurrent_merges_serialize_via_row_lock` isn't perturbed by asyncio interleaving). **Doc:** new "Cluster health caching" subsection in [`docs/01_architecture/data-model.md`](docs/01_architecture/data-model.md) documenting all three cache-population paths (registration / lazy on-demand / startup warmup) + the `/healthz` race-window caveat. **Tests:** 16 new/updated cases across 4 files — 3 in `test_cluster_service.py` (AC-3 + AC-11 FR-7 cache-write guards) + 7 in `test_cluster_health_warmup.py` unit (AC-2/AC-4/AC-5/AC-6 + FR-5 log shape + FR-6 Redis-ping + FR-4/D-10 shutdown DB-session release) + 4 in `test_main_lifespan.py` (AC-1 + AC-7 + env-var gate + capability-check regression) + 3 in `test_cluster_health_warmup.py` integration (AC-8/AC-9/AC-10, skip outside service containers). Backend unit suite: **1349 pass** (+8 vs prior baseline). Phase-gate review caught 1 Medium (missing integration test file — added in `d3a63bf6`). Final cross-model review caught 4 (3 Medium + 1 Low — refactor to per-page session lifecycle + tighter AC-8/AC-9/AC-10 assertions). **CI-fix rounds:** round 1 surfaced `test_ac7_concurrent_merges_serialize_via_row_lock` failure → fixed by releasing DB session before probes (commit `545753ae`); round 2 same test still failed → root-caused as latent webhook merge-handler row-lock race exposed by ANY second lifespan task → env-var gate `RELYLOOP_DISABLE_STARTUP_WARMUP` (commit `5970f323`) + captured the underlying race as a separate bug idea at [`bug_webhook_concurrent_merge_race_timing_sensitive/idea.md`](docs/00_overview/planned_features/bug_webhook_concurrent_merge_race_timing_sensitive/idea.md); round 3 surfaced env-var leakage into unit tests via pytest collection order → explicit `monkeypatch.delenv` in the lifespan-spawn test (commit `3ae05e8a`). **Spec converged at GPT-5.5 cycle 3 (18 findings, all accepted); plan converged at cycle 3 (11 findings, all accepted); phase-gate cycle 1 (1 accepted); final review cycle 1 (4 accepted).** Gemini Code Assist: clean review, zero line-level findings. **Alembic head unchanged at `0019_digests_suggested_followups_jsonb`.** - **2026-05-24 — `bug_openai_capability_check_incapable_on_valid_key` admin-merged into `main` as PR #234 squash `d69189db`.** Closes one of the two pre-existing smoke-cascade bugs surfaced during PR #232 unblock; the other (`bug_demo_clusters_unreachable_in_healthz`) still keeps the smoke gate red at the dashboard-banner E2E layer. 4 stories across 1 epic extending the cached `CapabilityResult` + `/healthz` `openai_capabilities` response with a `models_endpoint` field + HTTP status code so operators can diagnose `subsystems.openai: "incapable"` without tailing api logs. **Backend:** `_probe_models_endpoint` return type widens from `bool` to `tuple[bool, int | None]` per spec §19 D-4 (`(True, None)` on `< 400`; `(False, resp.status_code)` only on `>= 400`; `(False, None)` on `httpx.HTTPError`). `CapabilityResult` gains optional `models_endpoint_status_code: int | None = None` (cache schema additive — pre-fix Redis rows deserialize cleanly via Pydantic optional-field default per AC-8). `OpenAICapabilities` response model gains required `models_endpoint: Literal["ok","fail","untested"]` + required-but-nullable `models_endpoint_status_code: int | None` (no Pydantic default; explicit-pass at both `/healthz` constructor sites). `"untested"` literal exists in the response schema only — cached `CapabilityResult.models_endpoint` stays `Literal["ok","fail"]` per D-2 (cache stores only outcomes of real probe runs; cache miss = no row, not a row with `"untested"`). Response handler unchanged: `JSONResponse(content=body.model_dump())` includes `None` fields by default → `models_endpoint_status_code` key is ALWAYS present, explicit `null` when no value. **Security (AC-10 / Absolute Rule #10):** OpenAI response bodies are NEVER captured — only the integer status code. 401 error bodies often quote the bearer token back; surfacing them would leak the secret into `/healthz` (unauthenticated by design) and into the CI smoke-logs.txt artifact. AC-10 regression test mocks a 401 with body `"Invalid Bearer token: sk-redacted-token-abc"` and asserts on three surfaces (Redis JSON via `redis.set.call_args`, captured structlog text, raw `/healthz` response text) that the forbidden substrings never appear while the integer `401` is exposed in `models_endpoint_status_code`. **Architecture doc** updated with success/failure JSON examples for both the cached `CapabilityResult` and the `/healthz` projection, plus a cascade-explanation paragraph and a repo-secret-vs-operator-`.env`-divergence note (when CI reports `models_endpoint: "fail"` + `models_endpoint_status_code: 401`, rotate the repo secret; the diagnostic surface is `pr.yml:444-445` smoke-logs.txt artifact, NOT the wait-loop failure curl — the wait loop succeeds for openai-incapable because overall `status: ok`). **No CI workflow edits in this PR** per D-6/D-9: the existing `curl >> smoke-logs.txt` capture already surfaces the new fields automatically once cached, with zero `.github/workflows/pr.yml` changes. **No migration.** **Tests:** +15 new/updated cases across 4 test files. `backend/tests/unit/test_capability_check.py` (+7): `TestModelsEndpointStatusCode` (401/429/500 capture, ConnectError → None, success path → None, AC-8 pre-fix JSON deserialization) + `TestSecurityRedaction::test_401_body_does_not_leak_into_cache_or_logs`. `backend/tests/unit/test_health.py` (+5): `TestModelsEndpointInHealthzResponse` (AC-1 cache-hit-fail with REAL `probe_openai_state` mapping — proves `subsystems.openai: "incapable"` still maps post-fix; AC-2 cache-hit-network-fail; AC-5 cache-hit-success null-presence; AC-6 cache miss; AC-10 endpoint-layer redaction chaining mock 401 → `check_capabilities` → Redis JSON round-trip → `/healthz` → assert raw response.text body-free). `backend/tests/unit/test_probes.py` (+1): defensive `test_models_endpoint_status_code_field_is_ignored_by_mapping` — guards `probe_openai_state`'s mapping from accidentally gating on the new field. `backend/tests/contract/test_health_contract.py` (+1 + key-set extension): `test_openai_capabilities_schema_advertises_required_and_enum` asserts OpenAPI schema has `models_endpoint.enum == ["ok","fail","untested"]` in `required`, AND `models_endpoint_status_code` in `required` with `anyOf: [integer, null]` (Pydantic v2 nullable encoding). 1335 backend unit pass (+8 from baseline) + 266 contract pass (+3) + 57 pre-existing Postgres-required skips (unchanged). Coverage gate 80% maintained. **Cross-model review history:** spec 3 GPT-5.5 cycles to convergence (13 findings, 12 accepted + 1 rejected with cited counter-evidence on unverifiable-test-paths claim — verified in Pass 1); plan 3 GPT-5.5 cycles to convergence (17 findings all accepted, mostly test-design refinements like splitting AC-10 across Story 1.2 cache-layer + Story 1.3 endpoint-layer); phase-gate 1 cycle (3 findings: F1 Medium WARN-log assertion strengthening + F3 Low success-path doc snippet both accepted into commit `3a40ec1a`; F2 Low rejected because dashboard regen is the auto-run pre-commit hook); final review 1 cycle (1 Low test-helper type hints deferred as non-regression — matches existing untyped `_probe_db(_engine)` pattern at test_health.py:91). Gemini Code Assist returned a clean review with zero line-level findings. **Alembic head unchanged** at `0019_digests_suggested_followups_jsonb`. - **2026-05-24 — `feat_digest_executable_followups_swap_template` (Tier B) admin-merged into `main` as PR #232 squash `791642e0`.** 35th MVP1-era artifact. Admin-merged with smoke gate red because the smoke failure was a compound cascade of pre-existing PR #188 + PR #228 regressions (NOT introduced by Tier B code); 5 in-cascade fixes are bundled in the same squash, 2 remaining issues captured as `bug_openai_capability_check_incapable_on_valid_key/idea.md` + `bug_demo_clusters_unreachable_in_healthz/idea.md` for focused investigation. Tier B's own code passed every other gate (3 GPT-5.5 spec cycles + 2 plan cycles + Gemini accept + final GPT-5.5 review with 6 of 7 findings rejected with cited counter-evidence). The in-cascade smoke fixes shipped in this squash: (1) `OPENAI_API_KEY_TEST` repo secret restored from local `.env` (operator-authorized); (2) Dockerfile `COPY scripts/` so the api container can import `scripts/seed_meaningful_demos.py` constants used by PR #228's `demo_seeding.py`; (3) `_wait_healthy` smoke helper now gates on `openai_capabilities.structured_output == "ok"` (not just top-level `/healthz status:ok`); (4) workflow now runs `make seed-demo FORCE=1` before Playwright E2E so the 4 demo cluster slugs the banner tests assert on actually exist; (5) `test_studies_with_parent_followup` integration test fixture: swap-target search_space now covers ALL declared params + `test_digest_followup_roundtrip` fixture: `best_trial_params` aligned to `declared_params` so the worker's `dropped` set is empty (otherwise a pre-pended "params dropped" text item produced 2 followups instead of the asserted 1). 13 stories across 5 epics extending the Tier-A discriminated union with a 4th `swap_template` followup kind. **Domain:** new `SwapTemplateFollowup` variant (`{kind, rationale, template_id (36-char), search_space}`) + `FOLLOWUP_KIND_VALUES` widens to 4-tuple; `_truncate` promoted to public `truncate_validation_error` per spec D-33; new pure-domain module `backend/app/domain/study/template_swap.py` with `remap_search_space_for_swap_target` + `RemapResult` (trusted-intersection / disjoint-fill / dropped-parent / ignored-LLM name sets) — raises `InvalidSearchSpaceError` on empty swap target / empty trusted intersection / cardinality blowup; calls `build_starter_search_space` only when disjoint fill is non-empty per cycle-1 F2. **Worker:** `DIGEST_RESPONSE_SCHEMA` widens `kind` enum to 4 values + adds uniform `template_id: string` field on every item with empty-string sentinel pre-clean per spec D-29 (OpenAI strict mode rejects oneOf/if/then on item subschemas); catalogue fetch via `list_query_templates(engine_type=parent_engine)` excluding the parent template; new `_apply_swap_template_remap` async helper runs AFTER `[:5]` truncation per AC-15 and downgrades each swap_template item that fails the FR-8 cascade with `reason ∈ {not_found, same_as_parent, engine_type_mismatch, remap_invalid_search_space}` via `digest_followup_validation_downgraded` WARN; happy path emits `digest_followup_swap_template_remapped` INFO with the 4 `RemapResult` name lists; `digest_complete` log adds `followups_swap_template_count`. **LLM prompts:** system prompt gains a `swap_template` subsection covering selection criteria (parameter-importance skew / dead-weight params / cross-template lever sets), the contract (`template_id` must match `` + skip same-as-parent + `search_space` covers intersection only), and the skip-rule for empty catalogues per D-34; user.jinja renders new `` + `` blocks when `render_digest_user_prompt()` receives the new kwargs. **Frontend:** `enums.ts FOLLOWUP_KIND_VALUES` widens to match backend; `types.ts FollowupItem` union widens; `SuggestedFollowupsPanel` refactored from inline if-chain to exhaustive `Record` lookups per D-28 (SHOWS_SEARCH_SPACE_EXPANDER / SHOWS_RUN_BUTTON / SHOWS_DECLARED_PARAMS_DIFF); new same-file `SwapTemplateCard` child component calls `useTemplate(followup.template_id)` at its own top per cycle-1 F2/F3 (Rules of Hooks + N-target scale); side-by-side parent-vs-target declared-params diff with shared-key highlight; new `ACTIONABLE_FOLLOWUP_KINDS` Record gate on the proposal-detail page so swap_template-only digests still trigger parent-study fetch (cycle-1 F1 regression guard); `resolveTemplateIdForPrefill` exhaustive switch helper seeds `template_id = followup.template_id` for swap_template per AC-12; lazy `useTemplate(parentStudy.data?.template_id)` passes parent template to the panel; create-study-modal autofill effect short-circuits when `initialValues.search_space_text` is non-empty per FR-14 + AC-16; 2 new glossary keys (`proposal.followup_kind_swap_template` + `proposal.followup_declared_params_diff`). **No new migration** — Tier A's `0019_digests_suggested_followups_jsonb` + lineage columns apply unchanged. **Tests:** 7 new domain unit tests (`test_template_swap.py`) + 6 new followup union tests + 1 swap_template parse-layer downgrade test + 7 new worker validation tests (`test_digest_followup_validation.py` — 4 reason codes + happy-path INFO + non-swap passthrough no-DB-lookup + per-call cache test) + 4 new prompt-render tests (the two new jinja blocks render-when-provided / omit-when-absent) + 2 new integration tests (swap_template happy path + AC-13 single-template install yields no swap items) + 1 new integration test for AC-12 cross-template lineage on `POST /api/v1/studies` + 2 new contract tests (DigestResponse round-trip + OpenAPI $defs include SwapTemplateFollowup with required template_id) + 5 new vitest cases on the panel (badge + data-testids + per-card loading + per-card error + multi-target independent fetches + Run callback) + AC-16 vitest case on the create-study modal + 1 new E2E Playwright spec (swap_template Run → modal opens with swap target → submit → assert new study.template_id = swap_target.id). 1316 → 1331 backend unit (+15); +3 integration; +2 contract; +20 vitest; +1 Playwright. Cross-model review: spec converged at GPT-5.5 cycle 3 (multiple findings accepted); plan converged at cycle 2 (7 accepted in cycle 1; 4 rejected with cited counter-evidence in cycle 2). **Alembic head unchanged at `0019_digests_suggested_followups_jsonb`.** 13 stories across 5 epics extending the Tier-A discriminated union with a 4th `swap_template` followup kind. **Domain:** new `SwapTemplateFollowup` variant (`{kind, rationale, template_id (36-char), search_space}`) + `FOLLOWUP_KIND_VALUES` widens to 4-tuple; `_truncate` promoted to public `truncate_validation_error` per spec D-33; new pure-domain module `backend/app/domain/study/template_swap.py` with `remap_search_space_for_swap_target` + `RemapResult` (trusted-intersection / disjoint-fill / dropped-parent / ignored-LLM name sets) — raises `InvalidSearchSpaceError` on empty swap target / empty trusted intersection / cardinality blowup; calls `build_starter_search_space` only when disjoint fill is non-empty per cycle-1 F2. **Worker:** `DIGEST_RESPONSE_SCHEMA` widens `kind` enum to 4 values + adds uniform `template_id: string` field on every item with empty-string sentinel pre-clean per spec D-29 (OpenAI strict mode rejects oneOf/if/then on item subschemas); catalogue fetch via `list_query_templates(engine_type=parent_engine)` excluding the parent template; new `_apply_swap_template_remap` async helper runs AFTER `[:5]` truncation per AC-15 and downgrades each swap_template item that fails the FR-8 cascade with `reason ∈ {not_found, same_as_parent, engine_type_mismatch, remap_invalid_search_space}` via `digest_followup_validation_downgraded` WARN; happy path emits `digest_followup_swap_template_remapped` INFO with the 4 `RemapResult` name lists; `digest_complete` log adds `followups_swap_template_count`. **LLM prompts:** system prompt gains a `swap_template` subsection covering selection criteria (parameter-importance skew / dead-weight params / cross-template lever sets), the contract (`template_id` must match `` + skip same-as-parent + `search_space` covers intersection only), and the skip-rule for empty catalogues per D-34; user.jinja renders new `` + `` blocks when `render_digest_user_prompt()` receives the new kwargs. **Frontend:** `enums.ts FOLLOWUP_KIND_VALUES` widens to match backend; `types.ts FollowupItem` union widens; `SuggestedFollowupsPanel` refactored from inline if-chain to exhaustive `Record` lookups per D-28 (SHOWS_SEARCH_SPACE_EXPANDER / SHOWS_RUN_BUTTON / SHOWS_DECLARED_PARAMS_DIFF); new same-file `SwapTemplateCard` child component calls `useTemplate(followup.template_id)` at its own top per cycle-1 F2/F3 (Rules of Hooks + N-target scale); side-by-side parent-vs-target declared-params diff with shared-key highlight; new `ACTIONABLE_FOLLOWUP_KINDS` Record gate on the proposal-detail page so swap_template-only digests still trigger parent-study fetch (cycle-1 F1 regression guard); `resolveTemplateIdForPrefill` exhaustive switch helper seeds `template_id = followup.template_id` for swap_template per AC-12; lazy `useTemplate(parentStudy.data?.template_id)` passes parent template to the panel; create-study-modal autofill effect short-circuits when `initialValues.search_space_text` is non-empty per FR-14 + AC-16; 2 new glossary keys (`proposal.followup_kind_swap_template` + `proposal.followup_declared_params_diff`). **No new migration** — Tier A's `0019_digests_suggested_followups_jsonb` + lineage columns apply unchanged. **Tests:** 7 new domain unit tests (`test_template_swap.py`) + 6 new followup union tests + 1 swap_template parse-layer downgrade test + 7 new worker validation tests (`test_digest_followup_validation.py` — 4 reason codes + happy-path INFO + non-swap passthrough no-DB-lookup + per-call cache test) + 4 new prompt-render tests (the two new jinja blocks render-when-provided / omit-when-absent) + 2 new integration tests (swap_template happy path + AC-13 single-template install yields no swap items) + 1 new integration test for AC-12 cross-template lineage on `POST /api/v1/studies` + 2 new contract tests (DigestResponse round-trip + OpenAPI $defs include SwapTemplateFollowup with required template_id) + 5 new vitest cases on the panel (badge + data-testids + per-card loading + per-card error + multi-target independent fetches + Run callback) + AC-16 vitest case on the create-study modal + 1 new E2E Playwright spec (swap_template Run → modal opens with swap target → submit → assert new study.template_id = swap_target.id). 1316 → 1331 backend unit (+15); +3 integration; +2 contract; +20 vitest; +1 Playwright. Cross-model review: spec converged at GPT-5.5 cycle 3 (multiple findings accepted); plan converged at cycle 2 (7 accepted in cycle 1; 4 rejected with cited counter-evidence in cycle 2). **Alembic head unchanged at `0019_digests_suggested_followups_jsonb`.** -- **2026-05-24 — `feat_home_demo_reseed_endpoint` merged into `main` as PR #228 squash `ad6ff826`.** Dev-only `POST /api/v1/_test/demo/reseed` endpoint + dashboard reset affordance. New service module [`backend/app/services/demo_seeding.py`](backend/app/services/demo_seeding.py) orchestrates wipe + reseed (TRUNCATE 10 demo tables → DELETE 3 ES + 1 OS indices → loop 4 scenarios → rename 4 studies); imports `SCENARIOS` + `TRUNCATE_TABLES` + `DEMO_ES_INDICES` + `DEMO_OS_INDICES` from `scripts/seed_meaningful_demos.py` (no CLI refactor per locked decision D2). Route handler at [`backend/app/api/v1/_test.py`](backend/app/api/v1/_test.py) acquires `pg_try_advisory_lock` on a dedicated pinned `AsyncConnection` (FR-3 / AC-16), constructs dual `httpx.AsyncClient` instances with the new `Settings.demo_reseed_per_call_http_timeout_s` per-call ceiling (default 120s, range 30..600), runs cleanup-on-failure via a FRESH DB connection (cycle-1 finding B1) before returning 503 `SEED_FAILED`. Frontend disclosure at [`ui/src/components/dashboard/reset-demo-state-button.tsx`](ui/src/components/dashboard/reset-demo-state-button.tsx) — `AlertDialog` confirmation + 180s client-side abort + 4-key TanStack invalidation; wired into [`ui/src/components/dashboard/start-here-checklist.tsx`](ui/src/components/dashboard/start-here-checklist.tsx) as a `
` disclosure rendered only when all three first-run signals are false. Two new error codes (`SEED_IN_PROGRESS` 409, `SEED_FAILED` 503) added to [`docs/01_architecture/api-conventions.md`](docs/01_architecture/api-conventions.md). Runbook at [`docs/03_runbooks/demo-reseed-debugging.md`](docs/03_runbooks/demo-reseed-debugging.md) covers `pg_locks` inspection + the `ReadTimeout`-edge `docker compose restart api` recovery per spec §10 Threat 4. Cross-model review: spec 14 GPT-5.5 cycles to convergence (driven mostly by the locking + dual-client + no-outer-timeout architecture); plan 14 cycles; 2 Gemini Medium findings accepted (`limit=50`→`200` + `KeyError`→`DemoSeedingError` with descriptive message); GPT-5.5 final review 5 findings — 1 High accepted (OpenSearch in-container port `:9201` → `:9200` correction), 2 Medium accepted (frontend status=0 SERVICE_UNAVAILABLE branching + uvicorn localhost-resolves-to-IPv4 smoke probe), 1 Medium rejected as stale line-number, 1 Low deferred. Tests: backend unit + contract 1560 pass (+45 vs prior baseline of 1515); 10 integration tests at [`backend/tests/integration/test_demo_seeding.py`](backend/tests/integration/test_demo_seeding.py) + [`test_demo_seeding_timeout.py`](backend/tests/integration/test_demo_seeding_timeout.py) covering AC-1..AC-5 + AC-12..AC-16 (in-process uvicorn fixture; AC-4 isolated to its own function-scoped fixture per spec ReadTimeout-residual concern); 21 dashboard vitest cases; 1 Playwright spec at [`ui/tests/e2e/dashboard-reseed.spec.ts`](ui/tests/e2e/dashboard-reseed.spec.ts). Tangential capture: [`bug_vitest_jsdom_localstorage_failures/idea.md`](docs/02_product/planned_features/bug_vitest_jsdom_localstorage_failures/idea.md) — 31 pre-existing vitest failures in 4 files all touching `window.localStorage`, confirmed unrelated to this PR. **Alembic head unchanged at `0019_digests_suggested_followups_jsonb`** — no schema change. +- **2026-05-24 — `feat_home_demo_reseed_endpoint` merged into `main` as PR #228 squash `ad6ff826`.** Dev-only `POST /api/v1/_test/demo/reseed` endpoint + dashboard reset affordance. New service module [`backend/app/services/demo_seeding.py`](backend/app/services/demo_seeding.py) orchestrates wipe + reseed (TRUNCATE 10 demo tables → DELETE 3 ES + 1 OS indices → loop 4 scenarios → rename 4 studies); imports `SCENARIOS` + `TRUNCATE_TABLES` + `DEMO_ES_INDICES` + `DEMO_OS_INDICES` from `scripts/seed_meaningful_demos.py` (no CLI refactor per locked decision D2). Route handler at [`backend/app/api/v1/_test.py`](backend/app/api/v1/_test.py) acquires `pg_try_advisory_lock` on a dedicated pinned `AsyncConnection` (FR-3 / AC-16), constructs dual `httpx.AsyncClient` instances with the new `Settings.demo_reseed_per_call_http_timeout_s` per-call ceiling (default 120s, range 30..600), runs cleanup-on-failure via a FRESH DB connection (cycle-1 finding B1) before returning 503 `SEED_FAILED`. Frontend disclosure at [`ui/src/components/dashboard/reset-demo-state-button.tsx`](ui/src/components/dashboard/reset-demo-state-button.tsx) — `AlertDialog` confirmation + 180s client-side abort + 4-key TanStack invalidation; wired into [`ui/src/components/dashboard/start-here-checklist.tsx`](ui/src/components/dashboard/start-here-checklist.tsx) as a `
` disclosure rendered only when all three first-run signals are false. Two new error codes (`SEED_IN_PROGRESS` 409, `SEED_FAILED` 503) added to [`docs/01_architecture/api-conventions.md`](docs/01_architecture/api-conventions.md). Runbook at [`docs/03_runbooks/demo-reseed-debugging.md`](docs/03_runbooks/demo-reseed-debugging.md) covers `pg_locks` inspection + the `ReadTimeout`-edge `docker compose restart api` recovery per spec §10 Threat 4. Cross-model review: spec 14 GPT-5.5 cycles to convergence (driven mostly by the locking + dual-client + no-outer-timeout architecture); plan 14 cycles; 2 Gemini Medium findings accepted (`limit=50`→`200` + `KeyError`→`DemoSeedingError` with descriptive message); GPT-5.5 final review 5 findings — 1 High accepted (OpenSearch in-container port `:9201` → `:9200` correction), 2 Medium accepted (frontend status=0 SERVICE_UNAVAILABLE branching + uvicorn localhost-resolves-to-IPv4 smoke probe), 1 Medium rejected as stale line-number, 1 Low deferred. Tests: backend unit + contract 1560 pass (+45 vs prior baseline of 1515); 10 integration tests at [`backend/tests/integration/test_demo_seeding.py`](backend/tests/integration/test_demo_seeding.py) + [`test_demo_seeding_timeout.py`](backend/tests/integration/test_demo_seeding_timeout.py) covering AC-1..AC-5 + AC-12..AC-16 (in-process uvicorn fixture; AC-4 isolated to its own function-scoped fixture per spec ReadTimeout-residual concern); 21 dashboard vitest cases; 1 Playwright spec at [`ui/tests/e2e/dashboard-reseed.spec.ts`](ui/tests/e2e/dashboard-reseed.spec.ts). Tangential capture: [`bug_vitest_jsdom_localstorage_failures/idea.md`](docs/00_overview/planned_features/bug_vitest_jsdom_localstorage_failures/idea.md) — 31 pre-existing vitest failures in 4 files all touching `window.localStorage`, confirmed unrelated to this PR. **Alembic head unchanged at `0019_digests_suggested_followups_jsonb`** — no schema change. -- **2026-05-24 — `feat_digest_executable_followups` Phase 1 (Tier A) merged into `main` as PR #225 squash `83c526f2`.** 34th MVP1-era artifact. Turns the digest worker's `suggested_followups` field from `array of string` into a Pydantic discriminated union of `narrow | widen | text` kinds; structured kinds carry a validated `search_space` that pre-fills the create-study modal in one click. **Backend** (Epics 1–4, 12 stories): new domain module [`backend/app/domain/study/followups.py`](backend/app/domain/study/followups.py) with `FollowupItem` discriminated union (Pydantic v2 `TypeAdapter` + 3 kind variants) + defensive `parse_followup_list` (graceful downgrade on validation failure — invalid `narrow`/`widen` items become `text` with prefixed rationale) + `serialize_followup_list` JSONB serializer (closes the Pydantic→JSONB gap); digest worker structured-output schema swap with capability-degraded fallback preserved (D-27 — emits `[]` when LLM structured-output fails); two migrations (0018 + 0019) — 0018 adds `studies.parent_proposal_id VARCHAR(36) NULL` FK to `proposals(id)` + `parent_proposal_followup_index INT NULL` + CHECK constraint (pair-set-together-and-index-non-negative) + partial B-tree index + **BEFORE DELETE trigger** for the lineage pair invariant (NOT `ON DELETE SET NULL` because the trigger needs to preserve atomicity), 0019 changes `digests.suggested_followups` from `ARRAY(Text)` to `JSONB` via PL/pgSQL helper functions (subqueries rejected by Postgres in `ALTER COLUMN TYPE ... USING` — empirically verified); 3 new error codes (`PROPOSAL_NOT_FOUND` 404, `DIGEST_NOT_FOUND` 404 retryable, `FOLLOWUP_INDEX_OUT_OF_RANGE` 422); `POST /api/v1/studies` accepts new optional `parent` body for prefill lineage; `DigestResponse` + `_DigestEmbed` declare `list[FollowupItem]` with `parse_followup_list` wrapping at the serializer boundary. **Frontend** (Epic 5, 3 stories): `SuggestedFollowupsPanel` rewritten as kind-discriminated cards with lazy `useStudy(parent_study_id)` enabled whenever the proposal has ≥1 actionable followup (Gemini-accepted F2); `CreateStudyModal` prefill flow with `form.reset()` on prop change; 5 new glossary keys. The existing half-built `?hypothesis=` query-string path is retired in the same PR per the Legacy Behavior Parity table (6 rows: 4 preserved + 2 intentionally-dropped with FR-12 citations). **E2E** (Epic 6, 1 story): `ui/tests/e2e/followup_run.spec.ts` Playwright happy-path against real backend. **OpenAI strict-mode workaround:** `search_space_json` is sent as a `string` (not nested object) per OpenAI's strict-mode constraint; the worker parses it to `SearchSpace` after the LLM call. Cross-model review: spec 3 GPT-5.5 cycles (17 accepted + 1 rejected with cited counter-evidence at D-17 — persisted `generated_by` is lineage data per CLAUDE.md Absolute Rule #8); plan 1 cycle (3 accepted + 2 rejected — F4 MVP4 forward-looking convention, F5 D-17 re-raise); Gemini Code Assist 2 Medium accepted + applied (head-and-tail truncate; parent-name 200-char cap); final GPT-5.5 PR-diff review 3 findings (1 accepted as documented drift — `search_space_json` workaround captured in plan §9; 2 rejected — repo `**fields` passthrough + types.ts already regenerated). Tests: backend unit 1282 → 1316 (+34); +5 integration, +6 contract, +19 vitest, +1 Playwright E2E. Tangential capture: [`bug_markdown_doc_localstorage_undefined_jsdom/idea.md`](docs/02_product/planned_features/bug_markdown_doc_localstorage_undefined_jsdom/idea.md) — pre-existing vitest failure in unrelated guide-viewer tests, captured per CLAUDE.md tangential-discoveries rule. **Phase 2 (Tier B `swap_template`) + Phase 3 (Tier C `edit_template`) remain deferred** via `phase2_idea.md` + `phase3_idea.md` inside the folder — feature folder stays in `planned_features/` until those ship per CLAUDE.md PARTIAL convention. **Alembic head moves to `0019_digests_suggested_followups_jsonb`.** +- **2026-05-24 — `feat_digest_executable_followups` Phase 1 (Tier A) merged into `main` as PR #225 squash `83c526f2`.** 34th MVP1-era artifact. Turns the digest worker's `suggested_followups` field from `array of string` into a Pydantic discriminated union of `narrow | widen | text` kinds; structured kinds carry a validated `search_space` that pre-fills the create-study modal in one click. **Backend** (Epics 1–4, 12 stories): new domain module [`backend/app/domain/study/followups.py`](backend/app/domain/study/followups.py) with `FollowupItem` discriminated union (Pydantic v2 `TypeAdapter` + 3 kind variants) + defensive `parse_followup_list` (graceful downgrade on validation failure — invalid `narrow`/`widen` items become `text` with prefixed rationale) + `serialize_followup_list` JSONB serializer (closes the Pydantic→JSONB gap); digest worker structured-output schema swap with capability-degraded fallback preserved (D-27 — emits `[]` when LLM structured-output fails); two migrations (0018 + 0019) — 0018 adds `studies.parent_proposal_id VARCHAR(36) NULL` FK to `proposals(id)` + `parent_proposal_followup_index INT NULL` + CHECK constraint (pair-set-together-and-index-non-negative) + partial B-tree index + **BEFORE DELETE trigger** for the lineage pair invariant (NOT `ON DELETE SET NULL` because the trigger needs to preserve atomicity), 0019 changes `digests.suggested_followups` from `ARRAY(Text)` to `JSONB` via PL/pgSQL helper functions (subqueries rejected by Postgres in `ALTER COLUMN TYPE ... USING` — empirically verified); 3 new error codes (`PROPOSAL_NOT_FOUND` 404, `DIGEST_NOT_FOUND` 404 retryable, `FOLLOWUP_INDEX_OUT_OF_RANGE` 422); `POST /api/v1/studies` accepts new optional `parent` body for prefill lineage; `DigestResponse` + `_DigestEmbed` declare `list[FollowupItem]` with `parse_followup_list` wrapping at the serializer boundary. **Frontend** (Epic 5, 3 stories): `SuggestedFollowupsPanel` rewritten as kind-discriminated cards with lazy `useStudy(parent_study_id)` enabled whenever the proposal has ≥1 actionable followup (Gemini-accepted F2); `CreateStudyModal` prefill flow with `form.reset()` on prop change; 5 new glossary keys. The existing half-built `?hypothesis=` query-string path is retired in the same PR per the Legacy Behavior Parity table (6 rows: 4 preserved + 2 intentionally-dropped with FR-12 citations). **E2E** (Epic 6, 1 story): `ui/tests/e2e/followup_run.spec.ts` Playwright happy-path against real backend. **OpenAI strict-mode workaround:** `search_space_json` is sent as a `string` (not nested object) per OpenAI's strict-mode constraint; the worker parses it to `SearchSpace` after the LLM call. Cross-model review: spec 3 GPT-5.5 cycles (17 accepted + 1 rejected with cited counter-evidence at D-17 — persisted `generated_by` is lineage data per CLAUDE.md Absolute Rule #8); plan 1 cycle (3 accepted + 2 rejected — F4 MVP4 forward-looking convention, F5 D-17 re-raise); Gemini Code Assist 2 Medium accepted + applied (head-and-tail truncate; parent-name 200-char cap); final GPT-5.5 PR-diff review 3 findings (1 accepted as documented drift — `search_space_json` workaround captured in plan §9; 2 rejected — repo `**fields` passthrough + types.ts already regenerated). Tests: backend unit 1282 → 1316 (+34); +5 integration, +6 contract, +19 vitest, +1 Playwright E2E. Tangential capture: [`bug_markdown_doc_localstorage_undefined_jsdom/idea.md`](docs/00_overview/planned_features/bug_markdown_doc_localstorage_undefined_jsdom/idea.md) — pre-existing vitest failure in unrelated guide-viewer tests, captured per CLAUDE.md tangential-discoveries rule. **Phase 2 (Tier B `swap_template`) + Phase 3 (Tier C `edit_template`) remain deferred** via `phase2_idea.md` + `phase3_idea.md` inside the folder — feature folder stays in `planned_features/` until those ship per CLAUDE.md PARTIAL convention. **Alembic head moves to `0019_digests_suggested_followups_jsonb`.** - **2026-05-24 — `feat_auto_followup_studies` merged into `main` as PR #223 squash `20cf183a`.** Operator-controlled cross-study compounding: a study's `config.auto_followup_depth` opts into a chain of up to 5 auto-enqueued follow-up studies, each narrowing the search space around the prior winner via the existing `propose_search_space` heuristic. Halts on no-lift / exhausted-budget / failed-parent / depth-0. Every chain member still produces a manual-review proposal — no PR opens automatically. **No schema migration** — re-uses the existing `studies.parent_study_id` self-FK (added in `feat_study_lifecycle` Phase 1 migration 0003 as the "MVP2 fork surface"). Backend (Stories 1.1/1.2/1.3/2.1/2.2/2.3): new `evaluate_chain_gate` pure domain function (FR-2a lift-over-first-decile gate, sorts by `optuna_trial_number` since Trial has no `created_at`); new `enqueue_followup_study` Arq job with two-layer idempotency (Arq `_job_id` + `list_children_of_study` re-check); digest-worker trigger fires on `auto_followup_depth is not None` per D-12 so depth-0 leaves emit their own `auto_followup_depth_exhausted`; `cancel_study_with_chain_cascade` service tolerates terminal parents and recurses through completed intermediates (cycle-3 C3-1 redesign); new `POST /studies/{id}/cancel?cascade=` query param + `GET /studies/{id}/children` sub-resource endpoint; `INVALID_CASCADE_PARAM` (400) + `AUTO_FOLLOWUP_DEPTH_OUT_OF_RANGE` (422) new error codes; custom error-code prefix-parser added to `validation_exception_handler` (regex + allowlist constrained per cycle-2 C2-1). FR-9 telemetry: 8 chain-state events + 4 auxiliary `digest_followup_*` / `auto_followup_cancel_terminal_parent` events. Frontend (Stories 3.1/3.2/3.3): new `AutoFollowupChainPanel` component on `/studies/[id]` with conditional render (parent link + remaining-depth + direct-children table); wizard depth selector in Step 5 of create-study modal (`AUTO_FOLLOWUP_DEPTH_WIZARD_VALUES = [0,1,2,3,4,5]` with `0` as the off-sentinel); cancel-modal cascade radio (defaults to cascade=true per D-6) shown when there's an in-flight child OR `status='running'` AND `auto_followup_depth > 0`; 4 new glossary keys; `useStudyChildren` + `CancelStudyVars` API additions. Node engines bumped to `>=22` (CI + package.json + local default; v18 uninstalled). Tests: backend `make test-unit` 1220 pass (+23 new across `test_auto_followup.py` / `test_validation_error_handler.py` / `test_study_state.py` cascade extension / `test_digest_followup_trigger.py` / `test_studies_router_chain_endpoints.py`); UI vitest 744 pass (+13 new across `auto-followup-chain-panel.test.tsx` + `study-action-bar-cascade.test.tsx`); 8 integration tests at `test_auto_followup.py` written (CI-gated against service containers). Cross-model review: spec 3 GPT-5.5 cycles (17 findings, all accepted with 13 decision-log entries D-1 through D-13); plan 3 GPT-5.5 cycles (25 findings across cycles, all accepted with the cycle-3 C3-1 cascade-through-completed-intermediates redesign locking the realistic chain lifecycle); Epic 1+2 phase-gate review 1 cycle (8 findings — 2 code fixes (F3 cascade=False delegation + F5 budget-gate unknown-model fallback), 2 doc fixes (F1 + F4), 1 deferred future-work idea `chore_auto_followup_completed_parent_stop_chain_race/idea.md` captured for the new race where cascade-cancelling a `completed` parent can lose to a pending followup-enqueue Arq job). New runbook at [`docs/03_runbooks/auto-followup-debugging.md`](docs/03_runbooks/auto-followup-debugging.md). **Alembic head unchanged at `0017_proposals_last_polled_at`** — no migration needed (existing self-FK re-used). -- **2026-05-23 — `chore_dashboard_pr_extraction_from_idea` merged into `main` as PR #221 squash `8a6452d5`.** Extends `_extract_pr_number` in [`scripts/build_mvp1_dashboard.py`](scripts/build_mvp1_dashboard.py) with two new cascade priorities (3.5 + 3.6) that mine `idea.md` content for own-PR assertions when pipeline_status / plan / spec are empty — the typical shape for ~50 early MVP1 features that shipped before /pipeline ceremony solidified. **Story 1.1:** 5 module-level `re.compile`'d patterns (3 line-anchored strict patterns A/B/C `**Status:** **Shipped** as PR #N` / `**Status:** **Implemented — PR #N` / line-start `**shipped YYYY-MM-DD as PR #N**` with markdown-link alternation across all three for symmetry per Gemini cycle 1; `**PR:**` frontmatter fallback; `_METADATA_KEY_RE`) + new `_extract_metadata_block(idea) -> str` helper bounding the `**PR:**` search to a contiguous metadata cluster (stops at first `## ` heading or first non-blank non-metadata line; 30-line cap for headingless ideas; `nonblank_seen` flag rejects later H1 as title per GPT-5.5 final review). Each pattern's `\b` boundary sits immediately after the digit capture (per GPT-5.5 spec-cycle-2 fix). **Story 1.2:** threads `idea.md` content through `_load_implemented` + `_load_planned` via new 4th arg; adds `_extract_idea_problem(idea)` one-liner fallback for legacy idea-only folders (Gemini cycle 1 finding #3 — material UX improvement on many rows that previously showed empty); 28-case test file at [`backend/tests/unit/scripts/test_dashboard_pr_extraction.py`](backend/tests/unit/scripts/test_dashboard_pr_extraction.py) covering all 17 spec ACs + mutual-exclusion + helper unit cases + regex-constant locks + the new H1-after-metadata regression; documents the `**PR:**` frontmatter convention in [`architecture.md`](architecture.md)'s new "Dashboard regen" subsection. **Empirical verification:** Status column correctly populated for the strict-pattern targets (`chore_create_study_modal_e2e_stability` → PR #161, `chore_precommit_node_path_resolution` → PR #171); ZERO existing PR-linked rows changed PR number. Below predicted 5–8 forward gain because only ~3 of ~50 idea-only folders carry the canonical strict shapes; the `**PR:**` frontmatter convention is the opt-in escape hatch for the rest. **Cross-model review:** spec converged at GPT-5.5 cycle 3 (12 findings: 2 H / 7 M / 3 L across 3 cycles, all 11 actionable accepted — Pattern A `\b` placement, metadata-block bounding algorithm with 30-line cap, structural empirical-verification script); plan converged at GPT-5.5 cycle 1 (3 findings: 0 H / 1 M / 2 L, all accepted — AC-12 ownership clarification, metadata-block title-once flag, pytest `-q`); Gemini Code Assist 3 Medium findings all accepted in `5a90d989` (markdown-link symmetry across Patterns A/B/C + `_load_implemented` one-liner fallback); final GPT-5.5 review 2 Medium findings — 1 accepted + applied in `d03e3d5f` (metadata-block `nonblank_seen` flag), 1 deferred ([`chore_dashboard_regen_quoted_pr_false_positive/idea.md`](docs/02_product/planned_features/chore_dashboard_regen_quoted_pr_false_positive/idea.md) captures the pre-existing priority-3 fuzzy-regex weakness — manifested as a cosmetic anomaly on this chore's own planned-row dashboard rendering until merge resolved it). Tests: 1191 backend unit (1163 baseline + 28 new) all green; CI green 5/5 jobs across all 3 pushes. **Alembic head unchanged at `0017_proposals_last_polled_at`** — chore is a build-script refactor with no schema changes. +- **2026-05-23 — `chore_dashboard_pr_extraction_from_idea` merged into `main` as PR #221 squash `8a6452d5`.** Extends `_extract_pr_number` in [`scripts/build_mvp1_dashboard.py`](scripts/build_mvp1_dashboard.py) with two new cascade priorities (3.5 + 3.6) that mine `idea.md` content for own-PR assertions when pipeline_status / plan / spec are empty — the typical shape for ~50 early MVP1 features that shipped before /pipeline ceremony solidified. **Story 1.1:** 5 module-level `re.compile`'d patterns (3 line-anchored strict patterns A/B/C `**Status:** **Shipped** as PR #N` / `**Status:** **Implemented — PR #N` / line-start `**shipped YYYY-MM-DD as PR #N**` with markdown-link alternation across all three for symmetry per Gemini cycle 1; `**PR:**` frontmatter fallback; `_METADATA_KEY_RE`) + new `_extract_metadata_block(idea) -> str` helper bounding the `**PR:**` search to a contiguous metadata cluster (stops at first `## ` heading or first non-blank non-metadata line; 30-line cap for headingless ideas; `nonblank_seen` flag rejects later H1 as title per GPT-5.5 final review). Each pattern's `\b` boundary sits immediately after the digit capture (per GPT-5.5 spec-cycle-2 fix). **Story 1.2:** threads `idea.md` content through `_load_implemented` + `_load_planned` via new 4th arg; adds `_extract_idea_problem(idea)` one-liner fallback for legacy idea-only folders (Gemini cycle 1 finding #3 — material UX improvement on many rows that previously showed empty); 28-case test file at [`backend/tests/unit/scripts/test_dashboard_pr_extraction.py`](backend/tests/unit/scripts/test_dashboard_pr_extraction.py) covering all 17 spec ACs + mutual-exclusion + helper unit cases + regex-constant locks + the new H1-after-metadata regression; documents the `**PR:**` frontmatter convention in [`architecture.md`](architecture.md)'s new "Dashboard regen" subsection. **Empirical verification:** Status column correctly populated for the strict-pattern targets (`chore_create_study_modal_e2e_stability` → PR #161, `chore_precommit_node_path_resolution` → PR #171); ZERO existing PR-linked rows changed PR number. Below predicted 5–8 forward gain because only ~3 of ~50 idea-only folders carry the canonical strict shapes; the `**PR:**` frontmatter convention is the opt-in escape hatch for the rest. **Cross-model review:** spec converged at GPT-5.5 cycle 3 (12 findings: 2 H / 7 M / 3 L across 3 cycles, all 11 actionable accepted — Pattern A `\b` placement, metadata-block bounding algorithm with 30-line cap, structural empirical-verification script); plan converged at GPT-5.5 cycle 1 (3 findings: 0 H / 1 M / 2 L, all accepted — AC-12 ownership clarification, metadata-block title-once flag, pytest `-q`); Gemini Code Assist 3 Medium findings all accepted in `5a90d989` (markdown-link symmetry across Patterns A/B/C + `_load_implemented` one-liner fallback); final GPT-5.5 review 2 Medium findings — 1 accepted + applied in `d03e3d5f` (metadata-block `nonblank_seen` flag), 1 deferred ([`chore_dashboard_regen_quoted_pr_false_positive/idea.md`](docs/00_overview/planned_features/chore_dashboard_regen_quoted_pr_false_positive/idea.md) captures the pre-existing priority-3 fuzzy-regex weakness — manifested as a cosmetic anomaly on this chore's own planned-row dashboard rendering until merge resolved it). Tests: 1191 backend unit (1163 baseline + 28 new) all green; CI green 5/5 jobs across all 3 pushes. **Alembic head unchanged at `0017_proposals_last_polled_at`** — chore is a build-script refactor with no schema changes. -- **2026-05-23 — `chore_migration_test_head_brittleness` merged into `main` as PR #219 squash `63cb7c41`.** Test-only refactor of [`backend/tests/integration/test_migrations.py`](backend/tests/integration/test_migrations.py): replaces two hardcoded `"0017"` head assertions at L132 + L157 with a single `_current_head() -> str` helper that resolves the head dynamically via `_alembic("heads").stdout` at test-body execution time (reusing the existing `_alembic()` helper at `:79-87` per Gemini's accepted finding — the spec/plan originally proposed a direct `subprocess.check_output(...)` call). Helper asserts the chain has exactly one head and raises `AssertionError` naming `alembic merge` as the canonical fix if a future branch lands. Per-migration changelog comment chain above each assertion (was growing by ~2 lines per migration shipped — a recurring sympathy-edit tax) collapses to a single anchor comment pointing at the helper's docstring. AC-4 manually verified end-to-end against a local standalone Postgres on `:5433`: created stub migration `0018_stub_for_ac4_verification.py` with `revision="0018"` / `down_revision="0017"` / `upgrade() → op.execute("SELECT 1")`, ran integration tests → 3/3 PASS at head=0018 **with zero edits to `test_migrations.py`**, downgraded + deleted stub, re-ran → 4/4 PASS at head=0017 (full transcript in PR body). Cross-model review: spec converged at GPT-5.5 cycle 1 (5 findings: 0 High / 1 Medium / 4 Low — all accepted and applied — including the AC-1 reword to describe parsing behavior instead of hardcoding `"0017"`, the comment-guidance reconciliation across §3/§4/FR-2/decision log, AC-5/AC-6 reclassification as inspection-level vs CI-exercised in DoD, FR-3 narrowing of the host-without-`uv` claim, and AC-1b + helper assertion for the single-head invariant); plan converged at cycle 1 (1 finding: 0 High / 1 Medium / 0 Low — DoD grep arithmetic fix accepted); Gemini Code Assist 2 Medium findings on the open PR, both accepted in `77495d0a` (`_alembic` helper reuse in code + matching plan update); final GPT-5.5 0 High / 1 Medium / 1 Low — Medium rejected with cited counter-evidence (the tangential-discovery capture pattern is explicitly authorized by [CLAUDE.md "Tangential discoveries"](CLAUDE.md) + `impl-execute` Step 2.5: separate same-branch commit for in-flight noticed issues is canonical, NOT cross-folder scope mixing), Low deferred to this finalization PR (pipeline_status.md `Not started → Complete` transition). Tangential capture committed in `684cc0dc`: [`chore_e2e_seed_acme_idea_obsolete/idea.md`](docs/02_product/planned_features/chore_e2e_seed_acme_idea_obsolete/idea.md) — discovered during `/idea-preflight` that the existing `chore_e2e_seed_acme_helper_dead/idea.md` is now obsolete (its premise of "0 Playwright callers" is contradicted by [`ui/tests/e2e/guides/06_create_and_monitor_study.spec.ts:28`](ui/tests/e2e/guides/06_create_and_monitor_study.spec.ts#L28) added by `2cbcb93b` on 2026-05-22; Path B effectively shipped via the guide-06 spec; coverage-audit.md also stale). CI green on both pushes (5/5 jobs incl. smoke). With 17 migrations shipped and ~12 more expected before MVP1 wraps, this removes ~50 LOC of recurring sympathy-edit tax. **Alembic head unchanged at `0017_proposals_last_polled_at`** — chore is purely a test-file refactor. +- **2026-05-23 — `chore_migration_test_head_brittleness` merged into `main` as PR #219 squash `63cb7c41`.** Test-only refactor of [`backend/tests/integration/test_migrations.py`](backend/tests/integration/test_migrations.py): replaces two hardcoded `"0017"` head assertions at L132 + L157 with a single `_current_head() -> str` helper that resolves the head dynamically via `_alembic("heads").stdout` at test-body execution time (reusing the existing `_alembic()` helper at `:79-87` per Gemini's accepted finding — the spec/plan originally proposed a direct `subprocess.check_output(...)` call). Helper asserts the chain has exactly one head and raises `AssertionError` naming `alembic merge` as the canonical fix if a future branch lands. Per-migration changelog comment chain above each assertion (was growing by ~2 lines per migration shipped — a recurring sympathy-edit tax) collapses to a single anchor comment pointing at the helper's docstring. AC-4 manually verified end-to-end against a local standalone Postgres on `:5433`: created stub migration `0018_stub_for_ac4_verification.py` with `revision="0018"` / `down_revision="0017"` / `upgrade() → op.execute("SELECT 1")`, ran integration tests → 3/3 PASS at head=0018 **with zero edits to `test_migrations.py`**, downgraded + deleted stub, re-ran → 4/4 PASS at head=0017 (full transcript in PR body). Cross-model review: spec converged at GPT-5.5 cycle 1 (5 findings: 0 High / 1 Medium / 4 Low — all accepted and applied — including the AC-1 reword to describe parsing behavior instead of hardcoding `"0017"`, the comment-guidance reconciliation across §3/§4/FR-2/decision log, AC-5/AC-6 reclassification as inspection-level vs CI-exercised in DoD, FR-3 narrowing of the host-without-`uv` claim, and AC-1b + helper assertion for the single-head invariant); plan converged at cycle 1 (1 finding: 0 High / 1 Medium / 0 Low — DoD grep arithmetic fix accepted); Gemini Code Assist 2 Medium findings on the open PR, both accepted in `77495d0a` (`_alembic` helper reuse in code + matching plan update); final GPT-5.5 0 High / 1 Medium / 1 Low — Medium rejected with cited counter-evidence (the tangential-discovery capture pattern is explicitly authorized by [CLAUDE.md "Tangential discoveries"](CLAUDE.md) + `impl-execute` Step 2.5: separate same-branch commit for in-flight noticed issues is canonical, NOT cross-folder scope mixing), Low deferred to this finalization PR (pipeline_status.md `Not started → Complete` transition). Tangential capture committed in `684cc0dc`: [`chore_e2e_seed_acme_idea_obsolete/idea.md`](docs/00_overview/planned_features/chore_e2e_seed_acme_idea_obsolete/idea.md) — discovered during `/idea-preflight` that the existing `chore_e2e_seed_acme_helper_dead/idea.md` is now obsolete (its premise of "0 Playwright callers" is contradicted by [`ui/tests/e2e/guides/06_create_and_monitor_study.spec.ts:28`](ui/tests/e2e/guides/06_create_and_monitor_study.spec.ts#L28) added by `2cbcb93b` on 2026-05-22; Path B effectively shipped via the guide-06 spec; coverage-audit.md also stale). CI green on both pushes (5/5 jobs incl. smoke). With 17 migrations shipped and ~12 more expected before MVP1 wraps, this removes ~50 LOC of recurring sympathy-edit tax. **Alembic head unchanged at `0017_proposals_last_polled_at`** — chore is purely a test-file refactor. - **2026-05-23 — `chore_study_default_stop_conditions` merged into `main` as PR #215 squash `370c87d9`.** First MVP1.0-cleanup chore in the operator's stated "finish MVP1.0 before MVP1.5" sweep. Frontend-only: `defaultValues.max_trials = 200` in the create-study modal; new Stop-condition 4-button preset selector (Focused 50 / Standard 200 / Deep 1000 / Custom) above the numeric inputs with `activePreset` derived purely from form values via `useMemo` (no `useState`+watcher pattern); refreshed `study.max_trials` + `study.time_budget_min` glossary copy with dimensionality-keyed framing + new `study.preset` glossary entry; chat orchestrator system prompt updated to recommend `max_trials=200` by default with scaling guidance for 1–2 vs 3–5 vs 6+ params. New 12-case vitest suite at [`ui/src/__tests__/components/studies/create-study-modal.stop-conditions.test.tsx`](ui/src/__tests__/components/studies/create-study-modal.stop-conditions.test.tsx) (AC-1..AC-6 + 2 bug-guards + AC-8 + AC-10 + type="button" check + AC-6 follow-up). Late-stage E2E regression debugged + fixed: `studies-create-builder.spec.ts:130` + `studies-create-target-dropdown.spec.ts:48` were failing in CI smoke because Playwright's `.fill('10')` on the non-empty Max trials input triggered a stray form-submit before the test's explicit submit-button click. Root-cause fix decouples submission from the form's `onSubmit` event entirely: `
e.preventDefault()}>` + submit button changed from `type="submit"` to `type="button"` with `onClick={form.handleSubmit(onSubmit)}` — submission goes through exactly one path. Seven other fix attempts (drop `form` from deps, modal-overflow scrolling, prev-open useRef gating, in-effect setValue removal, RHF subscription watcher, useMemo-derived activePreset, Enter-key suppression) were tried and ruled out before the root cause was identified by reproducing the failure locally against the rebuilt production UI image. Cross-model review: spec converged at GPT-5.5 cycle 3 (12 findings, 11 accepted + 1 rejected with cited counter-evidence at `backend/app/api/errors.py:62, 118`); plan converged at cycle 2 (5 accepted + 0 cycle-2); impl-diff GPT-5.5 cycle 1 raised 1 Medium finding (modal-open form-field reset gap); Gemini Code Assist 1 Medium finding (Deferred — pre-existing form-state persistence across modal toggles, out of scope). Tests: UI vitest 98/98 studies + 730+ overall green; 5/5 CI jobs green incl. smoke 70+/70 Playwright. **Alembic head unchanged at `0017_proposals_last_polled_at`** — purely additive at the frontend layer. @@ -50,7 +50,7 @@ - **2026-05-22 — PR #195 squash `ea2b242` — three sibling `chore_guides_*` items bundled.** Closes [`chore_guides_glossary_route`](docs/00_overview/implemented_features/2026_05_22_chore_guides_glossary_route/), [`chore_guides_faq`](docs/00_overview/implemented_features/2026_05_22_chore_guides_faq/), and [`chore_guide_06_screenshot_refresh_confidence_panel`](docs/00_overview/implemented_features/2026_05_22_chore_guide_06_screenshot_refresh_confidence_panel/) on one branch + one PR. Shipped via ad-hoc implementation rather than the formal `/pipeline` ceremony after the user observed the spec/plan/execute overhead was disproportionate for ~200 LOC of frontend per item; glossary's committed `feature_spec.md` is kept as a design reference (went through 3 GPT-5.5 cross-model review cycles, 10 findings adjudicated). New routes: `/guide/glossary` (renders 109-entry `ui/src/lib/glossary.ts` with substring search + 8 category facet chips + `#` deep links) and `/guide/faq` (renders new 19-entry `ui/src/lib/faq.ts` typed constant with same contract; entries' questions self-link). Catalog section on `/guide` renamed `Glossary` → `Reference` to hold both new cards. 10 walkthrough `script.md` files get a footer link to the glossary. Guide-06 demo Playwright spec waits up to 45s for the ConfidencePanel before screenshot → `04-study-detail.png` regenerated to include the partial-shape panel; script.md narrative gains a Monitoring sub-section. Five SKILL.md gate edits (impl-execute Step 2.5 FAQ-shaped catch-net + Step 3 terminology/drift/decision-point bullets; spec-gen #11 cite-glossary-key; impl-plan-gen line 111 source-of-truth comment target) all locked by `glossary-gate-skill-edits.test.ts`. New shared `ui/src/lib/markdown-safety.ts` exports `MARKDOWN_DISALLOWED_ELEMENTS` consumed by 4 surfaces (glossary + FAQ + HelpPopover + MarkdownDoc) — extracted post-merge in response to a Gemini security-medium finding + earlier GPT-5.5 cycle-2 spec finding. Tests: UI vitest **706/706** (+67 across 6 new files), 2 new Playwright real-backend specs (glossary 7 + FAQ 6), demo Playwright regen 4 PNGs. CI required 1 fix push — stale `glossary-section` data-testid in `glossary.spec.ts` after the FAQ commit renamed the catalog section to `reference-section` (vitest updated, Playwright spec missed); fix landed before merge, 5/5 jobs green on the final SHA. Gemini Code Assist 5 Medium findings (2 accepted: unused `Card*` imports + shared markdown-safety constant; 3 rejected with cited counter-evidence — no "FR-7" in faq.ts, `feat_pr_metric_confidence` slugs are deliberate codebase-grep handles, Card primitive at `ui/components/ui/card.tsx:9` uses identical hardcoded colors). **Alembic head unchanged at `0015_trials_per_query_metrics`** — frontend-only feature, no backend code. -- **2026-05-22 — `feat_home_first_run_demo_nudge` merged into `main` as PR #188 squash `21325432`.** **25th MVP1 feature shipped**, 12 stories across 4 epics, all green on the squash SHA (5/5 CI jobs). Frontend-only polish layer on top of PR #182's auto-seed. **Dashboard banner** (`` at `ui/src/components/dashboard/demo-data-banner.tsx`): dismissable, sticky-dismissed per-browser via localStorage key `relyloop.home-first-run-demo-nudge.dismissed`, plural-aware body copy for K∈{1,2,3,4} demo clusters present. Hydration via `useSyncExternalStore` with a CONSERVATIVE server snapshot of `true` (dismissed) so pre-dismissed users never see a flash; after client hydration the snapshot reads localStorage and reveals the banner only for fresh users. New shared `safeLocalStorageGet`/`safeLocalStorageSet` wrapper at `ui/src/lib/safe-local-storage.ts` swallows Safari-private-mode + `QuotaExceededError` throws. `useClusters({ enabled: !dismissed })` skips the fetch entirely for dismissed users (post-merge Gemini optimization). **"Demo" indicator** on 3 cluster surfaces: JSX `` on `/clusters` table (tooltip-wrapped via Radix; keyboard-focusable via `tabIndex={0}` + `role="img"` + `aria-label`); `" (Demo)"` text suffix in create-study modal cluster picker AND proposals fk-select cluster filter (text suffix because shadcn `` + native `` don't accept JSX `