Skip to content

UN-3636 [MISC] enable the unit-workers rig group and de-flake its suite#2176

Merged
chandrasekharan-zipstack merged 6 commits into
mainfrom
feat/un-3636-enable-unit-workers
Jul 17, 2026
Merged

UN-3636 [MISC] enable the unit-workers rig group and de-flake its suite#2176
chandrasekharan-zipstack merged 6 commits into
mainfrom
feat/un-3636-enable-unit-workers

Conversation

@chandrasekharan-zipstack

Copy link
Copy Markdown
Contributor

What

The unit-workers rig group filtered markers: "unit", but no worker test carries that marker — so it collected zero tests and silently never ran in CI. This switches it to the negative filter the other unit groups use, so all 734 worker tests actually run and gate.

Enabling collection surfaced pre-existing debt (the suite was never CI-gated). All fixed here — no product code changed.

Fixes

Test isolation (parallel/xdist pollution) — in workers/tests/conftest.py:

  • Worker Celery apps build a live-Postgres result backend from ambient DB_* / CELERY_BACKEND_DB_*; stripped before any app import so eager results stay in-memory.
  • Building a Celery app hijacks current_app; pin celery's finalized default app as current around each test so @worker_task proxies resolve (was NotRegistered / psycopg2 errors, order-dependent).

