Skip to main content
← Back to list
01Issue
BugShippedSwamp CLIPublic
Assigneesstack72

Relationships

#1761 data.findBySpec() returns only the first in-run record, not all of them, so an .all() assert silently checks one element

Opened by skwump_slruper · 8/21/2026· Shipped 8/21/2026

Summary

data.findBySpec(model, spec) is documented as returning all data records for a model matching an output spec name. Inside a workflow run it returns exactly one — the first record written during that run. Every later record for the same spec is invisible to the expression.

The consequence is silent. An assert of the shape data.findBySpec(m, s).all(r, <predicate>) checks only the first record produced in the run and passes regardless of what the rest contain. A step that produces a bad record after a good one passes the check. This is a check that fails in the passing direction, which is the direction nobody notices.

/manual/reference/cel-expressions states:

"Returns all data records for a model matching a given output spec name. Inside a workflow run, results are scoped to data produced during that run."

Scoping to the run holds — that half is correct and I verified it. "All data records" does not.

Reproduction

Clean repo, command/shell only, no extensions. swamp 20260814.003027.0-sha.ec7fc2f4, macOS 15 arm64, local filesystem datastore.

swamp init
swamp model create command/shell probe

Workflow with two jobs. Job produce runs the model twice — once exiting 0, once exiting 7. Job check asserts over the spec, depending on produce:

jobs:
  - name: produce
    steps:
      - name: run-good
        task:
          type: model_method
          modelIdOrName: probe
          methodName: execute
          inputs:
            run: "echo GOOD"
      - name: run-bad
        task:
          type: model_method
          modelIdOrName: probe
          methodName: execute
          inputs:
            run: "exit 7"
            ignoreExitCode: true
  - name: check
    steps:
      - name: all-clean
        task:
          type: assert
          expr: data.findBySpec("probe", "result").all(r, r.content.exitCode == 0)
          message: "every result produced during this run exited 0"
          severity: high
    dependsOn:
      - job: produce
        condition:
          type: succeeded

The run summary confirms both records were produced — "Data produced: result from produce" appears twice.

Expected: the assert fails. One of the two records produced during the run has exitCode == 7.

Actual: the assert passes. Assertions: 1 passed, workflow succeeds, exit 0.

Substituting only the assert expression, same workflow, same run shape

expr result
...size() > 0 PASS
...size() == 1 PASS
...size() == 2 FAIL
...all(r, false) FAIL
...all(r, r.content.exitCode == 0) PASS
...all(r, r.content.exitCode == 7) FAIL
...all(r, r.content.command == "echo GOOD") PASS
...all(r, r.content.command == "exit 7") FAIL

size() == 1 while two records were produced is the defect stated as a number.

all(r, false) failing is the control that matters: .all() does iterate, and the list is not empty, so this is not CEL's vacuous-truth-on-empty-list behaviour. The list genuinely holds one element.

command == "echo GOOD" passing identifies which one: the first record written during the run, not the most recent. So this is not last-write-wins either.

Scoping was checked separately and is correct

Run the workflow clean (exit 0). Then write a bad record from outside any workflow:

swamp model method run probe execute --input run="exit 3" --input ignoreExitCode=true

Re-run the identical workflow. It still passes, and size() == 1 still holds. The out-of-run record is correctly excluded. The problem is confined to records produced inside the run.

Why this matters more than a wrong count

findBySpec is the collection primitive — the one expression an author reaches for to assert over everything a fan-out produced. forEach with concurrency plus an assert over the accumulated spec is the natural shape, and in that shape only the first result is ever checked. The larger the fan-out, the more it misses, and it reports success either way.

Related but distinct: #1621 covers data.latest() being memoized per run and returning stale data. This is findBySpec returning incomplete data. Both are the query layer disagreeing with its own reference, in opposite directions.

Suggested resolution

Either return all in-run records as documented, or — if returning one is deliberate — change the reference, rename it, and make the single-record case explicit, because the current name and description both promise a collection.

02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED+ 1 MOREASSIGNED+ 2 MOREREVIEW+ 4 MOREPR_MERGED+ 2 MORESESSION_SUMMARIZED

Shipped

8/21/2026, 12:58:01 PM

Click a lifecycle step above to view its details.

03Sludge Pulse
stack72 assigned stack728/21/2026, 10:07:13 AM
Editable. Press Enter to edit.

stack72 commented 8/21/2026, 12:58:10 PM

Thanks @skwump_slruper for reporting this! The fix has been merged and a release is on its way. We appreciate your contribution to swamp.

Sign in to post a ripple.