diff --git a/docs/00_overview/DASHBOARD.md b/docs/00_overview/DASHBOARD.md index d0de5b6b..97533565 100644 --- a/docs/00_overview/DASHBOARD.md +++ b/docs/00_overview/DASHBOARD.md @@ -7,7 +7,7 @@ _Top-level index across MVP1 → GA v1+ as of **2026-06-09**. Click a release na | Release | Theme | Progress | Status | |---|---|---|---| | [MVP1 / v0.1](MVP1_DASHBOARD.md) | The Loop | 100 / 100 scoped done | **Complete** | -| [MVP2 / v0.2](MVP2_DASHBOARD.md) | Three-Engine + Real Signals | 25 / 28 scoped done · 17 remaining | **In progress** | +| [MVP2 / v0.2](MVP2_DASHBOARD.md) | Three-Engine + Real Signals | 26 / 28 scoped done · 16 remaining | **In progress** | | MVP3 / v0.3 | Observable | — | **Not yet scoped** | | [GA v1 / v1.0](GA_DASHBOARD.md) | Production-ready | 1 item(s) queued | **Held / queued** | diff --git a/docs/00_overview/MVP2_DASHBOARD.md b/docs/00_overview/MVP2_DASHBOARD.md index 6f84335a..2eda1d81 100644 --- a/docs/00_overview/MVP2_DASHBOARD.md +++ b/docs/00_overview/MVP2_DASHBOARD.md @@ -21,20 +21,20 @@ Plan approved; run /impl-execute to ship | Metric | Value | |---|---| | Filed under MVP2 | **47** folders total (done + specced not-done + idea backlog + bugs) | -| Specced features done | **25 / 28** (89%) — of features *past the idea stage* (those with a spec); the idea backlog below is NOT in this denominator, so 100% ≠ release complete | -| Pending work | **20** items (every not-done feat/infra/chore/bug across all priorities) | +| Specced features done | **26 / 28** (93%) — of features *past the idea stage* (those with a spec); the idea backlog below is NOT in this denominator, so 100% ≠ release complete | +| Pending work | **19** items (every not-done feat/infra/chore/bug across all priorities) | | → P0 — do next | **0** unblocking / paying daily cost | | → P1 | **0** high-value, ready when P0 clears | -| → P2 (default) | 13 important to file, not blocking | +| → P2 (default) | 12 important to file, not blocking | | → Backlog | 7 captured for record, not planned | | Open bugs | 7 | -| Legacy "Path to MVP2" | 17 items — scoped-not-done + bugs + chore-ideas only (excludes feat/infra ideas) | +| Legacy "Path to MVP2" | 16 items — scoped-not-done + bugs + chore-ideas only (excludes feat/infra ideas) | | Backlog ideas | 3 idea-only feat/infra (not yet scoped into MVP2) | | In flight | 1 feature(s) actively shipping | ## Pipeline -### Done (27) +### Done (28) | Feature | Type | One-liner | Depends on | Status | |---|---|---|---|---| @@ -47,6 +47,7 @@ Plan approved; run /impl-execute to ship | [feat_overnight_studies_summary_card](implemented_features/2026_06_04_feat_overnight_studies_summary_card/feature_spec.md) | Feature | A "ran while you were away" card surfaces at the top of `/studies` when at least one overnight chain has completed since the operator's last visit. | — | [PR #444](https://github.com/SoundMindsAI/relyloop/pull/444) merged 2026-06-04 | | [feat_proposal_full_param_space_view](implemented_features/2026_06_04_feat_proposal_full_param_space_view/feature_spec.md) | Feature | A new `` renders below `` on `/proposals/[id]`. | — | [PR #446](https://github.com/SoundMindsAI/relyloop/pull/446) merged 2026-06-04 | | [feat_query_normalization_tuning](implemented_features/2026_06_05_feat_query_normalization_tuning/feature_spec.md) | Feature | A template that opts in by declaring `query_normalizer` as a Categorical param gets the Optuna loop deciding empirically — on the operator's judgment set — whether lowercasing, trimming, or contractio | — | [PR #459](https://github.com/SoundMindsAI/relyloop/pull/459) merged 2026-06-05 | +| [feat_query_normalizer_typed_pipeline](implemented_features/2026_06_09_feat_query_normalizer_typed_pipeline/feature_spec.md) | Feature | A new typed search-space member `NormalizerPipelineParam` lets a template declare an **ordered list of normalization steps**; the Optuna loop samples over the powerset of declared steps and proposes t | — | [PR #509](https://github.com/SoundMindsAI/relyloop/pull/509) merged 2026-06-09 | | [feat_studies_convergence_visibility](implemented_features/2026_06_02_feat_studies_convergence_visibility/feature_spec.md) | Feature | The studies list shows a completed-trial count and a convergence badge (`Converged` / `Still improving` / `Too few trials`) per study, reusing the shipped classifier. | — | [PR #421](https://github.com/SoundMindsAI/relyloop/pull/421) merged 2026-06-02 | | [feat_study_convergence_indicator](implemented_features/2026_06_01_feat_study_convergence_indicator/feature_spec.md) | Feature | Every completed study carries a plain-language **convergence verdict** — `converged` / `still_improving` / `too_few_trials` — backed by a best-metric-so-far curve. | — | [PR #352](https://github.com/SoundMindsAI/relyloop/pull/352) merged 2026-06-01 | | [feat_study_sub_warmup_guard](implemented_features/2026_05_29_feat_study_sub_warmup_guard/feature_spec.md) | Feature | A non-blocking inline warning appears under the `max_trials` input whenever the derived preset is `custom` AND `max_trials < STUDIES_TPE_WARMUP_FLOOR (= 50)`, naming Focused/Standard as one-click reme | — | [PR #316](https://github.com/SoundMindsAI/relyloop/pull/316) merged 2026-05-29 | @@ -72,13 +73,12 @@ Plan approved; run /impl-execute to ship |---|---|---|---|---|---|---| | 1 | P2 | [bug_cluster_url_ssrf_hostname_bypass](planned_features/02_mvp2/bug_cluster_url_ssrf_hostname_bypass/feature_spec.md) | Bug | When private clusters are disallowed (`RELYLOOP_ALLOW_PRIVATE_CLUSTERS=False`), a `base_url` whose host **resolves** to a private / loopback / link-local / reserved / multicast / unspecified / carrier | — | [PR #510](https://github.com/SoundMindsAI/relyloop/pull/510) | -### Plan (3) +### Plan (2) | # | Priority | Feature | Type | One-liner | Depends on | Status | |---|---|---|---|---|---|---| | 1 | P2 | [feat_apply_path_normalizer_declaration](planned_features/02_mvp2/feat_apply_path_normalizer_declaration/feature_spec.md) | Feature | The winning normalizer ships as a **structured, language-agnostic manifest** in the config-repo PR — not just prose. | — | — | -| 2 | P2 | [feat_query_normalizer_typed_pipeline](planned_features/02_mvp2/feat_query_normalizer_typed_pipeline/feature_spec.md) | Feature | A new typed search-space member `NormalizerPipelineParam` lets a template declare an **ordered list of normalization steps**; the Optuna loop samples over the powerset of declared steps and proposes t | — | — | -| 3 | P2 | [chore_demo_seeding_integration_tests_rewrite](planned_features/02_mvp2/chore_demo_seeding_integration_tests_rewrite/feature_spec.md) | Chore | The 9 skipped cases are rewritten to the async "POST + poll-until-terminal" shape, the timeout case is re-homed to the worker layer, a new `AC-Async` case asserts the `running → complete` polling tran | — | [PR #286](https://github.com/SoundMindsAI/relyloop/pull/286) | +| 2 | P2 | [chore_demo_seeding_integration_tests_rewrite](planned_features/02_mvp2/chore_demo_seeding_integration_tests_rewrite/feature_spec.md) | Chore | The 9 skipped cases are rewritten to the async "POST + poll-until-terminal" shape, the timeout case is re-homed to the worker layer, a new `AC-Async` case asserts the `running → complete` polling tran | — | [PR #286](https://github.com/SoundMindsAI/relyloop/pull/286) | ### Spec (0) @@ -120,8 +120,6 @@ graph LR class chore_demo_seeding_integration_tests_rewrite plan; feat_apply_path_normalizer_declaration["apply path normalizer declaration"] class feat_apply_path_normalizer_declaration plan; - feat_query_normalizer_typed_pipeline["query normalizer typed pipeline"] - class feat_query_normalizer_typed_pipeline plan; feat_contextual_help_mvp2["contextual help mvp2"] class feat_contextual_help_mvp2 done; feat_study_sub_warmup_guard["study sub warmup guard"] @@ -172,6 +170,8 @@ graph LR class feat_query_normalization_tuning done; feat_ubi_llm_study_comparison["ubi llm study comparison"] class feat_ubi_llm_study_comparison done; + feat_query_normalizer_typed_pipeline["query normalizer typed pipeline"] + class feat_query_normalizer_typed_pipeline done; feat_ubi_judgments --> infra_adapter_solr ``` diff --git a/docs/00_overview/dashboard.html b/docs/00_overview/dashboard.html index fa07221b..36ddf42d 100644 --- a/docs/00_overview/dashboard.html +++ b/docs/00_overview/dashboard.html @@ -392,7 +392,7 @@