Stale assertions (prod drifted, tests never caught it since the suite wasn't running):

  • ExecutorToolShim gained execution_id/organization_id/file_execution_id.
  • _run_agentic_extraction() gained required execution_id.
  • Index result now carries usage_records from embedding.flush_pending_usage().
  • Highlight-disabled test over-asserted "plugin loader untouched" — lookup-enrichment is queried unconditionally and returns None in OSS; narrowed to assert the highlight plugin specifically.

Vanity removal — dropped tautological *_enum_exists tests that just mirror the source Operation enum (kept the routing/registration/removed-guard tests).

Renamestest_sanity_phase6* / test_phaseN* → intent-revealing names (git mv, history preserved).

Verification

tox -e groups -- unit-workers734 passed, stable across repeated parallel runs. tox -e rig -- validate OK. Lint clean under the repo's pinned ruff/ruff-format.

🤖 Generated with Claude Code

The unit-workers group filtered `markers: "unit"`, but no worker test carries
that marker, so it collected zero tests and silently never ran in CI. Switch to
the negative filter the other unit groups use so the 734 worker tests actually
run.

Enabling collection surfaced pre-existing debt (the suite was never CI-gated):

- Test isolation: worker Celery apps build a live-Postgres result backend from
  ambient DB_*/CELERY_BACKEND_DB_*, and building an app hijacks `current_app`.
  Both leaked across tests under xdist (psycopg2 connection errors, task
  `NotRegistered`). Fixed in conftest: strip the DB env before any app import,
  and pin celery's finalized default app as current around each test.
- Stale assertions (prod drifted, tests never caught it): ExecutorToolShim /
  _run_agentic_extraction gained args; index result now carries usage_records;
  the highlight-disabled test over-asserted "plugin loader untouched" when
  lookup-enrichment is queried unconditionally (returns None in OSS).
- Dropped tautological enum-existence tests that just mirror the source enum.
- Renamed the cryptic test_sanity_phase6* / test_phaseN* files to
  intent-revealing names.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019EN8hh518CbCVMP3BHTVmG
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e15ab2e7-1839-4d1e-948a-73d5fa563f17

📥 Commits

Reviewing files that changed from the base of the PR and between f66b9db and 2a1acc6.

📒 Files selected for processing (2)
  • tests/rig/cli.py
  • tests/rig/tests/test_cli.py

Summary by CodeRabbit

  • Tests
    • Improved worker test isolation by loading test env vars and ensuring Celery worker tests don’t connect to real DB-backed result backends.
    • Expanded agentic operation, highlight-data, and log streaming expectations for clearer end-to-end executor behavior.
    • Refined plugin migration and legacy operation coverage, including removing redundant enum-presence checks.
    • Added CLI regression coverage for non-optional test groups that collect zero tests, plus updated test selection rules to better separate unit vs integration/slow runs.
  • Documentation
    • Standardized and updated test module headers/descriptions and various assertion formatting for readability.

Walkthrough

Worker test selection and Celery isolation were updated. Rig handling now treats empty default pytest groups as failures, while worker executor, plugin, pipeline, routing, logging, and challenge/evaluation tests were clarified or reformatted without production-code changes.

Changes

Worker test configuration and execution safeguards

Layer / File(s) Summary
Test selection, environment isolation, and rig exit handling
tests/groups.yaml, workers/conftest.py, workers/tests/conftest.py, tests/rig/cli.py, tests/rig/tests/test_cli.py
Worker selection excludes integration and slow tests; Celery test setup removes database backend variables and restores the current app; default non-optional pytest groups collecting no tests now fail the rig, with regression coverage for marker overrides.

Executor, plugin, and operation coverage

Layer / File(s) Summary
Executor and plugin regression coverage
workers/tests/test_*operation.py, workers/tests/test_plugin_migration_regression.py, workers/tests/test_completion_and_highlight.py, workers/tests/test_table_lineitem_challenge_eval.py
Agentic, SPS, table, smart-table, highlight, migration, challenge, and evaluation tests were narrowed or updated to assert routing and plugin behavior through current execution paths.

Pipeline and dispatch test normalization

Layer / File(s) Summary
Pipeline, dispatch, and logging tests
workers/tests/test_structure_pipeline.py, workers/tests/test_structure_tool_pipeline.py, workers/tests/test_extraction_pipeline_contracts.py, workers/tests/test_dispatch_with_callback.py, workers/tests/test_log_streaming.py, workers/tests/test_prompt_studio_dispatch.py, workers/tests/test_single_pass_extraction.py, workers/tests/test_summarize_operation.py, workers/tests/test_variable_replacement_postprocessor.py
Mock setup, context construction, logging expectations, dispatch helpers, module descriptions, and formatting were updated while preserving the existing assertions except for explicit shim arguments and JSON-safe embedding usage.

Estimated code review effort: 3 (Moderate) | ~25 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers What/Fixes/Verification, but it misses most required template sections, including the breakage, migrations, env, related issues, and checklist fields. Add the missing template sections, especially Why, How, breakage impact, Database Migrations, Env Config, Related Issues, Notes on Testing, and the Checklist.
Docstring Coverage ⚠️ Warning Docstring coverage is 63.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: enabling the unit-workers rig group and stabilizing its suite.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/un-3636-enable-unit-workers

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR enables the worker unit-test group and updates the previously ungated suite. The main changes are:

  • Select worker unit tests with negative integration and slow markers.
  • Fail non-optional pytest groups that collect no tests.
  • Isolate worker tests from ambient database-backed Celery results.
  • Restore Celery's default app around each worker test.
  • Update and rename worker tests to match current production contracts.

Confidence Score: 5/5

This looks safe to merge.

  • The worker group now collects its intended tests.
  • Empty non-optional pytest groups fail instead of passing silently.
  • No blocking issue tied to an earlier review finding remains in the updated code.

Important Files Changed

Filename Overview
tests/groups.yaml Enables worker test collection with the unit tier's negative marker pattern.
tests/rig/cli.py Makes empty non-optional pytest groups fail unless marker or path overrides are supplied.
tests/rig/tests/test_cli.py Covers the empty-group failure behavior and the marker-override exception.
workers/tests/conftest.py Removes database backend variables and restores the finalized default Celery app around worker tests.
workers/tests/test_agentic_operations.py Updates agentic extraction tests for the required execution identifier.
workers/tests/test_extraction_pipeline_contracts.py Updates indexing mocks to return serializable pending usage records.
workers/tests/test_completion_and_highlight.py Narrows the disabled-highlight assertion to the highlight plugin lookup.

Reviews (6): Last reviewed commit: "Merge branch 'main' into feat/un-3636-en..." | Re-trigger Greptile

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
workers/tests/test_completion_and_highlight.py (1)

68-71: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Redundant dictionary check for call_kwargs.

In Python's unittest.mock._Call object, call_kwargs.kwargs and call_kwargs[1] refer to the exact same dictionary of keyword arguments. If the intent was to check whether the callback was passed as a positional argument instead, you would need to inspect call_kwargs.args (or call_kwargs[0]).

If process_text is always expected to be passed as a keyword argument, you can simplify this assertion by removing the redundant check.

♻️ Proposed refactor
-        assert (
-            call_kwargs.kwargs.get("process_text") is callback
-            or call_kwargs[1].get("process_text") is callback
-        )
+        assert call_kwargs.kwargs.get("process_text") is callback
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@workers/tests/test_completion_and_highlight.py` around lines 68 - 71, Update
the assertion around call_kwargs to inspect only call_kwargs.kwargs for the
process_text callback, removing the redundant call_kwargs[1] check since
process_text is expected as a keyword argument.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@workers/tests/test_completion_and_highlight.py`:
- Around line 68-71: Update the assertion around call_kwargs to inspect only
call_kwargs.kwargs for the process_text callback, removing the redundant
call_kwargs[1] check since process_text is expected as a keyword argument.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3bf16fee-70b3-4f7f-97e8-3d275d20f635

📥 Commits

Reviewing files that changed from the base of the PR and between 0786a0a and 503c177.

📒 Files selected for processing (19)
  • tests/groups.yaml
  • workers/tests/conftest.py
  • workers/tests/test_agentic_operations.py
  • workers/tests/test_completion_and_highlight.py
  • workers/tests/test_dispatch_with_callback.py
  • workers/tests/test_extraction_pipeline_contracts.py
  • workers/tests/test_log_streaming.py
  • workers/tests/test_plugin_loader.py
  • workers/tests/test_plugin_migration_regression.py
  • workers/tests/test_prompt_studio_dispatch.py
  • workers/tests/test_simple_prompt_studio_operation.py
  • workers/tests/test_single_pass_extraction.py
  • workers/tests/test_smart_table_extract_operation.py
  • workers/tests/test_structure_pipeline.py
  • workers/tests/test_structure_tool_pipeline.py
  • workers/tests/test_summarize_operation.py
  • workers/tests/test_table_extract_operation.py
  • workers/tests/test_table_lineitem_challenge_eval.py
  • workers/tests/test_variable_replacement_postprocessor.py

@chandrasekharan-zipstack chandrasekharan-zipstack changed the title UN-3636 [FIX] enable the unit-workers rig group and de-flake its suite UN-3636 [MISC] enable the unit-workers rig group and de-flake its suite Jul 14, 2026
@athul-rs
athul-rs self-requested a review July 14, 2026 12:38
Drop stale-prone specifics (function names, file:line refs, vendor
lists, class names) from groups.yaml comments; keep the WHY.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019EN8hh518CbCVMP3BHTVmG

@athul-rs athul-rs left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed the marker fix, the conftest isolation work, the stale-assertion fixes, and the deletions/renames. Net: the direction is right and this is a strict improvement — enabling 734 previously-dead tests is a clear win, and both conftest mechanisms (env strip + current_app pinning) are correct as written.

I ran an adversarial pass over my own findings before posting, and three didn't survive. Recording them so nobody re-litigates:

  • The *_enum_exists deletions are fine. I first read them as the only pin on sdk1's Operation values — they aren't. test_plugin_migration_regression.py:131-176 already pins the whole set bidirectionally (test_every_operation_is_mapped, test_no_extra_mappings, len(Operation) == 19). Genuine dedup, and sdk1 is an in-repo editable path dep, not a versioned external provider. Agreed with the call.
  • shared/tests is not missed by the env strip. os.environ.pop() is process-global and both paths run in one pytest process with conftests loaded before any test executes. Only the autouse current_app fixture doesn't reach shared/tests, and nothing there touches celery — latent, zero impact.
  • The env strip is the right mechanism, not a workaround — it's the only thing that handles an ambient exported DB_HOST and the only thing covering CELERY_BACKEND_DB_*. See the conftest note.

Four comments left. Two I think are worth acting on:

  1. The rig still scores "collected zero tests" as a pass for non-optional groups. This PR fixes the instance, not the class — the next marker typo goes green the same way.
  2. The log_component replica tests are the real tautologies — and one is currently green while asserting the opposite of what production does.

Plus two nits. Nothing blocking; CI is green.

Comment thread tests/groups.yaml
Comment thread tests/groups.yaml Outdated
Comment thread workers/tests/test_plugin_migration_regression.py Outdated
Comment thread workers/tests/test_completion_and_highlight.py
Comment thread workers/tests/conftest.py
chandrasekharan-zipstack and others added 2 commits July 14, 2026 18:58
…tions

- Replace the log_component replica (copied tasks.py if/elif, drifted so the
  ide_index case asserted the opposite of production) with tests that drive the
  real execute_extraction, covering the two special-cased branches.
- Match the highlight-plugin assertion on positional or kwarg calls so it can't
  pass vacuously if the call style changes.
- Reword the unit-workers marker note: integration/slow worker tests are
  currently unrouted, not run "elsewhere".
- Cross-reference the two workers conftests on the DB_* env they set vs strip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019EN8hh518CbCVMP3BHTVmG
…thing

A zero-collect group returns pytest exit 5, which the rig treats as
non-failing — so a broken marker or moved path silently reported green
(the very way unit-workers stayed dormant). Gate on it: a non-optional
pytest group that collects zero tests now fails the overall exit.

Legit zero-collects are exempt: optional groups, hurl (its exit 5 means
"no files"), and dev runs with a --marker/--paths override.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019EN8hh518CbCVMP3BHTVmG
@athul-rs
athul-rs requested a review from jaseemjaskp July 15, 2026 05:25
@sonarqubecloud

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown
Contributor

Unstract test results

Per-group results

Status Group Tier Passed Failed Errors Skipped Duration (s)
e2e-login e2e 2 0 0 0 1.4
e2e-smoke e2e 2 0 0 0 1.0
integration-backend integration 74 0 0 27 44.7
integration-connectors integration 1 0 0 7 8.4
unit-backend unit 126 0 0 0 19.7
unit-connectors unit 63 0 0 0 9.5
unit-core unit 27 0 0 0 1.2
unit-platform-service unit 15 0 0 0 2.5
unit-rig unit 71 0 0 0 4.0
unit-sdk1 unit 435 0 0 0 26.2
unit-workers unit 723 0 0 0 45.3
TOTAL 1539 0 0 34 164.0

Critical paths

⚠️ Critical paths not yet covered

  • workflow-create-execute — Create a workflow, configure source+destination, execute, poll, fetch result. (declared coverage: e2e-workflow)
  • api-deployment-run — Deploy a workflow as an API, POST a document, receive structured JSON. (declared coverage: e2e-api-deployment)
  • prompt-studio-fetch-response — Prompt Studio: create project, add prompt, run single-pass, get response. (declared coverage: e2e-prompt-studio)
  • pipeline-etl-execute — Run an ETL pipeline from source connector to destination. (declared coverage: no groups declared)
  • usage-token-tracking — Per-execution token usage is recorded and retrievable. (declared coverage: no groups declared)
  • workflow-execution-fan-out — Multi-file workflow execution fans out to file-processing workers and rejoins. (declared coverage: no groups declared)
  • callback-result-delivery — Async results are posted back via the callback worker. (declared coverage: no groups declared)
✅ Covered critical paths
  • auth-login — covered by e2e-login
  • adapter-register-llm — covered by integration-backend
  • workflow-author — covered by integration-backend
  • api-deployment-provision — covered by integration-backend
  • api-deployment-auth — covered by integration-backend
  • prompt-studio-author — covered by integration-backend
  • connector-register-test — covered by integration-backend
  • usage-aggregate-read — covered by integration-backend

@chandrasekharan-zipstack
chandrasekharan-zipstack merged commit 202ebb9 into main Jul 17, 2026
11 checks passed
@chandrasekharan-zipstack
chandrasekharan-zipstack deleted the feat/un-3636-enable-unit-workers branch July 17, 2026 05:42
chandrasekharan-zipstack added a commit that referenced this pull request Jul 17, 2026
The comment block sat where #2176 reworded the e2e-login comment and inserted
a new group, creating a textual merge conflict against main. Move it inside
each group body; the optional: true removals merge cleanly on their own.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P4tU5vyB4G6BgfAnGqjxLs
chandrasekharan-zipstack added a commit that referenced this pull request Jul 21, 2026
…2170)

