Report a published node that has no code of its own
The store answers the new-node template when nothing was ever written for a
node, so such a node ran — returning {} on every call, reporting active and
ok, and saying nothing anywhere. Unreachable through `fluksio sync`, which
writes every body before it publishes; the editor end was open.
A run of a flow holding one is now refused, and the flow carries a
missing_source issue so it is visible before anybody runs it. A draft is
exempt: a node being written legitimately has no published body yet.
The generated client is regenerated for the new issue code, which also
catches up the drift left by earlier backend work (resources, code_digest,
idempotency_key).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -48,12 +48,13 @@ from sqlmodel import Session, col, select
|
||||
|
||||
from fluksio.core.db import engine as db_engine
|
||||
from fluksio.flow.artifacts import ArtifactStore, is_reference, valid_digest
|
||||
from fluksio.flow.controller import FlowController, RunContext
|
||||
from fluksio.flow.controller import NODE_TYPES, FlowController, RunContext
|
||||
from fluksio.flow.messages import qualify
|
||||
from fluksio.flow.pipeline import CacheHit, NodeOutcome, Pipeline
|
||||
from fluksio.flow.queue import WorkItem, WorkQueue
|
||||
from fluksio.flow.schemas import FlowDef
|
||||
from fluksio.flow.schemas import FlowDef, NodeDef
|
||||
from fluksio.flow.state import MemoryState, StateBackend
|
||||
from fluksio.flow.store import FlowStore
|
||||
from fluksio.models import Run, RunArtifact, RunMetric, RunNode
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -106,7 +107,9 @@ def digest_of(params: dict[str, Any], seed: int | None) -> str:
|
||||
return hashlib.sha256(canonical.encode()).hexdigest()
|
||||
|
||||
|
||||
def batch_issues(flow: FlowDef) -> list[str]:
|
||||
def batch_issues(
|
||||
flow: FlowDef, store: FlowStore | None = None, draft: bool = False
|
||||
) -> list[str]:
|
||||
"""Why this flow cannot be run as a batch, if it cannot.
|
||||
|
||||
One thing genuinely breaks: a port with a discretization interval holds
|
||||
@@ -118,6 +121,12 @@ def batch_issues(flow: FlowDef) -> list[str]:
|
||||
|
||||
A delay node is fine; without a queue to defer into it simply sleeps,
|
||||
which in a run is what was asked for.
|
||||
|
||||
The other is a node with no body. The store answers a new node's template
|
||||
when nothing was ever written for one, so such a node runs — and returns
|
||||
``{}`` every time, without a word. `fluksio sync` writes every body before
|
||||
it publishes, so this is unreachable from there; it is the other end that
|
||||
is open.
|
||||
"""
|
||||
issues: list[str] = []
|
||||
for node in flow.nodes:
|
||||
@@ -129,9 +138,28 @@ def batch_issues(flow: FlowDef) -> list[str]:
|
||||
"the value would be dropped. Remove the interval, or mark "
|
||||
"the port as streaming if it is a curve being thinned out."
|
||||
)
|
||||
if store is not None and _has_no_body(store, flow.name, node, draft):
|
||||
issues.append(
|
||||
f"Node '{node.id}' has no stored code. It would run as an "
|
||||
"empty node and publish nothing, so the run is refused."
|
||||
)
|
||||
return issues
|
||||
|
||||
|
||||
def _has_no_body(
|
||||
store: FlowStore, flow: str, node: NodeDef, draft: bool = False
|
||||
) -> bool:
|
||||
"""A node that should carry its own source, and does not.
|
||||
|
||||
A shared node runs the library's copy, so it is not this: a missing library
|
||||
fails the node loudly on its own.
|
||||
"""
|
||||
node_type = NODE_TYPES.get(node.type)
|
||||
if node_type is None or not node_type.has_source or node.source_ref:
|
||||
return False
|
||||
return not store.has_node_source(flow, node.id, draft=draft)
|
||||
|
||||
|
||||
def required_labels(flow: FlowDef) -> list[str]:
|
||||
"""Worker labels this flow cannot run without.
|
||||
|
||||
@@ -644,7 +672,7 @@ class RunService:
|
||||
if existing is not None:
|
||||
return existing
|
||||
flow = self.controller.store.read_flow(flow_name, draft=draft)
|
||||
issues = batch_issues(flow)
|
||||
issues = batch_issues(flow, self.controller.store, draft=draft)
|
||||
if issues:
|
||||
raise RunRejected(" ".join(issues))
|
||||
params = params or {}
|
||||
|
||||
Reference in New Issue
Block a user