[2a86d1f5] CI-watch: fix the CI regression on roboco-api (#563)

* [e530aa5e] Diagnose and fix roboco-api CI failure (run 29629255153) (#561) (#562)

* [e530aa5e] fix(tests): narrow None before indexing validate_init_data() result in telegram_initdata self-check

CI run 29629255153 failed on mypy, not the historical pydantic-settings
issue (uv.lock already pins 2.14.2). The __main__ self-check block in
test_telegram_initdata.py indexed the dict[str, object] | None return
of validate_init_data() without narrowing away None first.

* [e530aa5e] docs(qa): document CI fix for mypy type narrowing in telegram_initdata test

Explains the root cause (mypy type error in __main__ block), the solution (None narrowing before indexing), and the safe pattern for future test self-checks that call functions returning optional types.

---------

Co-authored-by: Backend Developer 1 <be-dev-1@roboco.tech>
Co-authored-by: Backend Documenter <be-doc@roboco.tech>

* [0884b737] Diagnose and fix Python quality gate + e2e lifecycle smoke CI failures on PR #563 (#564) (#565)

* [0884b737] fix(tests): isolate ROBOCO_SDK_URL for scripted e2e-smoke agents

tests/e2e_smoke/harness.py already isolates ROBOCO_AGENT_TOKEN from the
host environment (the #503/#504 fix) but left ROBOCO_SDK_URL leaking
through. flow_server/do_server both default it to
http://localhost:9000 and forward every rejection there for the
per-verb circuit breaker; inside a real spawned agent container that
port is a live SDK loopback, so the breaker records genuine attempts
for the ephemeral test-agent IDs and trips circuit_open mid-test
(test_sandbox_on_demand.py::test_request_sandbox_guard_chain_over_real_api,
which deliberately causes 3 rejections in a row). Point it at a
guaranteed-refused loopback address so every environment gets the same
fail-open bypass a bare CI runner already gets by having nothing
listening on 9000 at all.

* [0884b737] docs(changelog): document e2e-smoke harness ROBOCO_SDK_URL isolation fix

Document the fix that isolates ROBOCO_SDK_URL in the ScriptedAgent harness to prevent the per-verb circuit breaker from leaking state into ephemeral test-agent identities when the e2e-smoke suite runs inside a live agent container. This ensures the suite passes consistently regardless of whether it runs on bare CI or inside a spawned agent.

---------

Co-authored-by: Backend Developer 1 <be-dev-1@roboco.tech>
Co-authored-by: Backend Documenter <be-doc@roboco.tech>

* [3b9a1771] Diagnose and fix ALL make quality + e2e-smoke stage failures on PR #563; confirm real CI green (round 3) (#566) (#567)

* [3b9a1771] fix(e2e-smoke): match real embedding dimension when seeding fake journal chunk

test_c3_deleted_journal_unindexed inserted a 4-dim placeholder vector
into chunks_journals, but the e2e stack's app lifespan eagerly creates
that table with the real settings.embedding_dimensions (1024) before
the test runs, so the insert failed with "expected 1024 dimensions,
not 4". Derive _SMOKE_DIM from settings.embedding_dimensions instead
of a hardcoded constant so the seeded vector always matches the
table's actual column width.

* [3b9a1771] docs(qa): document e2e-smoke embedding dimension fix in round 3 CI diagnosis

Recorded the root cause, solution, and pattern for the final e2e-smoke test failure found in comprehensive sandbox testing: the test seeded a 4-dim placeholder vector but the app's eager lifespan init created chunks_journals with the real 1024-dim embedding column. Updated _SMOKE_DIM to derive from settings.embedding_dimensions instead of a hardcoded constant.

---------

Co-authored-by: Backend Developer 1 <be-dev-1@roboco.tech>
Co-authored-by: Backend Documenter <be-doc@roboco.tech>

---------

Co-authored-by: Backend Developer 1 <be-dev-1@roboco.tech>
Co-authored-by: Backend Documenter <be-doc@roboco.tech>
This commit is contained in:
Renzo F
2026-07-18 16:13:47 +02:00
committed by Renn F
co-authored by Backend Developer 1 Backend Documenter
parent 06cc986f06
commit ec6558e168
6 changed files with 104 additions and 2 deletions
+1
View File
@@ -36,6 +36,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
### Fixed
- **E2E smoke test harness now isolates `ROBOCO_SDK_URL` for scripted-agent environments.** The per-verb circuit breaker in flow_server and do_server POSTs every rejection to ROBOCO_SDK_URL (default http://localhost:9000) for tracking and tripping circuit_open on repeated failures. When the e2e-smoke suite runs inside a live agent container, that loopback port is the host agent's own SDK server — so the breaker records genuine attempts against ephemeral test-agent IDs and can trip mid-test even for deliberately-rejected test calls. The harness now sets `ROBOCO_SDK_URL = "http://127.0.0.1:1"` alongside its existing `ROBOCO_AGENT_TOKEN` isolation, pointing it at a guaranteed-refused loopback so every environment sees the same fail-open bypass that CI already gets (no SDK server listening). This fixes `test_request_sandbox_guard_chain_over_real_api` and ensures the e2e-smoke suite passes when run inside a spawned agent container.
- **Dispatcher no longer burns a spawn on a sequence-held task.** The dev dispatch path booted a full container for a pending task the assignee-blind sequence guard would immediately refuse at claim — spawn, refuse, exit, respawn, forever — because the dispatcher's own prefilter only checked declared dependencies and the dev's own lane, not sequence siblings. It now reuses the exact claim-gate predicate before spawning, mirroring the PM path.
- **Active tab and sidebar-footer highlighting.** An invalid or typo'd `?tab=`/`?view=` query param silently fell through as an out-of-set value and blanked the active tab highlight and its content pane; a new `pickTab` helper validates against the known set and falls back to the default. The sidebar footer never had an active-state branch at all — it now highlights on an exact path match (not prefix, so `/settings` doesn't also light up on `/settings/ai-providers`).
- **Release-proposal reject no longer deadlocks the readiness loop, and a failed approve surfaces why.** Rejecting a proposal left it PENDING rather than CANCELLED, so the one-open-proposal dedup blocked every future assessment cycle; reject now moves it to CANCELLED. A failed background execute (red gate, red CI, or an unhandled exception) used to leave the proposal silently pending with no signal; the approve route now stamps an execute-outcome marker on every terminal path so the panel can show a running/failed state and a retry instead of a silent wait.
@@ -0,0 +1,55 @@
# CI Fix: E2E Smoke Test Embedding Dimension Mismatch
**Round 3 — Resolved**
## Root Cause
The e2e smoke test `test_data_integrity.py::test_c3_deleted_journal_unindexed` was seeding a 4-dimensional placeholder vector into `chunks_journals`, but the e2e stack's app lifespan eagerly initializes every OptimalService plugin (including JOURNALS) at startup, which creates that table with the **real configured embedding dimension** (1024, from `settings.embedding_dimensions`) before the test ever runs. The insert failed with:
```
asyncpg.exceptions.DataError: expected 1024 dimensions, not 4
```
The test's own comment ("the table is created fresh per run") was stale: the app doesn't create it fresh on each test, it eagerly creates it once at startup.
## Solution Applied
Changed `_SMOKE_DIM` from a hardcoded constant to derive from the real runtime configuration:
```python
# Before
_SMOKE_DIM = 4 # tiny embedding dim — the table is created fresh per run
# After
_SMOKE_DIM = settings.embedding_dimensions
```
Now the seeded placeholder vector always matches the table's actual column width, regardless of the configured embedding dimension.
## Impact
- **Scope:** Test infrastructure only (e2e smoke test)
- **Risk:** Minimal — single constant reference change in the test module
- **Behavior:** No change to the code under test; the e2e module itself is unaffected
- **Verification:** Provisioned real postgres+redis sandbox matching `.github/workflows/e2e-smoke.yml`, ran full `make e2e-smoke` suite (51 tests), all passed
## Pattern
For future e2e smoke tests that seed placeholder data into a schema initialized at app startup, source placeholder dimensions from the runtime settings, not hardcoded constants:
```python
from roboco.config import settings
# Correct: placeholder always matches runtime schema
placeholder_dim = settings.embedding_dimensions
```
Do not assume the schema is created fresh per test — eager initialization hooks may create it once at app startup, before any individual test runs.
## Session Summary
Round 3 comprehensively diagnosed every failing CI stage:
- All 14 `make quality` stages were already clean
- The sole real failure was this e2e-smoke embedding-dimension bug
- All code-level failures from rounds 12 (mypy regression, SDK URL isolation) were already fixed
- This final fix clears both the Python quality gate and e2e lifecycle smoke checks for PR #566
@@ -0,0 +1,28 @@
# CI Fix: Mypy Type Narrowing in Telegram InitData Test
**Run 29629255153 — Resolved**
## Root Cause
CI run 29629255153 failed on the `mypy` quality gate, not the historical pydantic-settings issue. The test file's `__main__` self-check block at `tests/unit/utils/test_telegram_initdata.py:122` was indexing the return value of `validate_init_data()` (which returns `dict[str, object] | None`) without first narrowing away the `None` type.
## Solution Applied
Narrowed the `None` possibility before indexing:
- Assign the result to a variable
- Assert it is not `None`
- Then index the dict
This mirrors the existing safe-indexing pattern at line 41 of the same test file.
## Impact
- **Scope:** Test infrastructure only (no production code modified)
- **Risk:** Minimal — single-line change in a self-check block
- **Behavior:** No change to the module under test
- **Verification:** Local `make quality` run confirmed full pass (mypy clean, 13,383 tests passed, 94.46% coverage)
## Pattern
For future test self-check blocks that call functions returning `T | None`, always narrow before indexing:
```python
result = some_function_returning_optional()
assert result is not None
# now safe to access result["key"]
```
+11
View File
@@ -483,6 +483,17 @@ class ScriptedAgent:
# test agent IDs and causes 401s. Drop it so tests run in the same
# unsigned-token mode as CI.
os.environ.pop("ROBOCO_AGENT_TOKEN", None)
# Same leakage class for the per-verb circuit breaker: flow_server /
# do_server post rejections to ROBOCO_SDK_URL (default
# http://localhost:9000), which is this repo's own live agent SDK
# loopback port when the suite happens to run inside a real spawned
# agent container. That breaker then records genuine attempts for
# the ephemeral test-agent IDs and can trip circuit_open mid-test
# (surfaced by test_request_sandbox_guard_chain_over_real_api's 3
# deliberately-rejected calls). Point it at a guaranteed-refused
# loopback port so every environment sees the same fail-open
# bypass CI gets (no SDK process listening at all).
os.environ["ROBOCO_SDK_URL"] = "http://127.0.0.1:1"
module = importlib.import_module(name)
if getattr(module, "AGENT_ID", None) != str(self.agent_id):
module = importlib.reload(module)
+6 -1
View File
@@ -54,7 +54,12 @@ from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
if TYPE_CHECKING:
from tests.e2e_smoke.harness import E2EStack
_SMOKE_DIM = 4 # tiny embedding dim — the table is created fresh per run
# The e2e stack's app lifespan eagerly initializes every OptimalService
# plugin (including JOURNALS) at startup, which creates ``chunks_journals``
# with the REAL configured embedding dimension before this test ever runs —
# so seeding a fake chunk must match that dimension, not a fabricated small
# one, or the insert fails with "expected N dimensions, not _SMOKE_DIM".
_SMOKE_DIM = settings.embedding_dimensions
_EXPECTED_H12_ROWS = 2 # first (be-pm) + second (be-pm, main-pm) both persist
+3 -1
View File
@@ -119,6 +119,8 @@ if __name__ == "__main__":
# ponytail: smallest runnable self-check; pytest is the real suite above.
user = {"id": 42}
fields = {"auth_date": str(int(time.time())), "user": json.dumps(user)}
assert validate_init_data(_init_data(fields), _BOT_TOKEN, 600)["user"] == user
valid_result = validate_init_data(_init_data(fields), _BOT_TOKEN, 600)
assert valid_result is not None
assert valid_result["user"] == user
assert validate_init_data(_init_data(fields), "wrong", 600) is None
print("telegram_initdata self-check OK")