* UN-3636 [DEV] add hermetic LLM mock hook for execute-path tests

Execute-path critical paths (workflow-create-execute, usage-token-tracking,
etc.) need a deterministic LLM without real provider secrets. Add a single
sdk1 escape hatch: when UNSTRACT_LLM_MOCK_RESPONSE is set, inject litellm's
mock_response so completions return that string with fixed usage (10/20/30),
tunable via DEFAULT_MOCK_RESPONSE_* and error sentinels like
"litellm.RateLimitError". No-op in production (env unset).

Wire the env through worker-executor-v2 / worker-file-processing-v2 in the e2e
compose overlay so the mock reaches the process that runs the LLM.

Covered by unit tests pinning both our injector and the litellm mock contract
(string + deterministic usage + error sentinel) so a litellm bump can't
silently break hermetic execute-path coverage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017UVfw7aAocC3KCJaEyZ5GU

* UN-3636 [DEV] cover execute-path critical tests via mocked LLM

Add e2e tests that exercise the full execute path hermetically using the
UNSTRACT_LLM_MOCK_RESPONSE hook (no real LLM/secret):

- api-deployment-run + usage-token-tracking: deploy a workflow as an API,
  POST a doc synchronously, assert the mocked answer and fixed 10/20/30
  usage come back as structured JSON.
