Relationships
#2601 Run-scoped data get/list return the latest version, not the version the run produced
Opened by stack72 · 9/28/2026· Shipped 9/28/2026
Problem
Resolving data through a workflow run returns the latest version of each data item, not the version that run produced. When the recorded artifact no longer matches by id, it can return a same-named item from a different model.
Affected:
swamp data get <name> --workflow <wf> --run <runId>(anddata.getoverswamp servewithworkflowName+runId)swamp data list --workflow <wf> --run <runId>
Cause
WorkflowDataService.findAllForWorkflowRun (src/domain/data/workflow_data_service.ts:62):
- Loads
dataRepo.findAllGlobal(), which holds only the latest version of every data name. - Matches each run artifact ref (
{dataId, name, version, tags}) bydataId. The comment there notes data ids change per version, so refs from older runs miss. - Falls back to the first global item with the same
nameacross any model (lines 131-140). findByNameInWorkflowRuncompares an explicitversionagainst that latest item, so asking for the run's actual (older) version returns not_found even when it is still on disk.
The version the run recorded (DataArtifactRef.version) is never used for resolution.
Reproduction
- A workflow whose step writes data
result(or a workflow-scope reportreport-<name>). - Run it twice: run A writes v1, run B writes v2.
swamp data get result --workflow <wf> --run <runA>returns v2 (run B's content).swamp data get result --workflow <wf> --run <runA> --version 1returns not_found.
Expected
Run-scoped resolution returns the exact version recorded on the run:
- Workflow-scope artifacts (
run.workflowDataArtifacts) resolve under model typeworkflow/ the run's workflow id at the recorded version. - Step artifacts use the existing lookup only to identify the owning model, then read that owner's data at the recorded version.
- An explicit version selects that version of the run's artifact.
- A recorded version that has been garbage-collected yields not_found. It must never fall back to newer data or to another model's data.
Impact
Silent wrong content: a link or script that targets a specific run sees a later run's output. This blocks swamp-club#2595 (dashboard deep links to a run's reports), which relies on run-scoped data.get being pinned.
Only two callers are affected: src/libswamp/data/get.ts:244 and src/libswamp/data/list.ts:228. Output without --run (latest run) is normally unchanged.
Shipped
Click a lifecycle step above to view its details.
stack72 commented 9/28/2026, 5:42:37 PM
Findings from the adversarial review of swamp-club#2595 that belong to this fix:
- Owner identification. Do not rely on the latest-id index or the first-name-match-in-any-model fallback and then re-read at the pinned version: a wrongly guessed owner at the pinned version looks authoritative. Resolve the owner from run-record hints first (step output resources/files carry modelType + modelId; report artifact tags carry modelName). Accept a candidate only when the pinned version's metadata id equals the artifact's dataId (each write mints a new id). Renamed data should still resolve at the pinned old-name version.
- Repeated writes in one run. When a run writes the same (owner, name) more than once (step A v3, then step B v4), return the highest recorded version, not the first in job/step order.
- Cost. Resolution walks findAllGlobal and calls findById against every workflow per request (workflow_data_service.ts:67, get.ts:236-251). Pass the already-loaded run to the service; keep the global walk as a fallback only.
- Docs. .claude/skills/swamp/references/data/reference.md:80-88 documents data list/get with --workflow --run and --version; update it (deno fmt and skill review) along with data/guide.md:49 and workflow/guide.md:58 if they imply latest.
Tests to include: a same-name item in a second model, rename after the run, a GC'd recorded version (not_found, never newer data), and a repeated (owner, name) within one run.
Sign in to post a ripple.