feat(ITL-507): ephemeral function-pod output v1#214
Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ITL-507) Replace the single-tag Phase 1 lookup pseudocode with the full bulk resolution mechanism: partition tag table entries by record_id prefix, join each group against its respective store, union the hits, and derive the needs-computing set. Add three new test cases covering mixed-store bulk resolution and silent-drop miss paths. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ITL-507) Introduce explicit set_ephemeral_store() method on NodeBase (default no-op) and PersistentFunctionNode (stores the value), plus Pipeline.set_ephemeral_store() that propagates the store to all nodes uniformly. Replace direct attribute assignment in the pipeline section with the new method-based API. Update Scope, Design §2-4, File Layout, and Public API accordingly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…(ITL-507) Replace vague "NodeBase / node protocol" references with the actual files: FunctionNodeProtocol and OperatorNodeProtocol in node_protocols.py, and PipelineProtocol in pipeline_protocols.py. Update Scope, Design §2/§4, File Layout, and Public API sections accordingly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…(ITL-507) Distinguish the two miss paths in Phase 1 Step 3: a record_id present in the tag table but absent from the persistent result_database is unexpected (data loss / external modification) and must emit a WARNING-level log. An ephemeral store miss is the normal cross-session path and remains silent. Update the corresponding test name to assert on the warning. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Phase 2 always calls add_pipeline_record with skip_cache_lookup=True so that a new pipeline DB row is appended even when a stale entry_id already exists. The inner join in Phase 1 provides natural deduplication — stale rows find no join partner and are dropped, valid rows survive. Documents the recovery scenario (both R1 and R2 present) and the ephemeral accumulation behaviour. Adds two new tests covering the append path and the no-infinite-cycle guarantee. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ITL-507) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…TL-507) Update all signatures to InMemoryArrowDatabase | None. Passing None detaches the ephemeral store from the node (or all nodes via pipeline), reverting to persistent-only behaviour for subsequent writes. Updated node protocol, PersistentFunctionNode implementation, PipelineProtocol, Pipeline concrete implementation, and all Public API table rows. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…TL-507) Step 4 of Phase 1 bulk resolution is now an explicit priority merge: ephemeral_hits merged first, persistent_hits overwrites on collision. Persistent results are authoritative — they win whenever both stores have a valid entry for the same input. Update the recovery scenario note and add test_persistent_result_outcompetes_ephemeral to the testing plan. Also add test_set_ephemeral_store_none_detaches (missed in prior commit). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ependent joins (ITL-507) Step 3 now describes two separate inner joins (one against result_database, one against ephemeral_result_store), each producing its own entry_id-keyed dict. Step 4 merges them with persistent_hits second so it overwrites any collision — this is the concrete mechanism that enforces persistent priority. Notes that the same entry_id appearing in both dicts only occurs when the tag table has both a persistent and an ephemeral entry for the same input. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ge (ITL-507) Replace the Python dict-merge approach with a vectorised Polars anti-join: ephemeral rows whose entry_id already exists in persistent_df are excluded by the anti-join, then persistent_df and ephemeral_only_df are concatenated. O(n+m), zero-copy concat, no Python dict conversion needed. Update the recovery scenario note to describe the anti-join exclusion mechanism. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-up (ITL-507) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…or naming consistency Aligns with the INPUT_DATA_HASH_COL and NODE_CONTENT_HASH_COL pattern where the module-level constant name matches the SystemConstant property name. Also strengthens test assertion to pin exact column name. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ratorNodeProtocol, PipelineProtocol Implements Task 3 of ITL-507 (ephemeral function-pod output v1). Adds the set_ephemeral_store() method to all node and pipeline protocols, allowing orchestrators to manage ephemeral result stores. In v1, the implementation is a no-op for operator nodes (full ephemeral support deferred to ITL-509). Changes: - Add InMemoryArrowDatabase to TYPE_CHECKING imports in: * src/orcapod/protocols/node_protocols.py * src/orcapod/protocols/pipeline_protocols.py * src/orcapod/pipeline/graph.py * src/orcapod/pipeline/job.py * src/orcapod/core/nodes/function_node.py * src/orcapod/core/nodes/operator_node.py - Add set_ephemeral_store() method signatures to protocols: * FunctionNodeProtocol * OperatorNodeProtocol * PipelineProtocol - Implement set_ephemeral_store() in concrete classes: * FunctionNode (no-op for blueprint) * FunctionJobNode (no-op in v1, deferred to ITL-508) * OperatorNode (no-op for blueprint) * OperatorJobNode (no-op in v1, deferred to ITL-509) * Pipeline (propagates store to all nodes) * PipelineJob (propagates store to all nodes) - Create tests/test_protocols/test_ephemeral_store_protocol.py with basic protocol conformance tests - Update test_node_protocols.py mock classes to include set_ephemeral_store Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lasses Move no-op set_ephemeral_store from concrete FunctionNode/OperatorNode/ OperatorJobNode to FunctionNodeBase and OperatorNodeBase respectively. Move the pipeline propagation from Pipeline/PipelineJob to AbstractPipelineBase with a node_type != 'source' guard that prevents AttributeError on SourceNode when iterating _nodes.values(). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…l_store override to FunctionJobNode Add ephemeral_result_store and _ephemeral_cached_pod instance variables to FunctionJobNode.__init__, and implement set_ephemeral_store(store) method to assign or remove the ephemeral result store. When store is not None, creates a CachedFunctionPod backed by the store so ephemeral writes use the same format as persistent writes. When store is None, clears both the store and ephemeral pod. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ument read-only node behavior Remove unnecessary string-quoting from CachedFunctionPod annotation (hard import at top of file). Add Note section to set_ephemeral_store docstring explaining the invariant when _function_pod is None. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…orce skip_cache_lookup=True in Phase 2
…ephemeral/persistent join
…ipelineBase.set_ephemeral_store
…rue in add_pipeline_record When skip_cache_lookup=True we bypass the pre-check, so we must also tell the database to silently skip duplicates (skip_duplicates=True) rather than raise ValueError on repeated calls with the same input.
…nd anti-join priority tests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mirrors the sync _process_data_internal ephemeral/persistent branch logic in the async counterpart so that nodes with ephemeral_result=True work correctly when executed via the async path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…resolution_mixed_stores The test was passing config=cfg which silently set _orcapod_config instead of node_config, meaning ephemeral_result=True was never applied. Fixed to use node_config=cfg and added an assertion verifying the ephemeral store was actually written to. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…se _fetch_joined_records priority merge Replaces the previous test which used a fake UUID and add_pipeline_record (which silently skipped insertion due to skip_duplicates) with a proper DB manipulation approach: flush() to commit the persistent row, then insert the ephemeral row with skip_duplicates=False (succeeds because _pending_record_ids is empty after flush). Also writes a real ephemeral result to the ephemeral store so the inner-join produces a row, making ephemeral_df.height > 0 and triggering the anti-join branch. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…y node tests Adds TestPersistentMissWarning and TestEphemeralOnlyNode to test_ephemeral_result.py as part of ITL-507 task 10.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
| f"input_stream={self._input_stream!r})" | ||
| ) | ||
|
|
||
| def set_ephemeral_store(self, store: "InMemoryArrowDatabase | None") -> None: |
There was a problem hiding this comment.
There is actually no reason to make it InMemoryArrowDatabase -- we should make it generally accept ArrowDatabaseProtocol
There was a problem hiding this comment.
Fixed. Changed type hint from InMemoryArrowDatabase | None to ArrowDatabaseProtocol | None in FunctionNodeBase.set_ephemeral_store, FunctionJobNode.set_ephemeral_store, OperatorNodeBase.set_ephemeral_store, both protocol definitions (FunctionNodeProtocol, OperatorNodeProtocol), PipelineProtocol, and AbstractPipelineBase.
| f"input_stream={self._input_stream!r})" | ||
| ) | ||
|
|
||
| def set_ephemeral_store(self, store: "InMemoryArrowDatabase | None") -> None: |
There was a problem hiding this comment.
if this is not going to be implemented in the base, I want to make sure we assign this to be an abstraction method
There was a problem hiding this comment.
Fixed. FunctionNodeBase.set_ephemeral_store is now decorated with @abstractmethod. Added an explicit no-op override on FunctionNode (the thin blueprint). Did the same for OperatorNodeBase / OperatorNode / OperatorJobNode for consistency.
| self._cached_function_pod: CachedFunctionPod | None = None | ||
|
|
||
| # Ephemeral result store (None until set_ephemeral_store() is called by the pipeline) | ||
| self.ephemeral_result_store: "InMemoryArrowDatabase | None" = None |
There was a problem hiding this comment.
As above, this should be a general ArrowDatabaseProtocol
There was a problem hiding this comment.
Fixed. self.ephemeral_result_store in FunctionJobNode.__init__ is now typed as ArrowDatabaseProtocol | None.
| self._ephemeral_cached_pod: CachedFunctionPod | None = None | ||
|
|
||
| if pipeline_database is not None: | ||
| self.attach_databases( |
There was a problem hiding this comment.
Why not just go ahead and make attach_database optionally take in ephemeral_database (and also accept it in the init). This way, you could initialize everything correctly in one shot?
There was a problem hiding this comment.
Done. Added ephemeral_database: ArrowDatabaseProtocol | None = None to both FunctionJobNode.__init__ and attach_databases. When pipeline_database is provided, ephemeral_database is forwarded through attach_databases which calls set_ephemeral_store internally. When only ephemeral_database is provided (no pipeline DB), set_ephemeral_store is called directly.
| def attach_databases( | ||
| self, | ||
| pipeline_database: ArrowDatabaseProtocol, | ||
| result_database: ArrowDatabaseProtocol | None = None, |
There was a problem hiding this comment.
expand this to optionally accept ephemeral_database: ArrowDatabaseProtocol | None = None. If present, invoke attach ephemeral database on node
There was a problem hiding this comment.
Done. attach_databases now accepts ephemeral_database: ArrowDatabaseProtocol | None = None and calls self.set_ephemeral_store(ephemeral_database) at the end when it is not None.
| self._function_pod.node_config if self._function_pod is not None else None | ||
| ) | ||
| ephemeral_result = ( | ||
| node_config.ephemeral_result if node_config is not None else False |
There was a problem hiding this comment.
let's make the property for configuring ephemeral or not to be more obvious, something like node_config.is_result_ephemeral so that it's obvious that it's a boolean flag.
There was a problem hiding this comment.
Renamed throughout. NodeConfig.ephemeral_result is now NodeConfig.is_result_ephemeral in types.py, all internal usages in function_node.py (both sync and async paths, error messages, docstrings), and all test files.
…phemeral_store, rename to is_result_ephemeral, add ephemeral_database to attach_databases Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Review round 1 — changes addressedAll six comments resolved in commit f802d08:
Full test suite: 4168 passed, 56 skipped, 6 xfailed. |
|
|
||
| if pipeline_database is not None: | ||
| self.attach_databases( | ||
| pipeline_database=pipeline_database, |
There was a problem hiding this comment.
we should simply make attach_databases accept pipeline_database = None and skip setting it on nodes if None
| @@ -720,6 +745,7 @@ def attach_databases( | |||
| self, | |||
| pipeline_database: ArrowDatabaseProtocol, | |||
There was a problem hiding this comment.
just make this pipeline_database: ArrowDatabaseProtocol | None = None
| """ | ||
| if self._cached_function_pod is not None: | ||
| node_config = ( | ||
| self._function_pod.node_config if self._function_pod is not None else None |
There was a problem hiding this comment.
It makes no sense that node_config is attached onto _function_pod. Make a Linear issue to directly follow up and address this.
There was a problem hiding this comment.
Pull request overview
This PR introduces an “ephemeral result” mode for function-pod execution nodes so newly computed results can be written to an in-memory store while preserving the existing persistent-cache read path, using a boolean pipeline tag-table column to route lookups to the appropriate store.
Changes:
- Adds
NodeConfig.is_result_ephemeraland plumbs ephemeral-store injection viaset_ephemeral_store(...)across node/pipeline protocols and pipeline base. - Extends
FunctionJobNodewith an ephemeral store +CachedFunctionPodand restructures_fetch_joined_recordsto resolve hits from persistent+ephemeral stores with persistent priority. - Adds a large set of tests covering write routing, cache hits/misses, cross-session behavior, and priority resolution.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_system_constants.py | Adds a small assertion test for the new IS_EPHEMERAL_COL constant value. |
| tests/test_protocols/test_ephemeral_store_protocol.py | Verifies new set_ephemeral_store API presence on key protocols. |
| tests/test_pipeline/test_node_protocols.py | Updates protocol conformance tests to include set_ephemeral_store. |
| tests/test_core/test_node_config.py | Adds tests for the new NodeConfig.is_result_ephemeral field default/behavior/frozenness. |
| tests/test_core/function_pod/test_ephemeral_result.py | Comprehensive end-to-end tests for ephemeral result routing, hits/misses, and store priority. |
| superpowers/specs/2026-07-07-itl-507-ephemeral-function-pod-output-design.md | Design doc describing the ephemeral result store feature and intended semantics. |
| superpowers/plans/2026-07-07-itl-507-ephemeral-function-pod-output.md | Implementation plan detailing tasks, APIs, and test coverage for ITL-507. |
| src/orcapod/types.py | Adds NodeConfig.is_result_ephemeral to control ephemeral result writes. |
| src/orcapod/system_constants.py | Adds constants.IS_EPHEMERAL_COL used to mark tag-table rows as ephemeral/persistent. |
| src/orcapod/protocols/pipeline_protocols.py | Adds PipelineProtocol.set_ephemeral_store(...). |
| src/orcapod/protocols/node_protocols.py | Adds set_ephemeral_store(...) to function/operator node protocols. |
| src/orcapod/pipeline/base.py | Implements AbstractPipelineBase.set_ephemeral_store(...) propagation logic. |
| src/orcapod/core/nodes/operator_node.py | Adds no-op set_ephemeral_store(...) implementations for operator nodes. |
| src/orcapod/core/nodes/function_node.py | Implements ephemeral-store plumbing, write routing, and two-store join resolution in FunctionJobNode. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| entry_id, | ||
| combined_record, | ||
| skip_duplicates=False, | ||
| skip_duplicates=skip_cache_lookup, |
There was a problem hiding this comment.
You're correct that skip_duplicates=skip_cache_lookup (i.e. skip_duplicates=True) silently skips the write when the entry_id already exists, which leaves a stale pipeline record in place after a cross-session ephemeral-store reset. The stale entry is never overwritten with the new result ID, causing the same entry to re-enter Phase 2 on every subsequent iter_data call.
Flipping to skip_duplicates=not skip_cache_lookup (i.e. skip_duplicates=False when skip_cache_lookup=True) is not viable: it raises a ValueError from InMemoryArrowDatabase.add_records when _process_data_internal is called twice for the same input within the same batch (a scenario covered by test_execute_data_second_call_same_input_deduplicates).
The proper fix is an upsert/overwrite operation on ArrowDatabaseProtocol so that stale pipeline records can be atomically replaced. That requires updating the protocol and all four implementations (InMemoryArrowDatabase, ConnectorArrowDatabase, DeltaLakeArrowDatabase, NoopDatabase), which is out of scope for this PR. Filed ITL-513 to track it.
| is_result_ephemeral: If ``True``, new computation results are written to | ||
| the pipeline-scoped ephemeral store (``ArrowDatabaseProtocol``) | ||
| instead of the persistent result database. Persistent cache hits | ||
| are still served when available. Raises ``RuntimeError`` at | ||
| execution time if ``is_result_ephemeral=True`` but no ephemeral | ||
| store has been injected via ``set_ephemeral_store()``. | ||
| """ | ||
|
|
||
| max_concurrency: int | None = None | ||
| is_result_ephemeral: bool = False |
There was a problem hiding this comment.
The field was renamed from ephemeral_result to is_result_ephemeral in response to eywalker's review request (round 1, comment #214 (comment): "let's make the property for configuring ephemeral or not to be more obvious, something like node_config.is_result_ephemeral"). The PR description and all call sites have been updated to use the new name. No compatibility alias is provided per project convention (greenfield, pre-v0.1.0).
…se=None, simplify __init__
Review round 3 (Copilot) — changes addressed
Full test suite remains clean: 4180 passed, 56 skipped, 6 xfailed. |
| """Assign or remove the ephemeral result store for all nodes. | ||
|
|
||
| Propagates ``store`` to every node in the pipeline by calling | ||
| ``node.set_ephemeral_store(store)`` on each. Nodes that do not | ||
| support ephemeral storage (e.g. operator nodes in v1) treat this | ||
| as a no-op. | ||
|
|
||
| Pass ``None`` to detach the ephemeral store from all nodes, | ||
| reverting them to persistent-only writes for subsequent runs. | ||
| """ |
There was a problem hiding this comment.
Fixed. The docstring for PipelineProtocol.set_ephemeral_store now explicitly states that only function-pod and operator-pod nodes receive the store, and that source nodes are skipped because they produce data rather than consume it. See commit 4edd0c56.
| def _make_source_stream(rows: list[dict], tag_columns: list[str] | None = None) -> ArrowTableStream: | ||
| if tag_columns is None: | ||
| tag_columns = ["id"] | ||
| table = pa.table( | ||
| {k: pa.array([r[k] for r in rows], type=pa.int64()) for k in rows[0]} | ||
| ) | ||
| source = ArrowTableSource(table, tag_columns=tag_columns, source_id="test_src", infer_nullable=True) | ||
| return source |
There was a problem hiding this comment.
Fixed. _make_source_stream return type annotation corrected from ArrowTableStream to ArrowTableSource, matching the actual value returned. See commit 4edd0c56.
| if results is None: | ||
| # Tag table has persistent entries but result DB is empty — data loss | ||
| for row_dict in persistent_taginfo_df.to_dicts(): | ||
| logger.warning( | ||
| "Pipeline DB entry '%s' has no match in persistent result DB " | ||
| "— data may have been deleted externally. " | ||
| "This input will be recomputed.", | ||
| row_dict.get(_PIPELINE_ENTRY_ID_COL), | ||
| ) |
There was a problem hiding this comment.
Fixed. The per-entry loop (for row_dict in persistent_taginfo_df.to_dicts(): logger.warning(...)) has been replaced with a single aggregated warning: logger.warning("%d pipeline DB entries have no match in persistent result DB ...", persistent_taginfo_df.height). This emits at most one log record regardless of how many entries are missing. See commit 4edd0c56.
| # Warn about persistent tag rows that found no match in the result DB | ||
| if full_persistent_df.height < persistent_taginfo_df.height: | ||
| matched_ids = set( | ||
| full_persistent_df.select(_PIPELINE_ENTRY_ID_COL) | ||
| .to_series() | ||
| .to_list() | ||
| ) | ||
| for row_dict in persistent_taginfo_df.to_dicts(): | ||
| if row_dict[_PIPELINE_ENTRY_ID_COL] not in matched_ids: | ||
| logger.warning( | ||
| "Pipeline DB entry '%s' has no match in persistent result DB " | ||
| "— data may have been deleted externally. " | ||
| "This input will be recomputed.", | ||
| row_dict[_PIPELINE_ENTRY_ID_COL], | ||
| ) |
There was a problem hiding this comment.
Fixed. The second per-entry loop (join-miss case) has also been replaced with a single aggregated warning: we now compute missing_count = persistent_taginfo_df.height - full_persistent_df.height and emit one logger.warning("%d pipeline DB entries have no match ...", missing_count) when missing_count > 0. The matched_ids set construction and inner loop are gone entirely. See commit 4edd0c56.
- Replace two per-entry warning loops in `_fetch_joined_records` with
single aggregated `logger.warning("%d ... have no match ...", count)`
calls to avoid log flooding and O(n) overhead.
- Fix `_make_source_stream` return-type annotation: `ArrowTableStream`
→ `ArrowTableSource` (matches actual return value).
- Clarify `PipelineProtocol.set_ephemeral_store` docstring: source nodes
are skipped (they produce data, not consume it).
- Update test assertion to match new plural wording ("have no match"
instead of the old per-entry "has no match").
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Round 4 (Copilot) — all comments addressed in commit
|
| Comment | Change |
|---|---|
PipelineProtocol.set_ephemeral_store docstring says "every node" |
Updated to "every function-pod and operator-pod node"; added explicit note that source nodes are skipped. |
_make_source_stream annotated as → ArrowTableStream but returns ArrowTableSource |
Return-type annotation corrected to ArrowTableSource. |
First per-entry warning loop (results is None branch) floods logs |
Replaced loop + to_dicts() with one logger.warning("%d ... have no match ...", persistent_taginfo_df.height). |
| Second per-entry warning loop (join-miss branch) floods logs + builds a set | Removed matched_ids set and inner loop; replaced with missing_count = persistent_taginfo_df.height - full_persistent_df.height then one aggregated warning. |
The test assertion in TestPersistentMissWarning was also updated from "has no match" to "have no match" to align with the new plural wording.
All 3727 tests pass (uv run pytest tests/ --ignore=tests/test_databases).
Four previously uncovered code paths in the ephemeral function-pod feature, identified from the codecov patch report: - TestAsyncEphemeralExecution: async_execute with is_result_ephemeral=True (happy path + RuntimeError guard when no store assigned). Covers _async_process_data_internal ephemeral branch (lines 1311–1342). - TestAddPipelineRecordDeduplication: calling add_pipeline_record twice with skip_cache_lookup=False verifies the duplicate-detection early return fires without inserting a second row (lines 1430, 1436–1439). - TestFetchJoinedRecordsGuards: get_all_records() on a no-DB node returns None (line 1611); and records written without IS_EPHEMERAL_COL (legacy/backward-compat) are treated as persistent (lines 1637–1638). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
NodeConfig.is_result_ephemeral: bool = Falsefield to route new computation results to anArrowDatabaseProtocolstore instead of the persistent result DBset_ephemeral_store(store: ArrowDatabaseProtocol | None)abstract method onFunctionNodeBase/OperatorNodeBasewith no-op overrides on blueprint nodes; propagated throughFunctionNodeProtocol,OperatorNodeProtocol,PipelineProtocol, andAbstractPipelineBaseFunctionJobNodegainsephemeral_result_storeslot +_ephemeral_cached_pod(aCachedFunctionPodbacked by the ephemeral store);attach_databasesacceptsephemeral_database: ArrowDatabaseProtocol | None = None(andpipeline_databaseis now optional) for one-shot init_fetch_joined_recordsrestructured into two independent Polars inner-joins (one per store) merged with an anti-join giving persistent results priorityIS_EPHEMERAL_COLboolean column in the pipeline tag table routes record lookups to the correct store (preservesDATA_RECORD_IDaspa.large_binary())skip_cache_lookup=Trueto bypass the pre-write DB lookuptests/test_core/function_pod/test_ephemeral_result.pyFixes ITL-507
Test plan
uv run pytest tests/ -q— all tests pass (~4180 passed, 56 skipped, 6 xfailed)test_ephemeral_result_written_to_memory_not_persistent_db— result goes to ephemeral store onlytest_within_session_ephemeral_hit— second call does not recomputetest_cross_session_miss_recomputes— fresh store triggers recomputetest_persistent_hit_served_when_ephemeral_true— persistent cache still works when ephemeral store attachedtest_persistent_result_outcompetes_ephemeral— persistent wins anti-join (verified with direct DB manipulation)test_persistent_miss_warns_and_recomputes— WARNING log emitted when result DB trimmedtest_store_not_assigned_raises— clear RuntimeError for is_result_ephemeral=True without storetest_ephemeral_only_node_no_persistent_db— works without a result_database🤖 Generated with Claude Code