- workflow-create-execute: two-step /workflow/execute/ then poll to
  COMPLETED; COMPLETED + a successful file is the hermetic proof (without
  the mock the completion would fail).

A shared provisioned_workflow fixture stands up a Prompt Studio-backed API
workflow (NoOp x2text/vectordb, chunk_size=0 so embedding/vectordb are
skipped). The rig seeds a canonical mock string in ComposeRuntime so both
the workers and pytest see the same value; tests skip if it is unset.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017UVfw7aAocC3KCJaEyZ5GU

* UN-3636 [MISC] Fix lint, promote e2e groups, document LLM mock

- test_mock_response.py: split the module docstring summary line (D205 was
  failing pre-commit.ci, the only red hook on the PR).
- groups.yaml: drop `optional: true` from e2e-workflow / e2e-api-deployment.
  They now hold real execute-path tests rather than placeholders, so a red one
  should fail the build directly instead of only surfacing as a critical-path
  gap via --fail-on-critical-gap.
- README: the workflows/ and api_deployment/ dirs are no longer "(future)", and
  document UNSTRACT_LLM_MOCK_RESPONSE under E2E runtime — including that only
  completions are mocked (embedding.py is unhooked; chunk_size=0 is what keeps
  indexing off the real provider).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P4tU5vyB4G6BgfAnGqjxLs

