feat: AVA-73 add explicit skipped outcomes, UI styling, and example - #70
feat: AVA-73 add explicit skipped outcomes, UI styling, and example#70eriberdy022 wants to merge 11 commits into
Conversation
There was a problem hiding this comment.
do not commit this
There was a problem hiding this comment.
Removed in cc4c8dc. The unchanged capture is now a native GitHub attachment: https://github.com/user-attachments/assets/8d7ec381-b4f4-4599-815c-e7d110397f14 (also embedded in the PR body; original PNG bytes verified). Only the three requested PNGs were deleted; application source and history are unchanged. All four new-head CI jobs and fresh independent review passed. Leaving this thread open for your acceptance.
There was a problem hiding this comment.
do not commit this
There was a problem hiding this comment.
Removed in cc4c8dc. The unchanged capture is now a native GitHub attachment: https://github.com/user-attachments/assets/fe216904-0150-4bd4-82d8-63b6789d06e8 (also embedded in the PR body; original PNG bytes verified). Only the three requested PNGs were deleted; application source and history are unchanged. All four new-head CI jobs and fresh independent review passed. Leaving this thread open for your acceptance.
There was a problem hiding this comment.
do not commit this
There was a problem hiding this comment.
Removed in cc4c8dc. The unchanged capture is now a native GitHub attachment: https://github.com/user-attachments/assets/3f1c3522-53af-463c-8c2d-9e6100976532 (also embedded in the PR body; original PNG bytes verified). Only the three requested PNGs were deleted; application source and history are unchanged. All four new-head CI jobs and fresh independent review passed. Leaving this thread open for your acceptance.
|
Resolved by operator-authorized commit cc4c8dc: only the three requested repository PNGs were removed, without application changes or history rewrite. The unchanged captures are native GitHub attachments in the PR body; original bytes and rendered markup are verified. All four new-head CI jobs and fresh independent review passed. Replies link the correction and individual attachment in each review thread; none was auto-resolved. This PR is ready for human acceptance again. |
| return value.value if isinstance(value.value, Skipped) else None | ||
| if isinstance(value, Skipped): | ||
| return value | ||
| if isinstance(value, (tuple, list)) and value: |
There was a problem hiding this comment.
[P2] Distinguish container values from whole-node skips
_skip_outcome treats every nonempty tuple/list of equal skips as a whole-node absence, although the container can be the actual single-return value. For example, LocalExecutor().submit_with_status(lambda: [ava.skip("nested")]) reports a skipped status rather than successful None; the same classification reaches on_node_skipped for a workflow declared with a plain BaseContext subclass, whose wrapper does not add a LineagedResult. This contradicts the documented distinction between a container containing skips and a skipped node. Recognize expanded multi-return transport explicitly instead of inferring it from arbitrary container contents.
There was a problem hiding this comment.
Fixed in 389c138 (current head 46e68fa adds only import ordering). Whole-node expansion now carries an explicit internal marker; ordinary lists/tuples and equal returned slots remain successful values. Direct executor status and normal/custom-context hooks were independently exercised on Local and real Ray, including services and unwrap hooks, without driver payload materialization. Successful-container UI evidence: https://github.com/user-attachments/assets/f9cd08fd-97e9-492b-b179-21c0b86f6fb1 . All four current-head CI jobs pass. Leaving the thread open for your acceptance.
| if context.node_slug is not None: | ||
| lineage[context.node_slug] = context.run_id | ||
|
|
||
| if num_returns > 1 and isinstance(value, Skipped): |
There was a problem hiding this comment.
[P2] Normalize multi-return skips for custom BaseContext workflows
The expansion is confined to _wrap_lineaged_result, but supported @ava.workflow(context=MyContext) declarations with MyContext(ava.BaseContext) use _with_current_run_context's plain branch and never call this helper. A @ava.source(num_returns=2) returning ava.skip(...) therefore returns a bare Skipped: LocalExecutor rejects its return shape and Ray's status wrapper tries to unpack a non-iterable value. Intentional absence becomes a failed run merely because an application context is supplied. Normalize whole-node skip return shapes on both context paths, before executor return-count validation.
There was a problem hiding this comment.
Fixed in 389c138 (current head 46e68fa). Both RunContext and plain BaseContext paths normalize resolved sync/async whole-node skips before return-count validation. Indexed outputs, flattened fan-in, service receipts and unwrap hooks were independently exercised on Local and real Ray; custom contexts retain only their declared fields and malformed ordinary returns still fail. Custom-context authored skip evidence: https://github.com/user-attachments/assets/7e5b6ed4-b970-4b52-a184-479414a802e5 . All four current-head CI jobs pass. Leaving the thread open for your acceptance.
| empty_result = df.head(0) | ||
| skipped_slugs = skips_by_run.get(current_run_id, set()) | ||
| if "_ava_node_slug" in df.columns: | ||
| df = df.filter(~pl.col("_ava_node_slug").is_in(seen_slugs | skipped_slugs)) |
There was a problem hiding this comment.
[P2] Preserve null producer slugs when excluding skipped versions
The public run_with_context(RunContext(run_id="original", workflow_name="flow"), table.append, rows) can write rows with a valid run ID and a null _ava_node_slug, since node_slug defaults to None. Replaying that run through consume_stream(table, rerun=Rerun(run_id="original", start=["consumer"])) now silently discards those rows: is_in propagates null and filter rejects the null predicate, even without skip receipts. The pre-existing _overlay_rerun_frames explicitly preserves the null-slug producer group. Keep null slugs in this exclusion predicate so the new receipt handling does not remove ordinary replay data.
There was a problem hiding this comment.
Fixed in 389c138 (current head 46e68fa). The exclusion predicate explicitly retains null producer slugs. Independent Iceberg and Lance regressions retain ordinary null-slug rows both without receipts and across sparse rerun ancestry, while named skipped producer rows remain excluded. Physical original rows are unchanged; no fallback resurrection. Covered by test_null_slug_replay_survives_named_skips_and_sparse_ancestry. All four current-head CI jobs pass. Leaving the thread open for your acceptance.
Rationale
Optional work needs an explicit outcome instead of using
Nonefor both intentional absence and successful empty values. This implements AVA-73, including the human-approved addition of visible skipped-node styling and a simple runnable example.Summary
ava.skip(reason, metadata=None)andava.Skipped. An authored skip succeeds, satisfies dependency-only successors and existing&fan-in, and remains distinct from failure, cancellation, and successfulNone. Python value inputs receive the explicit marker; whole-node multi-return skips retain every output position. No gather API.SKIPPEDstate with reason, JSON metadata, and start/end timestamps. Extend spawned-worker events, snapshots/live updates, regenerated Python/TypeScript protobuf bindings, result bundles, and CLI result downloads.table.append(ava.skip(...))writes a run/producer metadata receipt in Iceberg or Lance without payload rows. Streams do not consume backlog or advance cursors for skipped input. Reruns do not resurrect that producer's ancestor rows; unrelated producers remain eligible. Validate receipt data before ancestry traversal.examples/skipped_node.py, document its CLI/browser flow, include it in the existing smoke inventory, and update API, execution-service, storage, rerun documentation, and Unreleased changelog. Add focused Local/Ray/storage/operator/UI regressions and adjust affected accessible selectors.Test Plan
Current review correction
46e68fa02c33100bf1508fe60999a60cfa92995f: Lint + tests, Browser UI, Ray tests, Tmux TUI tests (github-actions app 15368). The normal remote Ray command passed; current head is conflict-free against main1cb60ca154abe57fbf45144d7012712f0d357af9.make lint && make smoke-test— passed; eight smoke cases.uv run pytest test/skipped_outcome_test.py test/operator_tests/test_skipped_outcomes.py -m 'not ray' -q— 25 passed, 12 deselected.RAY_ENABLE_UV_RUN_RUNTIME_ENV=0 POLARS_MAX_THREADS=2 .venv/bin/python -m pytest test/skipped_outcome_test.py test/ray_materialization_contract_test.py -m ray -q— 16 passed, 19 deselected, real Ray. Initial local uv invocation exceeded 600 seconds; workaround is local-only and does not replace normal remote CI.uv run pytest test/workflow_execution_test.py::test_wrong_return_count_fails_before_downstream_can_lose_data test/execution_services_test.py::test_malformed_multi_return_aborts_before_finalize -q— 4 passed.uv run pytest test/storage/test_table_contracts.py -k concurrent_skip -q— 8 passed, 24 deselected.Earlier implementation checks (retained history)
make lint— passed.make smoke-test— 8 passed, including the new example and actual CLI/gRPC result download.make web-lint— passed.make web-format-check— passed for both browser packages.uv run pytest test/skipped_outcome_test.py test/operator_tests/test_skipped_outcomes.py test/rerun_test.py test/tui_test.py -m 'not ray' -q— 33 passed, 8 deselected.uv run pytest test/skipped_outcome_test.py test/ray_materialization_contract_test.py -m ray -q— 9 passed, 12 deselected with real Ray, including driver-materialization checks.uv run pytest test/storage/test_table_contracts.py test/iceberg/namespace_test.py -q— 42 passed; concurrent skips/data, separate handles, and persistent conflict failure included.uv run pytest test/operator_tests/test_workflow_results.py -k spawned_results_materialize -q— 1 passed, 23 deselected.LIVE [] / REPLAY []; concurrent Iceberg reportsSUCCESS 8 / RECEIPTS 8; Pydantic nesting preserves native skipped values. Actual spawned operator andava resultdownload preserve nested public reason/metadata.pnpm exec vitest run src/Inspector.test.tsx src/state.test.ts src/api.test.tsfromweb/operator— 21 passed;pnpm exec vitest run src/OperatorUi.test.tsx— 3 passed; finalpnpm test src/Inspector.test.tsx— 5 passed.make web-build— packaged assets rebuilt and committed; protobuf bindings regenerated withmake proto/make web-proto.uv run python examples/skipped_node.py— prints successful downstream completion.rstart: overall success, skipped source, successful downstream, reason/metadata/timestamps; full and compact views and keyboard inspection exercised. Focused skip-detail axe scan: 17 passes, zero violations/incomplete.Runtime evidence
Current captures are from source
389c13860e0d197a933d2d440ad2fea72a7312da; final head46e68fa02c33100bf1508fe60999a60cfa92995fdiffers only by import ordering. The custom-context run shows a whole-node skip, successful containers containing skipped values, and completed downstream work. Authenticated GitHub-rendered markup and fetched image bytes match each original PNG; only permanent native attachment URLs are published.Boundaries and risks
Durability is limited to existing table metadata and result-bundle storage. Operator run state and embedded handles remain process-local; no coordinator recovery, rollback, exactly-once execution, or production guarantees are added. Direct
return ava.skip(...)does not infer a table or undo prior side effects. Consumers of Python value edges must handleava.Skipped; successfulNoneis unchanged. Selecting a skipped producer in a rerun executes it again; pruned Python inputs retain existing rejection semantics. Generated Python protobuf files include generator formatting changes.