Skip to content

latent.lint.rules.lt016

LT016 — catalog["x"].load()/.save() where input=/output= says the same thing.

Functions

lt016

lt016(ctx: RuleContext) -> Iterator[tuple[int, str]]

catalog["x"].load()/.save() where input=/output= says the same thing.

The two spellings read as equivalent and are not: only the declared one reaches the cache key. A declared input= is fingerprinted into it by _CatalogInputCachePolicy; an imperative .load() is invisible to it, so editing the dataset re-runs the declaring task and leaves the imperative one serving its cached result. A declared report output= is what makes _ReportOutputCachePolicy withhold the key so the task re-runs and the teardown has a fresh report to re-gate; an imperative .save() leaves the declared outputs tuple empty, the veto never fires, and a cache hit skips the save the teardown then fails to find — GateFailure on a flow whose code looks fine.

Task-scoped, like LT001: input=/output= bind a dataset to a task invocation, so module-scope access is a different defect with a different fix. A genuinely conditional save fires and needs a waiver, not a carve-out: output= saves unconditionally, so the two really do differ and choosing the imperative form is a decision worth writing down.