* UN-3636 [MISC] Reposition groups.yaml comment to avoid main conflict

The comment block sat where #2176 reworded the e2e-login comment and inserted
a new group, creating a textual merge conflict against main. Move it inside
each group body; the optional: true removals merge cleanly on their own.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P4tU5vyB4G6BgfAnGqjxLs

* UN-3636 [MISC] Tighten comments to state why, not how

Drop the restated-critical-path and mechanism-narrating comments across the
LLM mock and the execute-path e2e tests; keep only the constraints the code
can't show (litellm's fixed usage, the mandatory filter arg, the two-call
execute contract). Also drop the groups.yaml 'not optional' comment: the field
is self-evident and would go stale.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P4tU5vyB4G6BgfAnGqjxLs

* UN-3636 [DEV] Warn on active LLM mock, skip groups whose deps failed

Three follow-ups from review:

- llm.py: warn once per process when UNSTRACT_LLM_MOCK_RESPONSE is active. The
  hatch was silent, so a stray env var in production would fake every
  completion and its billing with nothing in the logs.

- rig: implement the runtime-gate-skip TODO in cmd_run. Groups run in topo
  order, so a dep's result is known before its dependents start; a red dep
  means the precondition it gates does not hold and dependents would only
  produce noise against a half-up stack. Adds GroupManifest.transitive_deps and
  a 'blocked' GroupResult status which is never green, so a blocked group
  attests no coverage and --fail-on-critical-gap still catches a path that
  quietly stopped being covered. This mattered less while every platform
  dependent was optional; it does now that the execute-path groups gate.

