A run keeps the inputs its flow declared at submit
Run.declared (migration 2e7ca7d10044) snapshots the declarations on submit and import. The run detail's Inputs panel reads them, and a run from before falls back to the flow's current declarations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WgBfkm4rCmtd1T4vHWjEPx
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
"""run.declared
|
||||
|
||||
The inputs the flow declared when a run was submitted, so its record reads
|
||||
against that version of the flow and not against whatever the flow declares
|
||||
now.
|
||||
|
||||
Revision ID: 2e7ca7d10044
|
||||
Revises: 930ffa094aad
|
||||
Create Date: 2026-10-15
|
||||
|
||||
"""
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "2e7ca7d10044"
|
||||
down_revision = "930ffa094aad"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# Nullable: a run submitted before this kept no snapshot, and its readers
|
||||
# fall back to the flow's current declarations.
|
||||
op.add_column("run", sa.Column("declared", sa.JSON(), nullable=True))
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_column("run", "declared")
|
||||
@@ -210,6 +210,10 @@ class RunRow(BaseModel):
|
||||
class RunDetail(RunRow):
|
||||
result: dict[str, Any] = Field(default_factory=dict)
|
||||
flow_version: int = 1
|
||||
#: The inputs the flow declared when this was submitted, with their
|
||||
#: declared values. Null for a run from before this was kept, which reads
|
||||
#: against the flow's current declarations instead.
|
||||
declared: dict[str, Any] | None = None
|
||||
nodes: list[RunNodeRow] = Field(default_factory=list)
|
||||
artifacts: list[ArtifactRow] = Field(default_factory=list)
|
||||
|
||||
|
||||
@@ -454,6 +454,14 @@ def seed_values(
|
||||
return values
|
||||
|
||||
|
||||
def declared_inputs(flow: FlowDef) -> dict[str, Any]:
|
||||
"""What a flow declares it can be given, by name, with the declared values.
|
||||
|
||||
Kept on each run, since the flow can change after it.
|
||||
"""
|
||||
return {one.spec.name: one.initial for one in flow.inputs}
|
||||
|
||||
|
||||
def collect_result(flow: FlowDef, state: StateBackend) -> dict[str, Any]:
|
||||
"""What the run produced, keyed by message name without the flow prefix."""
|
||||
prefix = f"{flow.name}."
|
||||
@@ -925,6 +933,7 @@ class RunService:
|
||||
code_digest=run_digest(flow),
|
||||
params=params,
|
||||
passed=named if passed is None else passed,
|
||||
declared=declared_inputs(flow),
|
||||
params_digest=digest_of(params, seed),
|
||||
seed=seed,
|
||||
group_id=group_id,
|
||||
@@ -1047,6 +1056,7 @@ class RunService:
|
||||
flow_version=flow.version,
|
||||
params=params,
|
||||
passed=passed,
|
||||
declared=declared_inputs(flow),
|
||||
params_digest=digest_of(params, seed),
|
||||
seed=seed,
|
||||
group_id=entry["group_id"],
|
||||
|
||||
@@ -362,6 +362,9 @@ class Run(SQLModel, table=True):
|
||||
#: The names in `params` the caller chose; the rest are the flow's declared
|
||||
#: values. Null for a run recorded before this was kept.
|
||||
passed: list[str] | None = Field(sa_column=Column(JSON), default=None)
|
||||
#: The inputs the flow declared when this was submitted, by name, with
|
||||
#: their declared values. Null for a run recorded before this was kept.
|
||||
declared: dict[str, Any] | None = Field(sa_column=Column(JSON), default=None)
|
||||
#: sha256 over params and seed. Two runs of the same thing share it, which
|
||||
#: is what makes "have I already run this?" a lookup.
|
||||
params_digest: str = Field(default="", index=True, max_length=64)
|
||||
|
||||
@@ -456,6 +456,9 @@ def test_a_run_records_the_inputs_it_actually_starts_from():
|
||||
assert spelled.params_digest == defaulted.params_digest
|
||||
# Only what the caller chose says which value was the flow's own.
|
||||
assert (defaulted.passed, spelled.passed) == (["epochs"], ["epochs", "lr"])
|
||||
# And what the flow declared then, so an input it adds later is not
|
||||
# drawn on this run as a default it never received.
|
||||
assert defaulted.declared == {"lr": 0.01, "epochs": None}
|
||||
finally:
|
||||
with Session(db_engine) as session:
|
||||
for run in session.exec(select(Run).where(col(Run.id).in_(made))).all():
|
||||
|
||||
Reference in New Issue
Block a user