Releases

Three-Engine + Real Signals
-
25 / 28 scoped done · 17 remaining
+
26 / 28 scoped done · 16 remaining
In progress
diff --git a/docs/00_overview/planned_features/02_mvp2/feat_query_normalizer_typed_pipeline/feature_spec.md b/docs/00_overview/implemented_features/2026_06_09_feat_query_normalizer_typed_pipeline/feature_spec.md similarity index 100% rename from docs/00_overview/planned_features/02_mvp2/feat_query_normalizer_typed_pipeline/feature_spec.md rename to docs/00_overview/implemented_features/2026_06_09_feat_query_normalizer_typed_pipeline/feature_spec.md diff --git a/docs/00_overview/planned_features/02_mvp2/feat_query_normalizer_typed_pipeline/idea.md b/docs/00_overview/implemented_features/2026_06_09_feat_query_normalizer_typed_pipeline/idea.md similarity index 100% rename from docs/00_overview/planned_features/02_mvp2/feat_query_normalizer_typed_pipeline/idea.md rename to docs/00_overview/implemented_features/2026_06_09_feat_query_normalizer_typed_pipeline/idea.md diff --git a/docs/00_overview/planned_features/02_mvp2/feat_query_normalizer_typed_pipeline/implementation_plan.md b/docs/00_overview/implemented_features/2026_06_09_feat_query_normalizer_typed_pipeline/implementation_plan.md similarity index 99% rename from docs/00_overview/planned_features/02_mvp2/feat_query_normalizer_typed_pipeline/implementation_plan.md rename to docs/00_overview/implemented_features/2026_06_09_feat_query_normalizer_typed_pipeline/implementation_plan.md index 452edcb9..2800e684 100644 --- a/docs/00_overview/planned_features/02_mvp2/feat_query_normalizer_typed_pipeline/implementation_plan.md +++ b/docs/00_overview/implemented_features/2026_06_09_feat_query_normalizer_typed_pipeline/implementation_plan.md @@ -1,7 +1,7 @@ # Implementation Plan — Typed normalizer pipeline (ordered step list) + JS snippet + smart-quote contractions -**Date:** 2026-06-01 (gates cleared 2026-06-09) -**Status:** Ready for Execution — Phase 1 (`feat_query_normalization_tuning`) merged (PR #459, symbols verified present 2026-06-09); Q-1 locked (include `expand_contractions_custom` inert, 6 steps) + Q-2 locked (frontend vitest fixture). Story 0 precondition gate confirmed passing. +**Date:** 2026-06-01 (gates cleared + shipped 2026-06-09) +**Status:** Complete (PR #509, squash-merged `7a24849`, merged 2026-06-09). Q-1 locked (include `expand_contractions_custom` inert, 6 steps) + Q-2 locked (frontend vitest fixture); all 8 stories shipped. **Primary spec:** [`feature_spec.md`](feature_spec.md) **Phase 1 foundation:** [`../feat_query_normalization_tuning/feature_spec.md`](../feat_query_normalization_tuning/feature_spec.md) (UNMERGED — plan stage), [`../feat_query_normalization_tuning/implementation_plan.md`](../feat_query_normalization_tuning/implementation_plan.md) **Policy source(s):** CLAUDE.md (Absolute Rules #3, #4, #8 — adapter-confined, no hardcoded models, source-of-truth enum discipline), `docs/01_architecture/optimization.md`, `docs/01_architecture/adapters.md` diff --git a/docs/00_overview/planned_features/02_mvp2/feat_query_normalizer_typed_pipeline/pipeline_status.md b/docs/00_overview/implemented_features/2026_06_09_feat_query_normalizer_typed_pipeline/pipeline_status.md similarity index 66% rename from docs/00_overview/planned_features/02_mvp2/feat_query_normalizer_typed_pipeline/pipeline_status.md rename to docs/00_overview/implemented_features/2026_06_09_feat_query_normalizer_typed_pipeline/pipeline_status.md index 42377b16..c54b463f 100644 --- a/docs/00_overview/planned_features/02_mvp2/feat_query_normalizer_typed_pipeline/pipeline_status.md +++ b/docs/00_overview/implemented_features/2026_06_09_feat_query_normalizer_typed_pipeline/pipeline_status.md @@ -1,6 +1,8 @@ # Pipeline Status — Typed normalizer pipeline (Phase 2 of query-normalization-tuning) -> **DESIGN-AHEAD.** Implementation is GATED on Phase 1 (`feat_query_normalization_tuning`) merging to `main`. Phase 1 is currently UNMERGED (plan stage). Do NOT run `/impl-execute` until Phase 1 ships — see `feature_spec.md` §5 and the design-ahead banner. +**Release:** mvp2 + +> **SHIPPED 2026-06-09 (PR #509).** The design-ahead gate cleared: Phase 1 (`feat_query_normalization_tuning`) merged, and Q-1 (include `expand_contractions_custom` inert — 6 steps) + Q-2 (JS parity via frontend vitest fixture) were locked. ## Idea - Status: Complete @@ -27,4 +29,9 @@ - Execution gate: Story 0 asserts Phase 1 symbols exist and aborts otherwise. Open Questions Q-1 + Q-2 must be locked before `/impl-execute`. ## Implementation -- Status: Not started — BLOCKED on Phase 1 (`feat_query_normalization_tuning`) merge + Q-1/Q-2 lock. +- Status: **Complete** (PR #509, squash-merged `7a24849`, 2026-06-09) +- CI: all 19 `pr.yml` checks green (smoke skipped — opt-in/off); coverage 81.64% ≥ 80% +- Stories: 8/8 complete across 5 epics (Story 0 gate confirmed passing; Epic 1 domain ×4; Epic 2 PR body ×1; Epic 3 frontend ×2; Epic 4 docs ×1) +- Migration: none (Alembic head stays 0023) +- Cross-model review: Opus self-review (GPT-5.5 unreachable in the Claude Code remote sandbox); Gemini Code Assist — 2 Medium findings, both accepted (`7047190`: strip_punctuation snippets use the runtime's regex) +- Q-1 locked: include `expand_contractions_custom` inert (6 steps). Q-2 locked: JS parity via frontend vitest fixture. diff --git a/docs/00_overview/mvp2_dashboard.html b/docs/00_overview/mvp2_dashboard.html index 8aade395..5a81e366 100644 --- a/docs/00_overview/mvp2_dashboard.html +++ b/docs/00_overview/mvp2_dashboard.html @@ -397,13 +397,13 @@

MVP2 Progress

Specced features done
-
25 / 28
-
89% specced · 47 filed under MVP2
-
+
26 / 28
+
93% specced · 47 filed under MVP2
+
Pending work
-
20
+
19
every not-done feat/infra/chore/bug across all priorities
@@ -425,7 +425,7 @@

MVP2 Progress

P2 (default)
-
13
+
12
important to file, not blocking
@@ -435,7 +435,7 @@

MVP2 Progress

Legacy "Path to MVP2"
-
17
+
16
scoped not-done + bugs + chore-ideas only (excludes feat/infra ideas)
@@ -680,7 +680,7 @@

Spec 0

-

Plan 3

+

Plan 2

@@ -695,19 +695,6 @@

Plan 3

-
- -
- Feature - P2 - -
-
A new typed search-space member `NormalizerPipelineParam` lets a template declare an **ordered list of normalization steps**; the Optuna loop samples over the powerset of declared steps and proposes t
- - -
- -
@@ -740,7 +727,7 @@

Implementing 1

-

Done 27

+

Done 28

@@ -859,6 +846,19 @@

Done 27

+
+ +
+ Feature + + PR #509 merged 2026-06-09 +
+
A new typed search-space member `NormalizerPipelineParam` lets a template declare an **ordered list of normalization steps**; the Optuna loop samples over the powerset of declared steps and proposes t
+ + +
+ +
@@ -1109,8 +1109,6 @@

Dependency graph (feat_ + infra_)

class chore_demo_seeding_integration_tests_rewrite plan; feat_apply_path_normalizer_declaration["apply path normalizer declaration"] class feat_apply_path_normalizer_declaration plan; - feat_query_normalizer_typed_pipeline["query normalizer typed pipeline"] - class feat_query_normalizer_typed_pipeline plan; feat_contextual_help_mvp2["contextual help mvp2"] class feat_contextual_help_mvp2 done; feat_study_sub_warmup_guard["study sub warmup guard"] @@ -1161,6 +1159,8 @@

Dependency graph (feat_ + infra_)

class feat_query_normalization_tuning done; feat_ubi_llm_study_comparison["ubi llm study comparison"] class feat_ubi_llm_study_comparison done; + feat_query_normalizer_typed_pipeline["query normalizer typed pipeline"] + class feat_query_normalizer_typed_pipeline done; feat_ubi_judgments --> infra_adapter_solr
diff --git a/state.md b/state.md index a03694a3..23d1a473 100644 --- a/state.md +++ b/state.md @@ -2,7 +2,7 @@ > Read this first. A one-page snapshot: current focus, the last few merges, what's in flight, what's queued, and where the project sits in the MVP1 → MVP2 → MVP3 → GA roadmap. **Historical feature-merge narrative + chained execution context lives in [`state_history.md`](state_history.md)** — new merge entries land there, not here (per `chore_state_md_size_compression`, 2026-05-29). Keep this file loadable in a single `Read` call. -**Last updated:** 2026-06-09 (**`bug_cluster_url_ssrf_hostname_bypass` Phase 1 merged** — PR #510, squash-merged `3cb28c7`; closes the cluster `base_url` SSRF hostname-bypass via a flag-gated async resolve-and-classify guard before any probe. New `domain/cluster/url_policy.py` (pure classifier) + `services/cluster_url_policy.py` (async orchestrator) + `400 CLUSTER_URL_BLOCKED`; the two `base_url` validators de-duped to one structural helper. No migration/UI (Alembic head stays `0023`). Surfaced by security review #504 (auto-closed); driven through `/pipeline --auto` (spec→plan→3 stories). 52 new tests; Gemini 2 Med accepted (bounded DNS, malformed-port 422). **Phase 2 (connect-time IP pinning) deferred — folder stays in `planned_features/02_mvp2/` with `phase2_idea.md`, NOT archived.** Previously: **`chore_overnight_result_card_screenshot` finalized** — PR #492 reviewed via `/pr-review` and squash-merged (`4128572`); this entry is its `state.md`/`state_history.md` finalization. Ships the tutorial Step 12 morning-result-card PNG + the first-ever tutorial-image **ferry plumbing** across both doc-copy pipelines (`ui/scripts/copy-docs.mjs` `copyImageAssets`/`pruneStaleImages`; `website/scripts/build_guides.py` `copy_long_form_images()` + a back-compat-defaulted prune kwarg), both guarding the `images/` subdir from the flat `.md`/`rmtree` prune and no-op'ing when the source dir is absent. **Docs/test/script only — no code-path/API/migration** (Alembic head stays `0023`). 17 new tests (10 vitest + 7 pytest). GPT-5.5 unreachable → Opus self-review; Gemini 1 Med accepted (`02450dd`: hoist `readFileSync` to a top-level import vs inline `require`). All three freshness gates (`generated-artifacts-fresh`/`copy-docs`/`build-guides`) + 18/19 checks green (smoke skipped — opt-in/off). Full narrative in [`state_history.md`](state_history.md).) +**Last updated:** 2026-06-09 (**`feat_query_normalizer_typed_pipeline` merged** — PR #509, squash-merged `7a24849`. Extends Phase 1's fixed-bundle `query_normalizer` into a **typed pipeline**: declare `{type:"normalizer_pipeline", steps:[…]}` over 6 atomic `NormalizerStep`s; the Optuna loop searches the **powerset** (`2^N` labels). Bundles stay backward-compatible (one engine — `normalize_pipeline`; `normalize(bundle)` is now a wrapper). FR-3 smart-quote (U+2019) expansion, reserved-key-only validation (`NormalizerPipelineMisplacedError` → `INVALID_SEARCH_SPACE`), adapter pre-render hook resolves bundle OR pipeline label via `steps_for_label`, **bilingual** (Python+JS) PR-body snippets with three-way output parity (committed corpus fixture), `` builder row + 7 glossary keys + digest-advisory broadening, docs. **No migration** (head stays `0023`). Q-1 locked (include `expand_contractions_custom` inert — 6 steps); Q-2 locked (JS parity via frontend vitest fixture). Opus self-review (GPT-5.5 unreachable); Gemini 2 Med accepted (`7047190`: strip_punctuation snippets use the runtime regex). Extended `verify_enum_source_of_truth` to resolve StrEnum classes. All 19 `pr.yml` checks green (smoke skipped). Full narrative in [`state_history.md`](state_history.md). Previously: **`bug_cluster_url_ssrf_hostname_bypass` Phase 1 merged** — PR #510, squash-merged `3cb28c7`; closes the cluster `base_url` SSRF hostname-bypass via a flag-gated async resolve-and-classify guard before any probe. New `domain/cluster/url_policy.py` (pure classifier) + `services/cluster_url_policy.py` (async orchestrator) + `400 CLUSTER_URL_BLOCKED`; the two `base_url` validators de-duped to one structural helper. No migration/UI (Alembic head stays `0023`). Surfaced by security review #504 (auto-closed); driven through `/pipeline --auto` (spec→plan→3 stories). 52 new tests; Gemini 2 Med accepted (bounded DNS, malformed-port 422). **Phase 2 (connect-time IP pinning) deferred — folder stays in `planned_features/02_mvp2/` with `phase2_idea.md`, NOT archived.** Previously: **`chore_overnight_result_card_screenshot` finalized** — PR #492 reviewed via `/pr-review` and squash-merged (`4128572`); this entry is its `state.md`/`state_history.md` finalization. Ships the tutorial Step 12 morning-result-card PNG + the first-ever tutorial-image **ferry plumbing** across both doc-copy pipelines (`ui/scripts/copy-docs.mjs` `copyImageAssets`/`pruneStaleImages`; `website/scripts/build_guides.py` `copy_long_form_images()` + a back-compat-defaulted prune kwarg), both guarding the `images/` subdir from the flat `.md`/`rmtree` prune and no-op'ing when the source dir is absent. **Docs/test/script only — no code-path/API/migration** (Alembic head stays `0023`). 17 new tests (10 vitest + 7 pytest). GPT-5.5 unreachable → Opus self-review; Gemini 1 Med accepted (`02450dd`: hoist `readFileSync` to a top-level import vs inline `require`). All three freshness gates (`generated-artifacts-fresh`/`copy-docs`/`build-guides`) + 18/19 checks green (smoke skipped — opt-in/off). Full narrative in [`state_history.md`](state_history.md).) ## Where the roadmap sits @@ -26,17 +26,17 @@ MVP1 (v0.1) **shipped** — all six differentiators live (Bayesian/TPE optimizer Detail + reasoning for each is in [`state_history.md`](state_history.md). +- **2026-06-09** — `feat_query_normalizer_typed_pipeline` (PR #509, squash-merged `7a24849`). **Typed normalizer pipeline (powerset search) — Phase 2 of query-normalization tuning.** A template declares `query_normalizer` as `{type:"normalizer_pipeline", steps:[…]}` over 6 atomic `NormalizerStep`s (`lowercase`, `strip_punctuation`, `expand_contractions_en`, `expand_contractions_custom` [inert/reserved], `collapse_whitespace`, `trim`); the loop samples the **powerset** (`2^N` labels, `estimate_cardinality *= 2**len(steps)`). Two decoupled orderings: `STEP_ORDER` (application — whitespace cleanup last) vs `LABEL_ORDER` (Phase-1-byte-compatible serialization). `normalize_pipeline` is the single engine; `normalize(bundle)` is now a thin wrapper (byte-parity guarded). FR-3: U+2019 smart-quote pre-normalization in the contraction step. Reserved-key-only: a pipeline under any non-`query_normalizer` key → `NormalizerPipelineMisplacedError` → `INVALID_SEARCH_SPACE` (D-8, no new code). Both adapter pre-render hooks resolve a bundle string OR a powerset label via `steps_for_label` → `normalize_pipeline`. PR body emits **Python + JS/TS** reference snippets generated from the winning label (three-way output parity: runtime ≡ Python-exec ≡ JS-via-Node, locked by a committed `ui/.../normalizer_snippet_parity.json` corpus + a backend freshness guard + a vitest spec). Frontend: `` (6-step checkbox multi-select), `NORMALIZER_STEP_VALUES`/`NORMALIZER_STEP_GLOSSARY_KEYS`, 7 glossary keys, cardinality `2^N`, digest advisory broadened to the `lowercase`-token test. Broke the `normalizers`↔`search_space` import cycle (function-local back-edge). **No migration** (head stays `0023`). Q-1 locked: include `expand_contractions_custom` inert (6 steps). Q-2 locked: JS parity via frontend vitest fixture. GPT-5.5 unreachable → Opus self-review; Gemini 2 Med accepted (`7047190`: strip_punctuation snippets use the runtime regex). Two downstream `ParamSpec` consumers (`search_space_defaults`, `baseline_resolver`) fixed for the new union member; `verify_enum_source_of_truth` extended to resolve StrEnum classes (first StrEnum enums.ts citation). 18 new test files across unit/contract/integration/e2e/vitest; coverage 81.64%. All 19 `pr.yml` checks green (smoke skipped). Merged main (SSRF guard) in before merge — no skew. Finalization shipped as this separate docs PR. - **2026-06-09** — `bug_cluster_url_ssrf_hostname_bypass` Phase 1 (PR #510, squash-merged `3cb28c7`). **Hostname-aware SSRF guard on cluster `base_url`.** The prior validator only inspected literal IPs, so in the hardened posture (`RELYLOOP_ALLOW_PRIVATE_CLUSTERS=False`) any DNS hostname — `metadata.google.internal`, an internal name, or anything resolving to a private/loopback/link-local IP — bypassed the check and got probed. New flag-gated async `assert_base_url_allowed` (`backend/app/services/cluster_url_policy.py`) runs **before** any adapter build / probe in `register_cluster` + `test_cluster_connection`: metadata-hostname denylist → literal-IP classify → else `getaddrinfo` (bounded 5s) + classify every resolved address; raises `ClusterUrlBlocked` → **400 `CLUSTER_URL_BLOCKED`**. Pure classifier in `backend/app/domain/cluster/url_policy.py` (private/loopback/link-local/reserved/multicast/unspecified + CGNAT + IPv4-mapped unwrap); the two duplicated `base_url` Pydantic validators collapsed to one structural helper (scheme + host + malformed-port→422). **Default posture unchanged** (flag defaults `True` → strict no-op; local Docker hostnames keep working). **No migration, no UI** (Alembic head stays `0023`). Surfaced by the codebase security review (#504, auto-closed). Found via the full pipeline (`/pipeline --auto`): spec → plan → 3 stories. 52 new tests (domain classifier + service orchestrator incl. enforcement-before-probe + real-DNS integration + contract envelope). GPT-5.5 unreachable → Opus self-review; Gemini 2 Medium accepted (`8c2ca37`: bounded DNS timeout + malformed-port 422). All `pr.yml` jobs green (smoke skipped). **Phase 2 (connect-time IP pinning for DNS rebinding) deferred** → folder stays in `planned_features/02_mvp2/` with `phase2_idea.md`; NOT moved to `implemented_features/`. - **2026-06-07** — `chore_overnight_result_card_screenshot` (PR #492, squash-merged `4128572`). **Tutorial Step 12 morning-result-card screenshot + image-ferry plumbing.** Closes FR-9 of `feat_overnight_final_solution_phase2` (PR #442) — the morning result-card prose shipped there but the screenshot deliverable didn't (the standard demo seed can't produce a terminated `follow_suggestions` chain with a winning proposal + digest; the plan's hard-fallback escape hatch authorized this follow-up chore). Ships three things: (1) the 34 KB PNG (`docs/08_guides/images/12-overnight-result-card.png`, captured 1440×960 against a deterministically-seeded anchor+follow-up chain, `selected_followup_kind='narrow'`, +0.0450 lift); (2) the Step 12 `![Overnight result card](images/12-overnight-result-card.png)` reference in `tutorial-first-study.md`; (3) the **first-ever tutorial-image ferry plumbing** — `ui/scripts/copy-docs.mjs` gains `copyImageAssets`/`pruneStaleImages` (mirrors `docs/08_guides/images/*.png` → `ui/public/docs/images/`) and `website/scripts/build_guides.py` gains `copy_long_form_images()` + a back-compat-defaulted `copied_long_form_images` prune kwarg (mirrors → `website/docs/guides/in-depth/images/`); both guard the `images/` subdir from the flat `.md`/`rmtree` prune, no-op when the source dir is absent, and are path-traversal-safe by construction. **Docs/test/script only — no code-path/API/migration** (head stays `0023`). 17 new tests (10 vitest + 7 pytest) cover copy + prune + steady-state-absent + overwrite-stale + protect-subdir-during-flat-prune + back-compat. GPT-5.5 unreachable → Opus self-review; Gemini 1 Med accepted (`02450dd`: hoist `readFileSync` to a top-level `node:fs` import vs inline `require`). All three freshness gates (`generated-artifacts-fresh`/`copy-docs`/`build-guides`) + 18/19 checks green (smoke skipped — opt-in/off). Bundled preflight idea patches (locks D-1 asset path, D-2 regen tool, D-3 plumbing-in-scope) + MVP2 dashboard regen. State finalization shipped as this separate docs PR (#492 was reviewed/merged via `/pr-review`). - **2026-06-05** — `bug_seed_meaningful_demos_silent_bulk_errors` (PR #482, squash-merged `7991147`). **`seed_meaningful_demos.py` fails loud on `/_bulk` errors.** The ESCI rich-scenario bulk loop read and DISCARDED the `/_bulk` response; ES bulk returns HTTP 200 even when the primary shard is INITIALIZING (error in the body), so `unavailable_shards_exception` on a cold ES — or any mapping bug — silently produced a partial/empty index while `make seed-demo` looked successful. **Script + test only — no code-path/API/migration** (head stays `0023`). Adds a **standalone urllib** retry helper (`_bulk_index_with_retry` + `_first_bulk_error`) mirroring the httpx-based `backend/app/scripts/seed_es.py` posture (`bug_smoke_seed_es_unavailable_shards_race`): parse the body, retry ONLY `unavailable_shards_exception` (3×2s), raise `RuntimeError` loud on any other error or exhausted retries. Locked **standalone** (not a shared `scripts/_es_bulk.py`) — the two scripts use different HTTP libs and `backend` already imports `SCENARIOS` *from* this script (cycle risk); `send`/`sleep` injectable for unit-testability. **GPT-5.5 unreachable → Opus self-review.** Gemini 1 Med accepted (`f24049d`): `_first_bulk_error` now type-guards the payload + returns a synthetic `unknown_bulk_error` when `errors:true` but no item error is found (a real gap in THIS caller — unlike `seed_es.py` it treats a `None` return as success), so a malformed body fails loud instead of phantom-success. 7 unit tests mirroring `test_seed_es_retry.py`; verified load-bearing via a `raise`→`return` mutation. Idea preflighted (stale `:917-935` → ~2549-2571; `scripts/seed_es.py` → `backend/app/scripts/seed_es.py`); bug_fix.md captures the locked design. All 19 `pr.yml` checks green. Finalization bundled (this batched-3 PR). - **2026-06-05** — `bug_relyloop_spec_ubi_section_drift` (PR #481, squash-merged `2b57848`). **Repoint 3 broken `relyloop-spec.md` UBI/Solr links to `implemented_features/`.** The §"Click-derived judgments" section linked `feat_ubi_judgments` + `infra_adapter_solr` via `planned_features/` paths, but both shipped + moved, so line 723 (`infra_adapter_solr` → `planned_features/02_mvp2/…`) and lines 2282 + 2293 (the original broken `../../00_overview/planned_features/…` pattern) 404'd; all three repointed to `implemented_features/2026_05_31_infra_adapter_solr/` and `implemented_features/2026_05_29_feat_ubi_judgments/` (verified resolve). **Docs-only — no code, no migration** (head stays `0023`). Preflight found the idea mostly OBE: the `(MVP1.5)` title + the §706 `feat_ubi_judgments` link were already fixed in earlier sweeps; the real remaining breakage was the 3 shipped-feature links. No Gemini findings; reduced docs-only check set green. -- **2026-06-05** — `chore_demo_reseed_partial_completion_fast_test` (PR #480, squash-merged `878cd96`). **Fast unit guard for the demo-reseed partial-completion path.** The engine-tolerant behavior (an unreachable engine's scenario skips, the reseed still finishes `status="complete"` with a non-empty `scenarios_skipped` + exactly one `demo_reseed_partial_completion_engines_unreachable` WARN, AC-7) was asserted end-to-end only by the 13-19 min heavy-lane `test_demo_seeding_ubi_full.py`. **Test only — no production diff, no migration** (head stays `0023`). Drives the real `reseed_demo_state` orchestrator with every module-level I/O helper (`_post`/`_get`/`_put`/`_seed_real_study_for_scenario`/`_seed_rich_scenario`/UBI helpers) monkeypatched to canned success (locked approach **b′** — no httpx-URL mock, no seam extraction; control flow stays real, so no conflict with the deferred `chore_demo_seeding_integration_tests_rewrite`). `is_engine_reachable` reports only Solr down. 2 cases: only-Solr-skip (`scenarios_skipped == ["acme-kb-docs-solr"]`, status complete, one WARN) + AC-3 (a reachable mid-seed failure raises a generic `DemoSeedingError`, never a skip). **GPT-5.5 unreachable → Opus self-review.** Gemini 3 Med ALL accepted (`3c69188`): `_fake_get` gains `auth`, the `is_engine_reachable` mock gains `**kwargs` (real fn has keyword-only `timeout_s`), AC-3 reads `captured["progress"]` directly. Verified load-bearing via a WARN-suppression mutation; pure unit (no DB/engine/OpenAI). bug_fix.md captures the locked design. All 19 `pr.yml` checks green. -_(older entries — full narrative in [`state_history.md`](state_history.md): `chore_pr_yml_parallelize_backend_job` PR #478, `chore_studies_post_arq_spy_fixture` PR #476, `chore_ubi_reader_search_after_pagination` PR #474, `feat_fts_rank_ordering` PR #472, `bug_judgment_header_omits_click_bucket` PR #470, `bug_baseline_phase_test_isolation` PR #466, `chore_cluster_detail_rung_badge` PR #464, `feat_ubi_llm_study_comparison` PR #461, `feat_query_normalization_tuning` PR #459, `feat_overnight_final_solution_phase3` PR #457, `feat_study_wizard_inline_judgment_generation` PR #453, `feat_walkthrough_video_cursor_captions` PR #451, `feat_website_walkthrough_guides` PR #448, `feat_proposal_full_param_space_view` PR #446, `feat_overnight_studies_summary_card` PR #444, `feat_overnight_final_solution_phase2` PR #442, `feat_overnight_final_solution` PR #440, `feat_studies_list_trial_convergence_columns` PR #438, `feat_list_count_columns` PR #436, `infra_generated_artifact_freshness_gate` PR #433, `chore_scorecard_pin_deps_postcss` PR #430, `bug_llm_capability_cache_no_refresh` PR #426, `infra_smoke_reseed_runtime_budget` PR #424, `feat_studies_convergence_visibility` PR #421/#422, `bug/cli-seed-ubi-missing-engine-type` PR #419, `chore_template_library_expansion` PR #416, `infra_solr_smoke_stability` PR #383, `infra_solr_ci_readiness` Phase 1 PR #367, MVP2 backlog batch PR #364, `feat_study_convergence_indicator` PR #352, `feat_overnight_autopilot` PR #343, `infra_adapter_solr` PR #336, …)_ +_(older entries — full narrative in [`state_history.md`](state_history.md): `chore_demo_reseed_partial_completion_fast_test` PR #480, `chore_pr_yml_parallelize_backend_job` PR #478, `chore_studies_post_arq_spy_fixture` PR #476, `chore_ubi_reader_search_after_pagination` PR #474, `feat_fts_rank_ordering` PR #472, `bug_judgment_header_omits_click_bucket` PR #470, `bug_baseline_phase_test_isolation` PR #466, `chore_cluster_detail_rung_badge` PR #464, `feat_ubi_llm_study_comparison` PR #461, `feat_query_normalization_tuning` PR #459, `feat_overnight_final_solution_phase3` PR #457, `feat_study_wizard_inline_judgment_generation` PR #453, `feat_walkthrough_video_cursor_captions` PR #451, `feat_website_walkthrough_guides` PR #448, `feat_proposal_full_param_space_view` PR #446, `feat_overnight_studies_summary_card` PR #444, `feat_overnight_final_solution_phase2` PR #442, `feat_overnight_final_solution` PR #440, `feat_studies_list_trial_convergence_columns` PR #438, `feat_list_count_columns` PR #436, `infra_generated_artifact_freshness_gate` PR #433, `chore_scorecard_pin_deps_postcss` PR #430, `bug_llm_capability_cache_no_refresh` PR #426, `infra_smoke_reseed_runtime_budget` PR #424, `feat_studies_convergence_visibility` PR #421/#422, `bug/cli-seed-ubi-missing-engine-type` PR #419, `chore_template_library_expansion` PR #416, `infra_solr_smoke_stability` PR #383, `infra_solr_ci_readiness` Phase 1 PR #367, MVP2 backlog batch PR #364, `feat_study_convergence_indicator` PR #352, `feat_overnight_autopilot` PR #343, `infra_adapter_solr` PR #336, …)_ ## In flight - **3-chore queue (2026-06-05 afternoon):** #1 `chore_studies_post_arq_spy_fixture` **shipped** (PR #476); #3 `chore_pr_yml_parallelize_backend_job` **shipped** (PR #478, descoped to lint-dedup; lane-split deferred to `infra_pr_yml_split_backend_test_lanes`). **#2 `chore_demo_seeding_integration_tests_rewrite` deferred** (14-story DB-only integration choreography; blocked on a local stack for CI-blind-safe validation). (Earlier 3-feature queue fully shipped: PR #470/#472/#474.) -- **Plan-stage, `/impl-execute`-ready (no gates):** `chore_demo_seeding_integration_tests_rewrite` (PR #286, DB-only integration — CI-only verification). **Note (drift correction 2026-06-05):** the two PR #364 normalizer siblings `feat_apply_path_normalizer_declaration` + `feat_query_normalizer_typed_pipeline` are **NOT** auto-executable — `feat_query_normalization_tuning` Phase 1 (PR #459) only cleared the G-1 *dependency* gate; both still carry **product gates** (apply-path: G-2 operator-friction evidence; typed-pipeline: open Q-1 Product decision + a "Do NOT `/impl-execute`" design-ahead banner). They stay product-gated/design-ahead until those clear. (`chore_arq_pool_aclose_deprecation`, `chore_cluster_detail_rung_badge`, `bug_baseline_phase_test_isolation`, `bug_judgment_header_omits_click_bucket`, `chore_ubi_reader_search_after_pagination`, `chore_studies_post_arq_spy_fixture`, `chore_pr_yml_parallelize_backend_job` shipped — PRs #463/#464/#466/#470/#474/#476/#478.) +- **Plan-stage, `/impl-execute`-ready (no gates):** `chore_demo_seeding_integration_tests_rewrite` (PR #286, DB-only integration — CI-only verification). **Note:** `feat_query_normalizer_typed_pipeline` **shipped** (PR #509, 2026-06-09 — Q-1 + Q-2 locked, design-ahead gate cleared). Its sibling `feat_apply_path_normalizer_declaration` remains **product-gated** (G-2 operator-friction evidence) + design-ahead. (`chore_arq_pool_aclose_deprecation`, `chore_cluster_detail_rung_badge`, `bug_baseline_phase_test_isolation`, `bug_judgment_header_omits_click_bucket`, `chore_ubi_reader_search_after_pagination`, `chore_studies_post_arq_spy_fixture`, `chore_pr_yml_parallelize_backend_job` shipped — PRs #463/#464/#466/#470/#474/#476/#478.) ## Queued (priority-ordered by dashboard / dep graph) diff --git a/state_history.md b/state_history.md index 899ecc72..838be7a4 100644 --- a/state_history.md +++ b/state_history.md @@ -4,6 +4,27 @@ --- +### `feat_query_normalizer_typed_pipeline` — typed normalizer pipeline (powerset search) + bilingual PR snippet + smart quotes (PR #509, 2026-06-09) + +**What shipped.** Phase 2 of the query-normalization-tuning epic. Where Phase 1 (`feat_query_normalization_tuning`, PR #459) let a template pick from four fixed bundle strings under the reserved `query_normalizer` key, this feature lets a template declare `query_normalizer` as a **typed pipeline** — `{type:"normalizer_pipeline", steps:[…]}` over six atomic `NormalizerStep`s (`lowercase`, `strip_punctuation`, `expand_contractions_en`, `expand_contractions_custom` [inert/reserved per Q-1], `collapse_whitespace`, `trim`) — and the Optuna loop searches the **powerset** of the declared steps (`2^N` labels, one categorical choice per subset incl. the empty subset `"none"`). **No migration** (Alembic head stays `0023`; rides existing JSONB columns). + +**Design spine.** +- **One execution engine.** `normalize_pipeline(query_text, steps)` applies the declared steps filtered+reordered by the canonical `STEP_ORDER` (whitespace cleanup LAST, so co-selecting `strip_punctuation` never leaves doubled/trailing spaces — D-11). `normalize(text, bundle)` was reimplemented as a thin wrapper over `normalize_pipeline(_BUNDLE_TO_STEPS[bundle])` (D-6), guarded byte-for-byte against Phase 1's hand-rolled branches by `test_normalizers_bundle_compat.py`. +- **Two decoupled orderings.** `STEP_ORDER` (application) vs `LABEL_ORDER` (serialization). `LABEL_ORDER` + `STEP_LABEL_TOKEN` (which maps `expand_contractions_en`→`"expand_contractions"`) keep the subset `{lowercase,trim,expand_contractions_en}` serializing to `"lowercase+trim+expand_contractions"` — byte-identical to Phase 1's bundle string, so bundles ⊂ pipeline label space and both share one wire vocabulary. `steps_for_label` is the token-aware reverse map (bundle string OR powerset label → step tuple), shared by the adapter hook and the PR-body generator. +- **FR-3 smart quotes.** The `expand_contractions_en` step pre-normalizes U+2019 → U+0027 before the contraction regex, so `"what’s"` now expands identically to `"what's"`. This intentionally supersedes Phase 1's D-7 "round-trips-unchanged" behavior; the Phase 1 unit test was updated. +- **Reserved-key-only validation.** A `normalizer_pipeline` under any non-`query_normalizer` key would be sampled+persisted but never applied by the adapter (silent no-op), so `validate_normalizer_reservation` raises `NormalizerPipelineMisplacedError` → `INVALID_SEARCH_SPACE` (D-8 — rides the existing code, no new one). The check discriminates on the `type` string (not `isinstance`) to keep the module cycle-free. +- **Adapter hook generalization.** Both `ElasticAdapter.render` + `SolrAdapter.render` resolve `query_normalizer` (bundle string OR powerset label) via `steps_for_label` → `normalize_pipeline`, closing the highest-risk gap (the loop sampling a non-bundle label the bundle-only `normalize()` couldn't apply). +- **Bilingual PR snippet (FR-4/FR-5).** The proposal PR body's "Operator-side requirement" section emits a `### Python` AND a `### JavaScript / TypeScript` reference `normalizeQuery`, generated from the winning label's steps (not a fixed 4-key dict — so any powerset label renders). Three-way OUTPUT parity (runtime ≡ Python-exec ≡ JS-via-Node) is locked by a committed `ui/src/__tests__/fixtures/normalizer_snippet_parity.json` corpus: a backend freshness test (`test_normalizers_pr_snippets_js.py`) keeps the fixture in sync with `build_js_snippet` + `normalize_pipeline`, and a vitest spec (`normalizer-snippet-parity.test.ts`) runs each JS snippet via `new Function` and asserts it equals the runtime golden (Q-2 — each runtime in its own suite). +- **Frontend.** `` (ordered 6-step checkbox multi-select, options `.map()`-sourced from `NORMALIZER_STEP_VALUES` in STEP_ORDER, glossary-labeled, empty-steps incomplete helper paralleling the categorical `__placeholder__` sentinel); `NORMALIZER_STEP_VALUES`/`NORMALIZER_STEP_GLOSSARY_KEYS` + 7 glossary keys; the `ParamSpec` discriminated union gains the 4th member end-to-end (TS `ParamSpec`, `estimateParamCardinality` `2^N`, row-type-selector, stash default, cardinality detail, discriminator parity test); the digest analyzer-redundancy advisory broadened from four-bundle membership to "the `+`-split label includes the `lowercase` token" (AC-13). + +**Cycle break.** `normalizers.py` and `search_space.py` previously had `normalizers → search_space` only; Story 1.2 needed `search_space → normalizers` (for `NormalizerStep` + `_pipeline_labels`). Resolved by moving `normalizers.py`'s `search_space` import to a function-local back-edge inside `validate_normalizer_reservation` (annotations are lazy via `from __future__ import annotations`), making `normalizers.py` the leaf for the pure pipeline logic. + +**Gates / decisions.** The plan shipped design-ahead (gated on Phase 1 merge + Q-1/Q-2). At execution: Phase 1 was confirmed merged (Story 0 precondition re-verified), Q-1 locked to **include `expand_contractions_custom` as inert (6 steps)** (operator decision — lower-churn, the plan was already written against 6), Q-2 locked to **frontend vitest fixture** (engineering default). Documented deviation: the plan's "reproduce Phase 1's 4 snippets byte-for-byte" is superseded by FR-3 (the generated expand snippet now includes smart-quote handling), so the generator is the single source and the guarantee is three-way output parity, not byte-identity to the pre-FR-3 text. + +**Review / CI.** GPT-5.5 unreachable in the Claude Code remote sandbox → Opus self-review. Gemini Code Assist posted 2 Medium findings — both accepted (`7047190`): the generated `strip_punctuation` snippets used a membership-filter instead of the runtime's regex, so both Python (`_PUNCTUATION_PATTERN.sub`) and JS (backslash-escaped char-class `PUNCTUATION_REGEX`) now mirror the runtime; output unchanged (parity re-verified, 110 JS↔runtime node checks). The full `mypy backend/` pre-push gate caught two downstream `ParamSpec` consumers (`search_space_defaults.estimate_param_cardinality` → `2**len(steps)`; `baseline_resolver._midpoint` → `"none"` label for a pipeline param) that the per-story per-file mypy missed — fixed inline with regression tests. The `verify_enum_source_of_truth` CI guard couldn't resolve the new `NormalizerStep` StrEnum citation; extended its helper to read member `.value` for any `enum.Enum` subclass (first StrEnum-backed enums.ts citation). Initial push failed on prettier (local `pnpm lint` runs eslint, not prettier) + the enum guard + one contract test (the overlap probe rejected the no-judgments seed — stubbed it, mirroring `test_studies_api.py`). Merged latest `main` (the SSRF guard) in before merge — 0 behind, no skew. 18 new test files (8 backend unit + 1 integration + 1 contract + 1 e2e + 4 vitest + the JS-parity fixture/spec); coverage 81.64%. All 19 `pr.yml` checks green (smoke skipped — opt-in/off). + +--- + ### `chore_overnight_result_card_screenshot` — tutorial Step 12 morning-result-card screenshot + image-ferry plumbing (PR #492, 2026-06-07) **What shipped.** The morning result-card prose landed in `feat_overnight_final_solution_phase2` (PR #442), but the screenshot deliverable for FR-9 didn't — the standard demo seed can't produce a terminated `follow_suggestions` chain with a winning proposal + digest, which the phase-2 plan's hard-fallback escape hatch explicitly authorized as a follow-up chore. This PR closes that FR-9 gap with three things: (1) the 34 KB PNG `docs/08_guides/images/12-overnight-result-card.png`, captured at viewport 1440×960 against a deterministically-seeded local chain (anchor + follow-up, winning proposal + digest narrative, `selected_followup_kind='narrow'`, +0.0450 lift — the card renders headline, Explored path, Best config link, Stop reason, and the digest-narrative excerpt); (2) the Step 12 `![Overnight result card](images/12-overnight-result-card.png)` reference in `docs/08_guides/tutorial-first-study.md`; (3) the first-ever **tutorial-image ferry plumbing**. **Docs/test/script only — no code-path/API/migration change** (Alembic head stays `0023`). diff --git a/website/docs/roadmap.md b/website/docs/roadmap.md index 35f3e829..92b8d50d 100644 --- a/website/docs/roadmap.md +++ b/website/docs/roadmap.md @@ -187,6 +187,7 @@ - ✅ [Overnight Studies Summary Card](https://github.com/SoundMindsAI/relyloop/tree/main/docs/00_overview/implemented_features/2026_06_04_feat_overnight_studies_summary_card) · [#444](https://github.com/SoundMindsAI/relyloop/pull/444) - ✅ [Proposal Full Param Space View](https://github.com/SoundMindsAI/relyloop/tree/main/docs/00_overview/implemented_features/2026_06_04_feat_proposal_full_param_space_view) · [#446](https://github.com/SoundMindsAI/relyloop/pull/446) - ✅ [Query Normalization Tuning](https://github.com/SoundMindsAI/relyloop/tree/main/docs/00_overview/implemented_features/2026_06_05_feat_query_normalization_tuning) · [#459](https://github.com/SoundMindsAI/relyloop/pull/459) +- ✅ [Query Normalizer Typed Pipeline](https://github.com/SoundMindsAI/relyloop/tree/main/docs/00_overview/implemented_features/2026_06_09_feat_query_normalizer_typed_pipeline) · [#509](https://github.com/SoundMindsAI/relyloop/pull/509) - ✅ [Studies Convergence Visibility](https://github.com/SoundMindsAI/relyloop/tree/main/docs/00_overview/implemented_features/2026_06_02_feat_studies_convergence_visibility) · [#421](https://github.com/SoundMindsAI/relyloop/pull/421) - ✅ [Study Convergence Indicator](https://github.com/SoundMindsAI/relyloop/tree/main/docs/00_overview/implemented_features/2026_06_01_feat_study_convergence_indicator) · [#352](https://github.com/SoundMindsAI/relyloop/pull/352) - ✅ [Study Sub Warmup Guard](https://github.com/SoundMindsAI/relyloop/tree/main/docs/00_overview/implemented_features/2026_05_29_feat_study_sub_warmup_guard) · [#316](https://github.com/SoundMindsAI/relyloop/pull/316) @@ -195,7 +196,6 @@ - ✅ [UBI LLM Study Comparison](https://github.com/SoundMindsAI/relyloop/tree/main/docs/00_overview/implemented_features/2026_06_05_feat_ubi_llm_study_comparison) · [#461](https://github.com/SoundMindsAI/relyloop/pull/461) - ✅ [Website Walkthrough Guides](https://github.com/SoundMindsAI/relyloop/tree/main/docs/00_overview/implemented_features/2026_06_04_feat_website_walkthrough_guides) · [#448](https://github.com/SoundMindsAI/relyloop/pull/448) - 🟡 [Apply Path Normalizer Declaration](https://github.com/SoundMindsAI/relyloop/tree/main/docs/00_overview/planned_features/02_mvp2/feat_apply_path_normalizer_declaration) -- 🟡 [Query Normalizer Typed Pipeline](https://github.com/SoundMindsAI/relyloop/tree/main/docs/00_overview/planned_features/02_mvp2/feat_query_normalizer_typed_pipeline) ??? note "Infrastructure & tooling (8)"