- api-deployment e2e: the 10/20/30 usage assert now explains itself, since
  counts are summed per reason and a multiple means extra completions rather
  than broken tracking.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P4tU5vyB4G6BgfAnGqjxLs

* UN-3636 [DEV] Cover the last four OSS critical paths

Closes the remaining gaps in the registry; every path is now proof: marker.

- callback-result-delivery: async API execution (timeout=0). With a file to
  process, COMPLETED is written only by the chord callback, so a polled result
  proves the callback worker ran. Pins the one-shot read too — fetching a
  delivered result acknowledges it and the next read 406s, which a retry loop
  added later would otherwise swallow silently.

- workflow-execution-fan-out: multi-file execution, one batch per file. Needed
  MAX_PARALLEL_FILE_BATCHES on the *backend* (workers ask it for the value and
  only fall back to their own env); at the default of 1 the files share a batch
  and run serially, so the test would have passed proving nothing. File bodies
  differ because identical ones are deduplicated by hash on ingest.

- prompt-studio-fetch-response: the authoring surface. Async, so the answer is
  polled from prompt-output/ rather than the task status, which reports the
  executor finishing and not the callback having stored anything. Upload is
  content-sniffed and OSS takes PDF only, hence the real (if empty) PDF.

- pipeline-etl-execute: source connector to destination, over MinIO — the only
  storage connector the compose stack both boots and registers. Reuses the
  exported tool but needs its own workflow, since endpoints are per-workflow
  and the existing one is committed to the API deployment tests.

None of these have run: the e2e job is skipped while the PR is a draft. Locally
verified only as far as it goes here — rig validate, rig self-tests, ruff, and
pytest collection.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P4tU5vyB4G6BgfAnGqjxLs

* UN-3636 [FIX] make api-deployment e2e poll instead of blocking, install minio

The e2e tier failed on two counts:

- api-deployment run/fan-out asserted COMPLETED on the synchronous POST, but a
  file-bearing execution only reaches COMPLETED once the chord callback rejoins
  — the sync path reports EXECUTING under CI load. All three api-deployment
  tests now dispatch async (timeout=0) and poll the status endpoint via shared
  dispatch_async/poll_delivered helpers, tolerating transient poll timeouts
  (the async test's earlier ReadTimeout). execution_id is parsed from status_api
  since the async PENDING body omits it as a top-level field.

- e2e-etl skipped ("minio client not installed"), leaving pipeline-etl-execute a
  gap. Add minio to the rig's injected pytest deps so the test runs.

Verified against a full local stack: api_deployment (3), workflow, etl,
prompt_studio all green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017UVfw7aAocC3KCJaEyZ5GU

* UN-3636 [MISC] Drop verbose minio comment, make MinIO scheme env-driven

Clears the SonarCloud S5332 clear-text-HTTP finding on the test fixture
(compose-internal MinIO has no TLS) by sourcing the scheme from env, and
trims the low-value minio dependency comment.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017UVfw7aAocC3KCJaEyZ5GU

* UN-3636 [MISC] Fail fast when no workflow endpoint matches

Assert at least one endpoint was patched to API in the provisioning
fixture, so a field-name mismatch surfaces here instead of as a confusing
downstream execute failure (CodeRabbit).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017UVfw7aAocC3KCJaEyZ5GU

* UN-3636 [MISC] Trim compose overlay comments to WHY only

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017UVfw7aAocC3KCJaEyZ5GU

* UN-3636 [MISC] Cache e2e image build via type=gha (bake)

The e2e job runs on ephemeral GitHub-hosted runners, so a plain
`docker compose build` rebuilt every image cold each run (~3min). Switch
to docker/bake-action with per-service type=gha cache scopes matching the
release workflow, so each runner restores layers from the shared GHA cache
(including entries the release build already wrote). load=true exports the
images into the daemon for the compose stack to boot.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017UVfw7aAocC3KCJaEyZ5GU

* UN-3636 [MISC] Pin buildx/bake actions to commit SHA (Sonar S7637)

SonarCloud flagged the two floating action tags added for the e2e gha
cache as new MAJOR vulnerabilities (security rating C). Pin to the commit
SHA the tag points to so a moved tag can't inject code.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017UVfw7aAocC3KCJaEyZ5GU

* UN-3636 [DEV] Skip e2e on frontend-only and docs-only changes

e2e ran on every non-draft PR regardless of paths, so image-only or
docs-only changes triggered a full ~7min platform boot for nothing.

Add an `e2e_relevant` path scope that skips frontend/** and docs/**
while keeping docker/** in scope (image changes must still be
e2e-tested), gate the e2e job on it, and accept a skipped e2e as a
pass in the report gate (matching how a skipped test tier is handled).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L549habQ6K2m1ACJ7DwtsE

* UN-3636 [FIX] Fail CI gate when the changes job fails

`report` needs `changes` but never consulted its result. A failed filter job
(paths-filter error, checkout failure, runner death) skips `test`, which the
gate reads as a legitimate path-filter skip and reports success with no unit
or integration tests having run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017UVfw7aAocC3KCJaEyZ5GU

* UN-3636 [MISC] Address PR review feedback on rig and e2e tests

Rig:
- Blocked groups now persist a synthetic junit carrying `rig-blocked-by`, so
  the cascade survives `rig report`, which rebuilds results from junit alone.
  Without it a blocked group parsed as a zero-count pass and falsely attested
  critical-path coverage.
- GroupResult rejects a blocked row with non-zero counters.
- Report statuses are module constants instead of scattered literals.
- An exit-5 (no tests collected) group that others depend on now counts as
  failed, so dependents are blocked rather than run against nothing.
- The LLM mock default moves to cmd_run so it applies to every runtime.
- groups.py docstring no longer claims runtime gating is unimplemented.

E2E:
- Shared `wait_for_execution` helper replaces three near-identical polls; a
  CsrfSession subclass stamps X-CSRFToken so per-call helpers can't drift.
- api_deployment poll: sleeps on every iteration, captures exceptions as
  evidence, treats 406 as a lost-200 acknowledge rather than a poll assert,
  and surfaces non-422 statuses as themselves. Timeout drops to 150s so the
  group's three tests fit one 600s budget.
- MinIO scheme drives both the workers' URL and the client's TLS flag.
- Missing rig-provisioned prerequisites fail instead of silently skipping.

Core/SDK:
- `ExecutionStatus.terminal_statuses()` names the terminal set.
- Mock-response tests cover the empty-string no-op and assert every
  completion path still calls the injection hook.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017UVfw7aAocC3KCJaEyZ5GU

* UN-3636 [FIX] Keep frontend in e2e scope

The e2e image build is the only CI check that runs `bun install` and
`bun run build`; ci-frontend-lint only runs biome. Excluding frontend/**
from e2e_relevant let a broken build or a desynced bun.lock merge green
and surface on main via production-build.yaml.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017UVfw7aAocC3KCJaEyZ5GU

* UN-3636 [FEAT] Prove fan-out by timing, not by assuming it

The fan-out test asserted only the rejoin: every assertion held identically
for three files processed serially in one batch, while critical_paths.yaml
retired the workflow-execution-fan-out gap on its strength.

Nothing distinguishes a batch — the batch index is a discarded loop local and
the celery task id reaches only worker stdout — so assert the timing instead.
WorkflowFileExecution rows are created by the worker owning the batch rather
than up front by the dispatcher, so fanned out each row's window is one delay
wide, while serialised into one batch the windows open together and grow by a
delay per file. Comparing durations rather than overlap is the point:
serialised rows are created in one tight loop and overlap too.

That needs each completion to cost a known, measurable amount, so add
UNSTRACT_LLM_MOCK_DELAY alongside the existing mock hatch. It is inert without
UNSTRACT_LLM_MOCK_RESPONSE, since litellm ignores mock_delay on a real
completion, and a non-numeric value degrades to no delay rather than breaking
every completion in the worker.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017UVfw7aAocC3KCJaEyZ5GU

* UN-3636 [FIX] Close the two remaining review holes

Manual runs reported green with nothing tested. paths-filter has no base to
diff on workflow_dispatch, so it resolves against the default branch and a
manual run on main diffs main against itself: every filter false, every tier
skipped, gate green. Filtering is a PR-time saving, so force both scopes true
off-PR and skip the filter step entirely there.

Gate the LLM mock on ENVIRONMENT as well as the mock var, so a stray variable
in a real deployment can't fake completions and their billing. Production sets
neither, so this fails closed. `development` is allowed alongside `test`
because that is what base compose sets on the two workers that run the
injection -- proven by the mock working today with the var set on exactly
those two services -- and the overlay now pins ENVIRONMENT=test on them so the
tier can't lose its mock to a base-compose edit. A refusal warns rather than
passing silently: setting the var at all means someone expected mocking.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017UVfw7aAocC3KCJaEyZ5GU

* UN-3636 [FIX] Capture compose logs before the rig tears the stack down

The rig's `down -v` destroys the containers, so the workflow step that ran
`docker compose logs` afterwards always wrote an empty file — and overwrote
nothing useful only because there was nothing else to write. Capture inside
`ComposeRuntime.down()` while the containers still exist, and drop the step.

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

* UN-3636 [FIX] Give the fan-out timing check room above CI jitter

The bound was half a delay — a 1s window on per-file durations of ~12s, which
measures runner contention as much as batching. One delay is the midpoint
between the two outcomes the check separates (~0 spread fanned out, ~2 delays
serialised), and the delay itself has to be large enough for that midpoint to
sit above the noise.

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

* UN-3636 [MISC] Clear SonarCloud float equality and duplicate literal

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

* UN-3636 [FIX] Record fan-out as a gap instead of proving it by timing

The worker log settles what the test could not: `Arranging 3 files into 3
batches` — the fan-out happens. Yet the per-file durations it was judged by came
out 6.6 / 18.1 / 22.9. Three tool containers running at once on a CI runner cost
more in contention than they gain in overlap, so genuine fan-out lands further
apart than serialised files do, and no threshold separates the two. Raising the
delay does not help: the contention scales with the parallelism being measured.

So the proof is removed rather than retuned, and with it the mock delay that
existed only to feed it -- every mocked completion across the whole e2e tier was
stalling for it. `workflow-execution-fan-out` becomes a declared gap (visible in
the report, non-gating), and the test keeps asserting the rejoin, which is
directly observable. Closing the gap wants a batch id on WorkflowFileExecution,
which is execution observability worth having on its own.

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants