feat: no add-downstream + on assets nothing in the workspace writes

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Diego Imbert
2026-09-30 17:55:19 +02:00
co-authored by Claude Opus 5.5
parent a26bb2e651
commit 91adb1e9e8
4 changed files with 36 additions and 5 deletions
@@ -623,6 +623,7 @@
error: a.error,
dbt: a.dbt,
onAddScript: onAddScriptForAsset,
neverWritten: a.never_written,
pathPrefix,
defaultPathSuffix,
producers: producersByAsset.get(`${a.kind}:${a.path}`) ?? [],
@@ -156,6 +156,8 @@
foldedFixes?: NodeFix[]
/** The assets-only node for the scripts that build no asset. */
noAsset?: boolean
/** Nothing in the workspace writes it. */
neverWritten?: boolean
/** Card width in the assets-only view, sized to its text. */
width?: number
/** Assets-only view: deletes the producing script and trigger no other
@@ -219,8 +221,9 @@
}
}
// A red asset needs fixing where its error comes from, not a downstream step.
let showAdd = $derived(data.onAddScript != undefined && !data.error)
// A red asset needs fixing where its error comes from, not a downstream step;
// and a step downstream of what nothing writes would never run on its writes.
let showAdd = $derived(data.onAddScript != undefined && !data.error && !data.neverWritten)
let menuItems: Item[] = $derived(
data.onDeleteUpstream
? [
@@ -8,6 +8,9 @@ export interface AssetGraphAssetNode {
path: string
// Filled in client-side: the node is misconfigured (see `pipelineNodeErrors`).
error?: string
// Filled in client-side: nothing in the workspace writes it, so a step
// downstream of it would never run on its writes.
never_written?: boolean
// Fork workspaces only: 'fork' when this ducklake asset was materialized in
// the fork itself, 'deferred' when reads fall back to the parent workspace's
// current table via a defer view. Absent outside forks / for other kinds /
@@ -1705,8 +1705,24 @@
// Assets an asset trigger waits on (explicit `on`, or an auto-triggering read)
// that nothing in the pipeline writes, checked against the whole workspace: an
// asset trigger fires on a write from anywhere.
// Every other asset nothing in the pipeline writes is looked up too, so an
// asset nothing writes anywhere can go without the "add downstream step" +.
let writtenInPipeline = $derived(
new Set(
shownGraph.edges
.filter((e) => e.access_type === 'w' || e.access_type === 'rw')
.map((e) => `${e.asset_kind}:${e.asset_path}`)
)
)
let unwrittenInPipeline = $derived(
[...pipelineNodeErrors(shownGraph, explicitOnByPath).unwrittenTriggerAssets]
[
...new Set([
...pipelineNodeErrors(shownGraph, explicitOnByPath).unwrittenTriggerAssets,
...shownGraph.assets
.filter((a) => !a.dbt && !writtenInPipeline.has(`${a.kind}:${a.path}`))
.map((a) => `${a.kind}:${a.path}`)
])
]
.sort()
.join('\n')
)
@@ -1768,8 +1784,16 @@
assets: g.assets
.filter((a) => !phantom.has(`${a.kind}:${a.path}`))
.map((a) => {
const error = errors.assets.get(`${a.kind}:${a.path}`)
return error ? { ...a, error } : a
const key = `${a.kind}:${a.path}`
const error = errors.assets.get(key)
// Only once the workspace lookup has answered: before, it is unknown.
const never_written =
!a.dbt &&
!writtenInPipeline.has(key) &&
!!writtenElsewhere.current &&
!writtenElsewhere.loading &&
!writtenElsewhere.current.has(key)
return error || never_written ? { ...a, error, never_written } : a
}),
runnables: g.runnables.map((r) => {
const draft = showDrafts ? pe.drafts.get(r.path)?.script : undefined