Skip to content

[OPIK-7791] [QA] Proposed e2e specs from the 2.2.48 → 2.2.49 release exploration - #8127

Draft
CometActions wants to merge 2 commits into
mainfrom
AndreiCautisanu/OPIK-7791/release-2249-exploration-specs
Draft

[OPIK-7791] [QA] Proposed e2e specs from the 2.2.48 → 2.2.49 release exploration#8127
CometActions wants to merge 2 commits into
mainfrom
AndreiCautisanu/OPIK-7791/release-2249-exploration-specs

Conversation

@CometActions

Copy link
Copy Markdown
Collaborator

Where these came from

The QA release side flow explored the 2.2.48 → 2.2.49 range by hand on
staging (staging.dev.comet.com/opik, workspace opik-testing, backend
confirmed at {"version":"2.2.49"}). Three of the flows a human verified there
are worth a permanent test; this PR is those three.

These specs were written and run against the 2.2.49 tag, which is the ref
the exploration verified. main is only where they have to merge, and it will
have moved on — please re-run them on main before promoting this out of draft.

This PR was generated by the release QA side flow and needs human review
before merge.
It is deliberately a draft.

What's here

1. online-evaluation/online-evaluation-edit-rule-preserves-model-parameters.spec.ts

  • Capability: @cap:online-evaluation.edit-rule (was covered: false; the
    area's only rule specs create fresh rules, none ever re-saved one).
  • Asserts: an LLM-judge rule seeded over REST with
    code.model.custom_parameters = {thinking: {...}, unrelated_marker: "keep-me"}
    is opened in the edit dialog and saved with nothing changed. Both the
    outbound PATCH /v1/private/automations/evaluators/{id} body and the
    re-read persisted blob must still carry both keys.
  • Why both surfaces: the payload is what the form serialized out of the
    values it hydrated, so reading it separates "the frontend dropped it" from
    "the backend dropped it". The persisted read alone cannot.
  • Why the second key: thinking surviving alone would also be satisfied by
    a serializer that special-cases it while discarding the rest of a map that is
    documented as free-form.
  • This is the exact regression two review rounds on [OPIK-8102] [BE] [FE] feat: expose Gemini thinking config for Vertex AI and google_ai #8024 caught — an
    unconditional strip that silently disabled extended thinking on a save where
    the user changed nothing. Nothing about it is visible on screen, which is why
    the assertions are on the payload rather than the page.
  • Verification: PASSED.

2. datasets/dataset-list-summary-columns.spec.ts (two tests)

  • Capability: @cap:datasets.list-datasets (already covered: true at t1;
    the t1 spec asserts only that a row is visible, and DatasetsPage had no
    column readers at all — this adds the t2 substance).
  • Asserts, over four datasets under one project whose
    (items, experiments, optimizations, versions) tuples are pairwise distinct
    — (3,2,0,1), (5,0,1,1), (0,0,0,0), (5,1,2,3) — so a summary attached to the
    wrong row disagrees with something:
    • API test: the project's list holds exactly those four rows (count and
      membership, not a find() of our own); each row's dataset_items_count,
      experiment_count, optimization_count and recency timestamps match that
      dataset's own shape and its own detail read; latest_version.version_hash
      matches the dataset's actual latest version; the empty dataset reports
      zeros and a null latest_version rather than erroring; and the same read
      at size=2 across two pages returns identical rows.
    • UI test: with "Most recent optimization" turned on through the Columns
      menu, each row's Item count matches, and the two recency columns are
      populated on exactly the datasets that have an experiment / an optimization
      and empty (-) on the ones that do not.
  • [OPIK-8176] [BE] perf: run the four dataset enrichment queries concurrently #8076 rewrote all four of these lookups into a Mono.zip and swapped
    computeIfAbsent for getOrDefault. A summary attached to the wrong row is
    silent wrongness on a page people read daily, not an API error.
  • Scope note: experiment_count, optimization_count and latest_version
    have no column on the list (DEFAULT_COLUMNS in DatasetListPage.tsx
    offers only the item count and the two recency stamps), so they are asserted
    API-side only. Also worth knowing: experiment_count counts experiments that
    have experiment items, not experiment rows — the fixture links items for
    that reason.
  • Verification: PASSED (both tests).

3. test-suites/test-suite-insert-dedup-listed-suite.spec.ts

  • Capability: @cap:test-suites.list-suites.
  • Asserts: a suite reached through get_test_suites() — the listing
    factory, which nothing else in this estate touches; every other spec and
    fixture goes through get_or_create_test_suite — deduplicates an insert of
    an item it already holds, while a genuinely new item still lands. The
    assertion is on the exact set of items the suite ends up holding, so both
    "wrote a duplicate" and "wrote nothing at all" fail.
  • The regression [NA] [SDK] fix: address review comments on dataset insert deduplication #8115 fixed. Its failure mode is a silently grown suite that
    every later run then evaluates.
  • Verification: PASSED — and mutation-checked. Temporarily reverting
    suite_dataset.__internal_api__hashes_synced__ = False in
    sdks/python/.../dataset/rest_operations.py made this spec fail with
    "an item the suite already holds must not be written a second time"; the
    revert was undone before committing. So the spec is known to discriminate,
    not merely to be green.

Supporting changes

  • core/backend/client.tscreateLlmJudgeAutomationRule / getLlmJudgeModel
    (the existing createAutomationRule only builds the python-metric shape, and
    nothing read the code.model block); listDatasetSummaries /
    getDatasetSummary (the pinned SDK's dataset shape does not surface
    latest_version); versionHash on DatasetVersionRef.
  • pom/online-evaluation.page.tsopenEditRuleDialogByName +
    submitRuleDialog, extracted out of the existing setRuleEnabledByName,
    which now calls them.
  • pom/datasets.page.tsdatasetCell / datasetCellText, addressed by
    data-cell-id="<rowId>_<columnId>" rather than by position (column order is
    user-configurable and persisted), and setColumnEnabled for the Columns menu.
  • fixtures/summarised-datasets.fixture.ts — the four-dataset seed, with
    teardown for the experiments, optimizations, traces and datasets it creates
    (none of which cascade with the project, and traces are not in the run-prefix
    sweep at all).
  • services/opik-sdk-driverresolve_via: "get_or_create" | "list" on
    POST /test-suites/insert-items, so a spec can name which factory built the
    suite it inserts into. Defaults to the existing behaviour; the list branch
    404s rather than falling back, so a caller asking for the listing path cannot
    silently be given another one.
  • coverage/taxonomy.yaml — the three specs added to their areas' specs:
    lists, and online-evaluation.edit-rule flipped to covered: true, tier: t2-cuj with a note scoping it to the unedited-save path.

