Record the inputs a run actually starts from, not only the ones passed

A run submitted without explicit inputs recorded `params = {}`: Port
initials filled the values at node level and were never written back, so an
exported row had a blank `param.*` cell and the runs listing could not tell
a run that took every default from one submitted with those same numbers.
Declared initials are now folded in at submit, explicit values winning, and
the run-level seed still wins over a declared one.

`params_digest` is computed over the resolved values, so it changes shape
once: a run recorded before this does not dedupe against a newer identical
submit, and its stage-cache entries miss once.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Hra4ndWMCLU5F3KjUuVAc
This commit is contained in:
2026-08-29 13:45:49 +02:00
co-authored by Claude Opus 5
parent 2d654fd943
commit c050a7a52c
2 changed files with 71 additions and 0 deletions
+14
View File
@@ -846,6 +846,20 @@ class RunService:
# reference a python caller would have passed and every later reader —
# the digest, the cache, the run detail — sees one spelling.
params = resolve_references(flow, params, self._artifacts)
# What the run actually starts from, not only what was passed: an input
# left out takes its declared value, and a row that records `{}` cannot
# say which. Folded literally — an initial is a value from the
# definition, never a reference to resolve. The run-level seed still
# wins over a declared one, the way `seed_values` has it.
params = {
**{
declared.spec.name: declared.initial
for declared in flow.inputs
if declared.initial is not None
and not (declared.spec.name == "seed" and seed is not None)
},
**params,
}
# Checked here rather than in the driver: a caller who mistyped a
# parameter should be told now, not by a run that fails in a minute.
seed_values(flow, params, seed)