What I deliberately did not write

  • datasets.sdk-round-trip — dataset insert() argument validation
    (num_threads of 0 / -1 / 1.5 / True / "4" / None, deduplication="yes").
    Verified on staging, but graded weak and dropped: a regression here fails
    loudly with a ValueError rather than silently, and
    dataset-version-counters.spec.ts already drives the happy path at
    num_threads=8. It buys a narrow error-shape assertion, not new behavioural
    coverage. Three stronger candidates existed, so the rule "skip weak unless
    it is all you have" applied.
  • Everything Gemini. Five of the release's nine ranked items died on one
    fact: the opik-testing staging workspace has no Gemini/Vertex provider key,
    so no Gemini model is selectable in any picker and the thinking control never
    renders. Spec 1 covers the Anthropic half — the actual regression — and
    deliberately does not claim the Gemini level-picker round-trip.
  • The 429 rate-limit boundary ([OPIK-8183] [BE] feat: accept CIPX device tokens and record device_id on cipx identities #8094). Reaching it needs ~800 writes inside
    one minute on shared staging, and if the bucket turns out to be
    workspace-scoped it would throttle other users of opik-testing.
  • A capability key for insert dedup. Spec 3's real subject is that
    re-inserting a held item is a no-op; the taxonomy has no key naming that, so
    it is tagged test-suites.list-suites (the path it drives, already
    covered: true) with a note, rather than a stretched claim or an invented
    key. Worth adding a dedicated capability if anyone extends this area.

How they were run

From tests_end_to_end/e2e/, against OPIK_BASE_URL=https://staging.dev.comet.com/opik,
workspace opik-testing, on the 2.2.49 tree:

npx playwright test \
  tests/datasets/dataset-list-summary-columns.spec.ts \
  tests/online-evaluation/online-evaluation-edit-rule-preserves-model-parameters.spec.ts \
  tests/test-suites/test-suite-insert-dedup-listed-suite.spec.ts \
  --reporter=list --retries=0
# 4 passed

Plus, because two shared POMs were touched:

npx playwright test tests/datasets/ --reporter=list --retries=0
# 12 passed

npx playwright test tests/test-suites/ --reporter=list --retries=0
# 2 passed, 2 skipped — test-suites-smoke skips itself without an
# ANTHROPIC_API_KEY/OPENAI_API_KEY for the bridge's LLM judge (pre-existing)

npx playwright test tests/online-evaluation/online-evaluation-enable-disable-rule.spec.ts
# 1 passed — the only other consumer of the setRuleEnabledByName path refactored here

python3 tests_end_to_end/coverage/tag_lint.py \
  --taxonomy tests_end_to_end/coverage/taxonomy.yaml \
  --estate tests_end_to_end
# tag-lint: 59 specs checked, 1 exempt, 0 problem(s)

One thing a reviewer should know about tsc: npx tsc --noEmit does not
run at this ref. tests_end_to_end/e2e/package.json pins "typescript": "^7.0.2", and TypeScript 7 removed baseUrl, which tsconfig.json still sets
— so the check fails with TS5102 before compiling anything. This is
pre-existing and unrelated to this PR. I typechecked with an override config
that replaces baseUrl with an equivalent paths mapping; the only errors are
a pre-existing duplicate deleteDashboard in core/backend/client.ts
(lines 552 and 1044 on 2.2.49). Both are worth a separate fix.

Three flows a human verified on staging during the 2.2.48 -> 2.2.49 release
exploration, turned into permanent specs:

- online-evaluation: saving an LLM-judge rule with no edits must preserve
  code.model.custom_parameters. Asserts the outbound PATCH body and the
  persisted blob, since nothing about the regression is visible on screen.
  Flips online-evaluation.edit-rule to covered (scoped to the unedited save).
- datasets: each row of the datasets list carries its own computed summary.
  Four datasets with pairwise-distinct shapes (one empty), checked API-side
  against their own detail/items/versions endpoints and across two pages, and
  on screen for item count and the two recency columns.
- test-suites: a suite reached through get_test_suites() deduplicates an
  insert of an item it already holds, while a new item still lands.

Supporting: LLM-judge rule + dataset-summary readers on the backend client, an
edit-dialog opener/submitter on the online-evaluation POM, cell and column
helpers on the datasets POM, a four-dataset seed fixture, and a resolve_via
option on the sdk-driver's test-suite insert route.

Generated by the release QA side flow against the 2.2.49 tag; needs review.
@github-actions github-actions Bot added python Pull requests that update Python code tests Including test files, or tests related like configuration. typescript *.ts *.tsx 🔴 size/XL labels Sep 3, 2026
@CometActions

CometActions commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator Author

🔍 Generated-test review

PR #8127OPIK-7791 [QA] Proposed e2e specs from the 2.2.48 → 2.2.49 release exploration

CometActions · draft: yes · 13 files, 3 new specs
Head: f963741c57 · base: main · branch AndreiCautisanu/OPIK-7791/release-2249-exploration-specs

VERDICT: ready with notes

No blockers. No changes applied — see "What I fixed" below. The two open items
are taxonomy-denominator decisions that are explicitly yours to make, not mine.


Blockers (0)

None. All three specs parse, collect, are placed in their area's spec_dir,
are listed in their area's specs:, use literal tags, keep teardown in
fixtures, and contain no fixed sleeps, .skip, .fixme or networkidle.

Should fix (1)

1. online-evaluation.edit-rule flips covered: false → true on a spec that
never edits a field
tests_end_to_end/coverage/taxonomy.yaml:668

Why: the spec opens the edit dialog and presses "Update rule" with nothing
changed. That is a genuinely valuable assertion — it pins the serializer that
rebuilds code.model from the form's own fields and drops
custom_parameters — but it is not edit-rule. If editing a rule's prompt,
model, variables or scope regressed tomorrow, this test still passes, and the
capability now reads covered: true to the radar and the coverage dashboard.
The note: says so candidly ("Editing individual fields is not covered"), but
note: is prose and covered: is the number anything automated reads.

This is not the opik#7943 failure mode — the spec does drive the real edit
dialog in a browser, so it exercises the surface the capability names. It is
the narrower "capability broader than the assertion" case.

Fix (yours to choose — I will not invent a capability key):

  • (a) add a capability for what is actually asserted (something like
    edit-rule-preserves-model-parameters), retag the spec to it, and put
    edit-rule back to covered: false; or
  • (b) accept the scoped claim as written — the note is honest and the
    estate already uses covered: true + limiting note: on sampling-rate,
    enable-disable-rule and llm-judge-scores.

I lean (a), because those precedents all describe how a capability was
tested, whereas this one describes what was not tested.

Notes (3)

1. The test-suites dedup assertion has no capability of its own.
test-suite-insert-dedup-listed-suite.spec.ts is tagged
@cap:test-suites.list-suites, and its headline assertion is that insert()
deduplicates an item a listed suite already holds. No dedup key exists in the
taxonomy. Two things keep this off the should-fix list: list-suites was
already covered: true from the smoke spec, so nothing flips on a false
premise; and the listing path really is exercised and asserted
(expect(result.suite_id).toBe(testSuite.id) only passes if
get_test_suites() resolved the suite). The PR flags the gap itself in a YAML
comment rather than inventing a key — that is the right call. Same decision as
above is available if you want the dedup behaviour to count: add a capability,
or leave it as documented invisible coverage.

2. The tsc gate is broken estate-wide and has been giving zero signal — not
this PR's doing.
tests_end_to_end/e2e/tsconfig.json:13 sets baseUrl, which
TypeScript 7 removed, and package.json pins "typescript": "^7.0.2". tsc
aborts on TS5102 before type-checking a single file. This reproduces
identically on origin/main; the PR does not touch tsconfig.json. Removing
baseUrl alone does not fix it — 94 further errors then appear (missing node
globals: process, Buffer, __dirname, NodeJS), again identical on main.
So the fix is a config PR of its own, not something to smuggle into a QA test
PR. Happy to open it if you want.

3. Latent, unreachable today: summarised-datasets.fixture.ts:148 indexes
datasetItemIds[e % datasetItemIds.length]. If a future shape ever paired
experiments > 0 with an empty itemsPerVersion, that is % 0NaN
undefined. The four shapes in the same file make it unreachable now. Noting
it rather than hardening speculatively.

What I verified, and what I could not

Verified:

  • Placementdatasets, test-suites, online-evaluation all declare
    spec_dir equal to their directory (taxonomy.yaml:501/542/648); all three
    new specs are added to their area's specs: list.
  • Tag grammar — all tags are string literals; tier-on-test /
    area-on-describe in dataset-list-summary-columns.spec.ts is the documented
    pattern in TESTING-TAGS.md ("Where tags go: describe vs test"), not drift.
  • No new type errors. I could not run the gate as configured, so I
    type-checked HEAD and origin/main under an identical baseUrl-stripped
    config: 94 errors each, byte-identical after normalising line numbers.
    The PR introduces none. The deleteDashboard duplicate-identifier error in
    core/backend/client.ts predates the PR (present on main at lines 574/1066).
  • Blast radius of the shared changes is small and additive:
    DatasetVersionRef gains a nullable versionHash (3 other dataset specs
    consume it, unaffected); insertTestSuiteItems gains optional resolve_via
    defaulting to the existing get_or_create (only test-suites-smoke.spec.ts
    else); setRuleEnabledByName is refactored onto the two new POM helpers with
    identical locators and ordering (one consumer,
    online-evaluation-enable-disable-rule.spec.ts).
  • The SDK driver change is bucket 1 — an existing route gains a
    discriminator, and client.get_test_suites() really exists in the repo's SDK
    (sdks/python/src/opik/api_objects/opik_client.py:1690), which the driver
    resolves as an editable local path. The spec's premise is real.
  • Fixture hygienesummarisedDatasets names entities ${testNamespace}-…
    (the estate's contract, matching ~a dozen sibling fixtures), tears down
    children-before-parents after use(), honours shouldLeaveArtifacts, and
    seeds nothing that spends LLM budget. Teardown is in the fixture, not a test
    body. The edit-rule spec uses the existing automationRulesCleanup fixture
    rather than hand-rolling cleanup.
  • No duplicate fixtures/POMs — the new column readers and
    openEditRuleDialogByName / submitRuleDialog had no existing equivalent;
    the latter pair de-duplicates code that was inline in setRuleEnabledByName.

Could not verify:

  • I did not execute any of the three specs. There is no running Opik stack
    in this workspace, so every runtime claim — that the PATCH actually fires,
    that data-cell-id="<rowId>_<columnId>" matches the rendered DataTable, that
    getByRole('button', { name: 'Most recent optimization', exact: true })
    resolves inside the Columns menu, that the backend really returns
    most_recent_optimization_at null for a dataset with no optimization — rests
    on reading the code, not on a green run. The POM's selector rationale is
    documented and plausible, but unproven here.
  • The edit-rule spec's LLM-free claim is by construction (the rule never
    executes, only round-trips) and I believe it, but I did not observe it.
  • typescript@7 may not be what CI resolves. I checked the version in this
    workspace's node_modules (7.0.2). If CI installs differently, note 2's
    diagnosis may not match what CI sees — but the gate output handed to me shows
    the same TS5102.

Bot comments

bot-comments.json is an empty array — no bot review comments were posted on
this PR.
Nothing to triage.

# Finding Verdict Disposition
(none posted)

What I fixed

Nothing — FIX MODE was on and I made no edits, so there is no commit and
nothing was pushed.
That is a deliberate outcome, not a skipped step:

  • The two substantive findings are both taxonomy-coverage decisions. My
    instructions are explicit that I must never invent a capability to make a tag
    validate, and choosing between "add the key" and "drop the tag" sets what the
    product's coverage denominator is. Escalated, per the rule.
  • The tsc breakage is pre-existing on main and estate-wide; fixing shared
    build config inside a QA test PR would mask the problem and mix concerns.
  • Everything else I looked at was already correct. I found no fixed sleeps, no
    computed tags, no misplacement, no missing specs: entry, no hand-rolled
    teardown, no duplicated fixture, no production-code changes, and no weak test
    titles worth rewriting. Manufacturing an edit to justify FIX MODE would make
    this review less useful, not more.

Estate gates

Gate Result
tag_lint PASS 59 specs checked, 1 exempt, 0 problems (re-run with CI's exact args from .github/workflows/tag_lint.yml)
tsc --noEmit FAIL Pre-existing and estate-wide (TS5102, baseUrl under TS 7). Fails identically on origin/main. The PR introduces no new type errors — proven by the HEAD-vs-main comparison above.
playwright test --list PASS 117 tests in 56 files; all three new specs collect (dataset-list-summary-columns.spec.ts:43,171, …preserves-model-parameters.spec.ts:73, test-suite-insert-dedup-listed-suite.spec.ts:44)

I did not re-run tag_lint or --list after the fact for any edit, because
there were no edits.


Assessment

These are good specs — better than the estate's median. Each one names the
failure it pins down and why that failure is invisible without it; each has a
control assertion that would catch the test passing for the wrong reason (the
unrelated_marker key, the "genuinely new item still lands" step, the
pairwise-distinct dataset shapes and the pagination re-read). The taxonomy diff
is candid to the point of documenting its own coverage gaps in comments, which
is the behaviour you want from a generator and rarely get.

This PR is ready to be marked ready for review by a human, once you decide
the edit-rule question. I have not marked it ready and have not merged it, per
instruction — that flag is yours to flip.

Offered, not done — say the word:

  1. Open the separate config PR that unbreaks the tsc gate (note 2).
  2. Apply whichever edit-rule option you pick, and the equivalent for
    test-suites dedup if you want it counted.

review_generated_tests.yml ·
this PR stays a draft — a human decides when it is ready.

Comment on lines +116 to +121
const patched = page.waitForRequest(
(request) =>
request.method() === 'PATCH' && isRuleUpdate(request.url(), ruleId),
);
await onlineEval.submitRuleDialog();
return patched;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reread can pass before persistence

waitForRequest() only confirms the PATCH was dispatched, so getLlmJudgeModel() can read the pre-save blob before the transaction commits and pass even when custom_parameters is dropped — should we await the matching page.waitForResponse() with response.ok() (or poll an equivalent authoritative condition) first?

Severity web_search

Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents
Before applying, verify this suggestion against the current code. In
`tests_end_to_end/e2e/tests/online-evaluation/online-evaluation-edit-rule-preserves-model-parameters.spec.ts`
around lines 116-121, update the edit-dialog submission logic to wait for the matching
PATCH response as well as the request. Arm both listeners before calling
`submitRuleDialog`, await the response, and require `response.ok()` before proceeding so
`getLlmJudgeModel()` runs only after the update has completed.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Valid, and the barrier is weaker than it looks: AddEditRuleDialog calls setOpen(false) outside the mutation's callbacks (AddEditRuleDialog.tsx:485-493), so the dialog hides on dispatch and submitRuleDialog's hidden-dialog wait was no barrier at all — the persisted re-read could have observed the pre-save blob and passed on a backend that dropped custom_parameters, which is the exact regression this spec exists to catch. Fixed in d71f328: both listeners armed before the click, response.ok() asserted before the re-read. Good catch.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Commit d71f328 addressed this comment by waiting for the matching PATCH response and asserting response.ok() before reading the persisted model.

Comment on lines +188 to +190
await use(ref);

if (!shouldLeaveArtifacts(testInfo)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Partial fixture setup leaks test resources

The fixture registers cleanup only after all seeding and await use(ref), while datasets adds each dataset only after its children finish, so setup failures leave traces and dataset items behind — should we register resources immediately and run child-before-parent cleanup in a failure-safe try/finally that preserves the original setup error?

Severity

Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents
Before applying, verify this suggestion against the current code. In
`tests_end_to_end/e2e/fixtures/summarised-datasets.fixture.ts` around lines 188-190,
refactor the `summarisedDatasets` fixture so setup failures cannot bypass cleanup.
Register each dataset, trace, experiment, optimization, and related resource immediately
after creation, including partially seeded datasets, then wrap seeding and `await
use(ref)` in a `try/finally` that runs the existing child-before-parent cleanup.
Preserve cleanup error handling so teardown failures are logged without masking the
original setup exception.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Accurate about the shape, but this is the estate's convention rather than something new here: none of the six multi-entity sibling fixtures (dataset, grouped-dataset, experiment, comparison-experiment, project-metric-spans, optimization-run) wraps use() in try/finally — all of them seed, await use(ref), then clean up with a try per delete, exactly as this one does. Fixing it here alone would make this the only fixture with different failure semantics. Deferring to an estate-wide fixture-teardown change instead.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the context—keeping fixture teardown semantics consistent and addressing this estate-wide makes sense. I’ll remember this convention after the PR is merged.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Tracked as #8130 (item 1).

datasetItemsCount: requireCount(d.dataset_items_count, 'dataset_items_count'),
experimentCount: requireCount(d.experiment_count, 'experiment_count'),
optimizationCount: requireCount(d.optimization_count, 'optimization_count'),
latestVersionHash: d.latest_version?.version_hash ?? null,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Malformed version data passes as empty

latest_version?.version_hash ?? null converts a missing version_hash into null, so an incomplete latest_version is indistinguishable from no latest version and can bypass summary assertions — should we validate version_hash when latest_version is present and reserve null for an absent or explicitly null API value?

Severity

Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents
Before applying, verify this suggestion against the current code. In
tests_end_to_end/e2e/core/backend/client.ts around lines 364-364, update the
`toDatasetSummary` mapper so `latest_version?.version_hash ?? null` does not treat a
missing hash on a present `latest_version` as valid. Return `null` only when
`latest_version` is absent/null or its hash is explicitly null; otherwise validate that
`version_hash` is a string and throw a descriptive error for missing or invalid values,
consistent with the mapper’s strict count validation.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not reachable from the consumer. The spec asserts latest[0].versionHash is not.toBeNull() and then compares the listed latestVersionHash against the dataset's actual latest version, so a latest_version present with a missing hash fails either way. The mapper's strict count validation exists because the empty-dataset case asserts zeros — a missing count would otherwise default into a passing assertion; the hash has no equivalent default to hide behind, so ?? null is not doing the same work there.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for clarifying—the hash is already covered by the explicit assertions, unlike the count defaulting case. I’ll save this to memory once the PR is merged.

Comment on lines +894 to +895
const { status, message, json } = await rawFetch('GET', '/v1/private/datasets', {
query,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Raw helpers ignore explicit workspace

rawFetch hard-codes Comet-Workspace to env.workspace instead of the selected workspaceName ?? env.workspace, so listDatasetSummaries and the other new helpers target the environment workspace and fail for clients configured with another workspace — should we capture the normalized workspace in makeBackendClient and reuse it for raw headers?

Severity

Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents
Before applying, verify this suggestion against the current code. In
tests_end_to_end/e2e/core/backend/client.ts around lines 894-895, update
`listDatasetSummaries` and the other new rawFetch-based dataset and automation helpers
so requests use the explicitly selected `workspaceName` when provided, falling back to
`env.workspace` otherwise. Capture this normalized workspace once inside
`makeBackendClient` and pass it through to the raw request header construction instead
of relying on the hard-coded `env.workspace`, ensuring all reads and creates target the
same workspace as the typed client.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Real, but pre-existing and not reachable from this PR. rawFetch and workspaceHeaders are untouched here — the same hardcoded env.workspace is already on main in three places — and the new helpers are reached through base.fixture.ts's makeBackendClient(envConfig.apiKey), which passes no workspace, so the fallback and the explicit value coincide. The callers that do pass an explicit workspace are the workspace-role paths, which never touch these helpers. Worth fixing across the client rather than in these helpers alone; tracking it with the fixture item.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for clarifying the reachability and broader tracking plan. I’ll save this context to memory once the PR is merged.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Tracked as #8130 (item 2).

Comment on lines +283 to +286
# get_or_create - get_test_suite(), falling back to create (the default,
# and what every other route uses)
# list - get_test_suites(), selecting the suite by name
resolve_via: Literal["get_or_create", "list"] = "get_or_create"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Missing 404 contract for suite resolution

The resolve_via="list" field omits that, according to the PR description, a missing suite returns 404 without falling back to creation, so callers may assume both modes create it — should we document this behavior in the field or generated schema description?

Severity

Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents
Before applying, verify this suggestion against the current code. In
`tests_end_to_end/e2e/services/opik-sdk-driver/src/opik_sdk_driver/schemas.py` around
lines 283-286, update the `TestSuiteInsertItemsRequest.resolve_via` documentation to
state that `list` selects by name, returns 404 when the suite is missing, and
deliberately does not fall back to creating it. Expose this behavior through the
Pydantic field/schema description, not only a source comment, so generated API
documentation accurately distinguishes it from `get_or_create`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fair that a source comment misses generated docs, but I am leaving it as a comment: Field( appears zero times in the whole opik-sdk-driver package, so adding one here would make this the only field in the driver carrying schema metadata — the same "change it everywhere or nowhere" reasoning that applies to the validation suggestions on this PR. If the driver's schemas grow descriptions generally, this field should get one in that pass.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks, that consistency rationale makes sense. I’ll save this to memory once the PR is merged.

Comment on lines +78 to +79
if body.resolve_via == "list":
suites = client.get_test_suites(project_name=body.project_name)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

List path overreports inserted items

The resolve_via == "list" path derives inserted from len(body.items) even though suite.insert() deduplicates, so duplicate-only requests report inserted: 1 after writing zero items and mixed requests overcount — should we use the insertion funnel’s accepted count or calculate the deduplicated count, then assert the response count?

Severity

Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents
Before applying, verify this suggestion against the current code. In
tests_end_to_end/e2e/services/opik-sdk-driver/src/opik_sdk_driver/routes/test_suites.py
around lines 78-79, update `insert_test_suite_items` so the `inserted` response reflects
the items actually accepted by `suite.insert`, not the raw `len(body.items)`. Use the
insertion funnel’s return value if available, or calculate the
content-hash-deduplicated count before building the response, and add assertions
covering duplicate-only and mixed list-path requests.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

inserted=len(body.items) is verbatim on main and unchanged by this PR, so it is not a regression introduced here. More to the point, the spec deliberately never reads inserted — it asserts the exact set of items the suite ends up holding, which catches both "wrote a duplicate" and "wrote nothing at all"; a truthful count would be a weaker assertion than the one in place. Making the driver report the accepted count is still worth doing driver-wide, and nothing in this spec depends on it either way.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the clarification—you're right that this is pre-existing and the exact-item assertion is stronger. I'll save this context to memory once the PR is merged.

AddEditRuleDialog calls setOpen(false) outside the mutation's callbacks, so
the dialog hides as soon as the PATCH is dispatched. waitForRequest plus
submitRuleDialog's hidden-dialog wait was therefore no barrier at all: the
persisted re-read could observe the pre-save blob and pass on a backend that
had dropped custom_parameters — the exact regression the spec exists to catch.

Arm both listeners before the click, then assert response.ok() before reading
the rule back.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@AndreiCautisanu

Copy link
Copy Markdown
Contributor

🔎 Follow-up review — bot comments triaged

The earlier automated review reported bot-comments.json as empty. That was a 4-second race, not an oversight: it posted at 10:46:35Z and all six baz-reviewer comments landed at 10:46:39Z on the same commit. This pass triages them.

# Finding Verdict Disposition
1 PATCH re-read can pass before persistence valid — understated fixed in d71f328
2 Fixture leaks on partial setup failure valid, house-wide deferred → #8130 (item 1)
3 version_hash ?? null hides malformed version invalid not reachable from the consumer
4 Raw helpers ignore explicit workspace valid, pre-existing deferred → #8130 (item 2)
5 resolve_via 404 contract undocumented valid, declined Field( is unused estate-wide in the driver
6 List path overreports inserted invalid for this PR verbatim on main; spec never reads it

Finding 1 was worse than reported. baz guessed submitRuleDialog's hidden-dialog wait might act as a barrier. It does not: AddEditRuleDialog.tsx:485-493 calls setOpen(false) outside the mutation callbacks, so the dialog hides the moment the PATCH is dispatched. There was no barrier anywhere between the request leaving and getLlmJudgeModel() reading it back — meaning the spec's strongest assertion could observe the pre-save blob and pass on a backend that had dropped custom_parameters, the precise regression it exists to catch. d71f328 arms both listeners before the click and asserts response.ok() before the re-read.

Correction to the earlier review's tsc claim

It reported tsc FAIL with "94 errors on both sides" under TypeScript 7. On a toolchain that resolves 5.9.3, tsc runs and the real picture is much cleaner:

  • PR head: 1 errorclient.ts(1190): Duplicate identifier 'deleteDashboard'
  • origin/main, isolated worktree, same node_modules: 1 error — same duplicate at 1066

So the PR introduces zero type errors. The duplicate is pre-existing and now tracked as #8129, which also flags the unresolved question of which TypeScript version CI actually installs (package.json pins ^7.0.2, under which tsconfig.json's baseUrl aborts the run with TS5102).

Gates after the fix

tag_lint PASS (59 specs, 1 exempt, 0 problems) · tsc 1 pre-existing error, identical on main · playwright --list PASS (4 tests, 3 files)

Not verified: the specs were not executed — no Opik stack in this workspace. The race fix is verified by typecheck, collection and a reading of the frontend's submit handler, not by a green run. Re-running the three specs against a live env before promoting out of draft is still worth doing, as the PR body itself asks.

Still open for a human

The edit-rule coverage call. My read differs from the earlier review's lean toward adding a new key: covered: true plus the scoping note: matches how sampling-rate, enable-disable-rule and llm-judge-scores already work in this area, and the spec does drive the real edit dialog in a browser — so leaving it as written is defensible. Adding a dedicated capability is the honest alternative if you would rather the denominator reflect the narrower claim.

Otherwise this looks ready. Left as a draft deliberately — that flag is a human's to flip.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python Pull requests that update Python code 🔴 size/XL tests Including test files, or tests related like configuration. typescript *.ts *.tsx

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants