Skip to content

Releases: systeminit/swamp

swamp 20260227.134645.0-sha.c48e8ccf

27 Feb 13:47
Immutable release. Only release title and notes can be modified.
c48e8cc

Choose a tag to compare

What's Changed

  • fix: prevent macOS resource fork files in extension archives (#516)

Summary

On macOS, the system tar command automatically includes AppleDouble resource fork files (._* prefix) alongside every file in the archive. When these archives are pulled on Linux, the pull command's safety analyzer rejects them with "Hidden files are not allowed in extensions" because filenames starting with . are flagged as hidden.

This adds COPYFILE_DISABLE=1 to the tar command's environment in extension push. This is the standard macOS mechanism to suppress ._* resource fork inclusion. The variable is harmlessly ignored on non-macOS systems.

Why fix on push (not pull)

  • The ._* files are noise — they should never be in the archive in the first place
  • Fixing on push prevents the problem at the source for all consumers
  • The pull-side hidden file check is a valid security measure that should remain untouched

Change

src/cli/commands/extension_push.ts (line 445)

- env: { GZIP: "-9" },
+ env: { GZIP: "-9", COPYFILE_DISABLE: "1" },

Fixes #515

Test plan

  • deno check passes
  • deno lint passes
  • deno run test — 2290 tests pass, 0 failures
  • Manual: on macOS, run swamp extension push and inspect the resulting archive to confirm no ._* files are included

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.134645.0-sha.c48e8ccf/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

macOS (Intel):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.134645.0-sha.c48e8ccf/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (x86_64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.134645.0-sha.c48e8ccf/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (aarch64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.134645.0-sha.c48e8ccf/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260227.103255.0-sha.63faf259

27 Feb 10:33
Immutable release. Only release title and notes can be modified.
63faf25

Choose a tag to compare

What's Changed

  • fix: extension push resolves workflows from both indexer and extension dirs (#514)

Summary

Follow-up to #513. extension push now resolves workflow references from both workflow directories instead of just one:

  • workflows/ — the indexer symlink tree (created by swamp workflow create)
  • extensions/workflows/ — the extension workflows directory (where swamp extension pull places files, configurable via workflowsDir in .swamp.yaml)

Previously, after #513 landed, workflow resolution only checked the extension workflows dir (resolveWorkflowsDir(marker), default extensions/workflows/). This broke resolution of workflows created via swamp workflow create, which live at workflows/{name}/workflow.yaml as symlinks.

What changed

src/cli/commands/extension_push.ts

  • Workflow resolution now tries the indexer dir (workflows/) first, then falls back to the extension workflows dir (resolveWorkflowsDir(marker))
  • Error messages report both directories when a workflow can't be found in either location

integration/extension_push_test.ts

  • Reverted the existing workflow test back to using workflows/ (indexer symlinks) — confirms the indexer path works
  • Added a new test that places a workflow directly in extensions/workflows/ — confirms the extension path works

Testing

  1. Integration tests — All 11 tests pass, including both workflow resolution paths
  2. Manual end-to-end test — Compiled the binary, created a fresh repo in /tmp, created one workflow via swamp workflow create (indexer dir at workflows/) and one manually in extensions/workflows/, created a manifest referencing both, and ran swamp extension push --dry-run. Both workflows resolved successfully:
    extension·push: Workflows (2):
    extension·push:   "../../private/tmp/.swamp/workflows/workflow-ccc8cfbb-...yaml"
    extension·push:   "../../private/tmp/extensions/workflows/ext-wf.yaml"
    extension·push: Dry run complete
    
  3. deno check — pass
  4. deno lint — pass
  5. deno fmt — pass

Test plan

  • deno check passes
  • deno lint passes
  • deno fmt passes
  • Integration tests pass (11/11)
  • Manual test: workflow from workflows/ (indexer) resolves
  • Manual test: workflow from extensions/workflows/ resolves
  • Manual test: both in same manifest resolve together

🤖 Generated with Claude Code

Co-authored-by: Blake Irvin blakeirvin@me.com


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.103255.0-sha.63faf259/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

macOS (Intel):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.103255.0-sha.63faf259/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (x86_64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.103255.0-sha.63faf259/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (aarch64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.103255.0-sha.63faf259/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260227.101345.0-sha.b4c26639

27 Feb 10:14
Immutable release. Only release title and notes can be modified.
b4c2663

Choose a tag to compare

What's Changed

  • fix: extension push respects modelsDir/workflowsDir from .swamp.yaml (#513)

Summary

Fixes #512

swamp extension push hardcoded model and workflow directory paths (extensions/models and workflows) instead of reading them from .swamp.yaml. Other commands (extension pull, mod.ts) correctly use resolveModelsDir(marker) / resolveWorkflowsDir(marker) to respect user configuration. This PR makes extension push consistent with the rest of the codebase.

What changed

src/cli/commands/extension_push.ts

  • Added imports for resolveModelsDir, resolveWorkflowsDir, RepoMarkerRepository, and RepoPath
  • Replaced hardcoded resolve(repoDir, "extensions/models") with resolve(repoDir, resolveModelsDir(marker)) — reads modelsDir from .swamp.yaml, falls back to env var SWAMP_MODELS_DIR, then default extensions/models
  • Replaced hardcoded resolve(repoDir, "workflows") with resolve(repoDir, resolveWorkflowsDir(marker)) — reads workflowsDir from .swamp.yaml, falls back to env var SWAMP_WORKFLOWS_DIR, then default extensions/workflows

integration/extension_push_test.ts

  • Fixed existing workflow test to use extensions/workflows/ (the correct default from resolveWorkflowsDir) instead of the old hardcoded workflows/
  • Added new integration test that creates a repo with a custom modelsDir in .swamp.yaml, places model files in that custom directory, and verifies extension push --dry-run finds them correctly

Why this is the right fix

The resolver functions (resolveModelsDir / resolveWorkflowsDir) are the canonical way to determine directory paths throughout the codebase. They implement a clear priority chain: env var > .swamp.yaml config > default. Using them in extension push makes it consistent with extension pull and all other commands, and ensures users who configure custom directories in .swamp.yaml get the expected behavior.

Testing

  1. Integration tests — All 10 extension_push_test.ts tests pass, including the new custom modelsDir test
  2. Manual end-to-end test — Compiled the binary (deno run compile), created a fresh repo in /tmp with modelsDir: custom/my-models in .swamp.yaml, placed a model file in custom/my-models/echo.ts, and ran swamp extension push --dry-run. The command correctly resolved the model at the custom path and completed successfully:
    extension·push: Models (1):
    extension·push:   "custom/my-models/echo.ts"
    extension·push: Dry run complete for "@test/custom-dir-test"@"2026.02.27.1"
    
  3. deno check — pass
  4. deno lint — pass
  5. deno fmt — pass

Test plan

  • deno check passes
  • deno lint passes
  • deno fmt passes
  • Integration tests pass (10/10)
  • Manual test with compiled binary and custom modelsDir

🤖 Generated with Claude Code

Co-authored-by: Blake Irvin blakeirvin@me.com


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.101345.0-sha.b4c26639/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

macOS (Intel):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.101345.0-sha.b4c26639/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (x86_64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.101345.0-sha.b4c26639/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (aarch64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.101345.0-sha.b4c26639/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260227.033008.0-sha.c637ddd9

27 Feb 03:31
Immutable release. Only release title and notes can be modified.
c637ddd

Choose a tag to compare

What's Changed

  • fix: resolve path mismatches in extension push dedup and bundle naming (#509)

Summary

Fixes two path-handling bugs in swamp extension push that affect extensions with symlinked workflow files and nested model paths.

Bug 1: Workflow dedup path mismatch

When merging workflow files from the dependency resolver with those from the manifest, the code compares file paths to avoid duplicates. But the two sources produce different path formats for the same file:

  • Manifest paths: resolved via Deno.realPath(), which canonicalizes the entire path including parent directory symlinks (e.g. /tmp/private/tmp on macOS)
  • Dependency resolver paths: built with join(repoDir, ".swamp/workflows/..."), which doesn't resolve parent symlinks

This caused the dedup to miss matches when any part of the repo path was a symlink, potentially including duplicate workflow files in the archive.

Fix: realPath() the dependency resolver paths before comparison, so both sides use canonical absolute paths.

Bug 2: Bundle naming collision with nested model paths

Bundle entry names used basename(entryPoint, ".ts"), which strips all directory structure. For extensions with nested model paths, this causes silent collisions:

extensions/models/aws/ec2/instance.ts  → basename → instance.js
extensions/models/aws/ecs/instance.ts  → basename → instance.js  ← overwrites!

Only the last model bundled survives. The runtime (user_model_loader.ts) already expects bundles at their relative paths (e.g. .swamp/bundles/aws/ec2/instance.js), so the archive was inconsistent with what the runtime produces locally.

Fix: Use relative(modelsDir, entryPoint) as the bundle key, preserving the full directory structure (e.g. aws/ec2/instanceaws/ec2/instance.js). The pull side already uses a recursive copyDir() for bundles, so nested paths are extracted correctly.

Integration test

Adds a test that creates 3 workflows with symlinks (mimicking swamp's indexer: workflows/{name}/workflow.yaml.swamp/workflows/workflow-{uuid}.yaml), builds a manifest referencing all 3, and verifies all 3 appear in the --dry-run output.

User impact

  • Workflow dedup: Prevents edge-case duplicate workflows when the repo lives under a symlinked path. Most visible on macOS where /tmp is a symlink.
  • Bundle naming: Critical for anyone publishing models with nested directory structures (e.g. aws/ec2/instance.ts, aws/ecs/instance.ts). Without this fix, only one model per basename would survive in the archive. This is a blocker for publishing large extension sets organized in subdirectories.

Test plan

  • deno check — type checking passes
  • deno lint — no lint errors
  • deno fmt — formatting correct
  • deno run test — all 2288 tests pass
  • Integration test verifies multi-workflow archiving with symlinks

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.033008.0-sha.c637ddd9/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

macOS (Intel):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.033008.0-sha.c637ddd9/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (x86_64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.033008.0-sha.c637ddd9/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (aarch64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.033008.0-sha.c637ddd9/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260227.023412.0-sha.a76b58ce

27 Feb 02:35
Immutable release. Only release title and notes can be modified.
a76b58c

Choose a tag to compare

What's Changed

  • fix: use unique archive names for workflow files in extension push (#507)

Summary

  • Fix workflow file collision in archives: all {name}/workflow.yaml entries produced the same basename()workflow.yaml, so only the last workflow survived in the tar.gz
  • Fix workflow name extraction for dependency resolver: basename("namespace-debug/workflow.yaml", ".yaml") gave workflow for all entries instead of the actual workflow name
  • Skip auto-resolved model files that don't exist at the conventional path (they may already be in the manifest under a different filename)

Problem

A user published @john/k8s — an extension with 15 models and 13 workflows. When pulled, only 1 workflow was present. The manifest listed:

workflows:
  - namespace-debug/workflow.yaml
  - deployment-status/workflow.yaml
  - service-connectivity/workflow.yaml
  - cluster-health/workflow.yaml
  - security-audit/workflow.yaml
  - rbac-audit/workflow.yaml
  - storage-health/workflow.yaml
  - autoscaling-status/workflow.yaml
  - batch-jobs-status/workflow.yaml
  - network-audit/workflow.yaml
  - pod-inventory/workflow.yaml
  - pod-health-check/workflow.yaml
  - cluster-summary/workflow.yaml

The archive copy step used basename() to name files in the tar.gz:

// basename("namespace-debug/workflow.yaml") → "workflow.yaml"
// basename("deployment-status/workflow.yaml") → "workflow.yaml"
// basename("cluster-summary/workflow.yaml") → "workflow.yaml"
// ... all 13 resolve to the same filename
const destPath = join(extDir, "workflows", basename(wfFile));
await Deno.copyFile(wfFile, destPath);  // each overwrites the previous

Inspecting the published 2026.02.27.1.tar.gz confirmed only cluster-summary (the last manifest entry) survived:

$ tar -tzf 2026.02.27.1.tar.gz | grep workflows
extension/workflows/
extension/workflows/workflow.yaml       ← 1 file instead of 13

$ head -2 extension/workflows/workflow.yaml
id: eaea8dd8-54f0-4d86-ab75-68bd3dd5ec1b
name: cluster-summary                    ← last entry wins

The models directory was fine (15 unique filenames), but all 13 workflows collapsed into one.

Fix

Track each workflow's unique archive name derived from its manifest reference directory:

// "namespace-debug/workflow.yaml" → "namespace-debug.yaml"
// "deployment-status/workflow.yaml" → "deployment-status.yaml"
// "cluster-summary/workflow.yaml" → "cluster-summary.yaml"
const refDir = dirname(wfRef);
const archiveName = refDir !== "."
  ? `${refDir.replace(/\//g, "-")}.yaml`
  : basename(realPath);

Also fixed two related issues in the same flow:

  1. Workflow name extraction for dependency resolverbasename("namespace-debug/workflow.yaml", ".yaml") gave "workflow" for all entries. Now correctly extracts the directory name ("namespace-debug") so the resolver can look up the right workflows.

  2. Phantom model paths from dependency resolver — when models are at non-conventional paths (e.g., greeter.ts instead of @stack72/greeter/model.ts), the resolver guessed a path that didn't exist on disk and the safety analyzer blocked the push. Now validates file existence before adding auto-resolved model files.

Test plan

  • deno check passes
  • deno lint passes
  • deno fmt passes
  • All 2287 tests pass
  • Manual: created test project with 2 models + 2 workflows, verified archive contains both workflow files with unique names
  • Manual: verified project with model instances (dependency resolver) no longer fails with phantom model paths

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.023412.0-sha.a76b58ce/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

macOS (Intel):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.023412.0-sha.a76b58ce/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (x86_64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.023412.0-sha.a76b58ce/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (aarch64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.023412.0-sha.a76b58ce/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260227.015924.0-sha.861a61bf

27 Feb 02:00
Immutable release. Only release title and notes can be modified.
861a61b

Choose a tag to compare

What's Changed

  • fix: forward all task.inputs as method arguments in workflow execution (#499) (#506)

fix: forward all task.inputs as method arguments in workflow execution (#499)

Summary

Fixes #499 — workflow step task.inputs were not forwarded as method arguments when keys matched the model definition's inputs.properties.

Root Cause

In DefaultStepExecutor.executeModelMethod() (lines 372-395 of execution_service.ts), a filter intentionally excluded task inputs whose keys appeared in originalDefinition.inputs.properties. This was added in PR #244 when the architecture changed from setAttribute to setMethodArgument, but it incorrectly prevented definition-level inputs from being forwarded as method arguments — causing the method to receive undefined for those fields at runtime.

The Fix

Replaced ~24 lines of filtering logic with simple forwarding of all stepInputs as method arguments. This is a net deletion of code.

Why this is correct

The execution flow in executeModelMethod has four steps:

  1. Evaluate task.inputs expressions → stepInputs
  2. Merge stepInputs into ctx.expressionContext.inputs
  3. Resolve ${{ inputs.X }} expressions in the definition via evaluateDefinitionExpressions()
  4. Forward stepInputs as method arguments via setMethodArgument()

Step 3 handles the CEL expression path (e.g., run: "${{ inputs.greeting }}" in the definition). Step 4 handles the direct argument path (method schema expects the value directly). The old filter broke step 4 for any key that appeared in the definition's input schema. Removing it means both paths work:

  • CEL path: Resolved at step 3, then redundantly overwritten with the same value at step 4 (harmless)
  • Direct path: Not resolved at step 3 (no expression), correctly set at step 4 (this was the broken case)

Why this is safe

  • Method arguments are validated with Zod safeParse() which strips unknown keys — extra inputs are silently dropped
  • No model uses .strict() validation, so redundant keys cause no errors
  • The only behavioral change is that the broken case now works correctly

Testing

Automated

  • Added regression test in execution_service_test.ts verifying task inputs with keys matching definition-level input properties are forwarded to the step executor
  • All 2273 existing tests pass

Manual (exact reproduction from issue #499)

Created a test project with a command/shell model that has inputs.properties (greeting, name) and a ${{ inputs.X }} expression in its run argument. Created a workflow with step task.inputs referencing workflow-level inputs via ${{ inputs.greeting }} / ${{ inputs.name }}.

1. Evaluate — expressions resolve correctly:

$ swamp workflow evaluate test-inputs --json --input '{"greeting":"Hello","name":"World"}'
# task.inputs.greeting: "Hello", task.inputs.name: "World" ✓

2. Run with --last-evaluated (the exact failing case from the issue):

$ swamp workflow run test-inputs --last-evaluated --verbose
# Output: "Hello World" ✓ (previously: undefined undefined)

3. Run directly with --input (non-cached path):

$ swamp workflow run test-inputs --input '{"greeting":"Hey","name":"There"}' --verbose
# Output: "Hey There" ✓ (previously: undefined undefined)

Verification

  • deno check — passed
  • deno lint — passed
  • deno fmt — passed
  • deno run test — 2273 passed, 0 failed
  • deno run compile — binary compiles successfully

Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.015924.0-sha.861a61bf/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

macOS (Intel):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.015924.0-sha.861a61bf/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (x86_64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.015924.0-sha.861a61bf/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (aarch64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.015924.0-sha.861a61bf/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260227.014932.0-sha.b86bd948

27 Feb 01:50
Immutable release. Only release title and notes can be modified.
b86bd94

Choose a tag to compare

What's Changed

  • fix: extension push follows symlinks when collecting workflow files (#505)

Summary

Closes #503.

  • Resolve symlinks to real paths using Deno.realPath() in extension_push.ts when collecting manifest workflow files, instead of passing symlink paths to the safety analyzer

Problem

Swamp's indexing service creates symlinks at workflows/{name}/workflow.yaml.swamp/workflows/workflow-{uuid}.yaml. When extension push collects workflow files listed in the manifest, it resolved them from workflows/ and passed the symlink paths directly to the safety analyzer, which rejected them with:

"Symlinks are not allowed in extensions."

This meant workflows created with swamp workflow create couldn't be published without manually replacing symlinks with real files first.

Fix

Replace Deno.stat() with Deno.realPath() when collecting manifest workflow files. realPath() resolves symlinks to their target path and also verifies the file exists — so the separate existence check becomes unnecessary.

This is consistent with how the dependency resolver already works — it calls workflowRepo.getPath(id) which returns the real .swamp/workflows/ path, not the symlink. The safety analyzer's lstat() + symlink rejection is preserved as defense-in-depth.

Reproduction

Created a test project in /tmp to verify both before and after:

Before (original code):

$ swamp extension push manifest.yaml --dry-run -y
ERR extension·push Safety errors (push blocked):
ERR extension·push   "/private/tmp/swamp-test-503/workflows/greet-workflow/workflow.yaml": "Symlinks are not allowed in extensions."
FTL error Error: "Extension has safety errors that must be resolved before pushing."

After (with fix):

$ swamp extension push manifest.yaml --dry-run -y
INF extension·push Extension: "@stack72/greeter"@"2026.02.27.1"
INF extension·push Models (1):
INF extension·push   "extensions/models/greeter.ts"
INF extension·push Workflows (1):
INF extension·push   ".swamp/workflows/workflow-a55742d8-1dec-471f-b9b0-c6e6e3345d4e.yaml"
INF extension·push Dry run complete for "@stack72/greeter"@"2026.02.27.1"
INF extension·push Archive size: "1.8KB"
INF extension·push No API calls were made.

The symlink at workflows/greet-workflow/workflow.yaml is resolved to the real file in .swamp/workflows/, passes the safety analyzer, and is correctly archived.

Test plan

  • deno check passes
  • deno lint passes
  • deno fmt passes
  • All 2272 tests pass
  • Manual verification: swamp workflow create + swamp extension push --dry-run succeeds with symlinked workflow

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.014932.0-sha.b86bd948/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

macOS (Intel):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.014932.0-sha.b86bd948/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (x86_64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.014932.0-sha.b86bd948/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (aarch64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.014932.0-sha.b86bd948/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260227.014632.0-sha.8b27d8e5

27 Feb 01:47
Immutable release. Only release title and notes can be modified.
8b27d8e

Choose a tag to compare

What's Changed

  • feat: auto-detect localhost telemetry endpoint from auth serverUrl (#504)

Summary

  • When the stored auth serverUrl is a localhost address (localhost, 127.0.0.1, [::1]), automatically use http://localhost:8080 as the telemetry endpoint instead of production https://telemetry.swamp.club
  • Adds isLocalhostUrl() and resolveTelemetryEndpoint() pure functions following the existing resolveLogLevel/resolveModelsDir pattern
  • Refactors initTelemetryService() to load auth credentials before resolving the telemetry endpoint (needed for localhost detection)

Test Plan

  • Added 8 tests for isLocalhostUrl covering localhost variants, remote URLs, invalid URLs, and empty strings
  • Added 4 tests for resolveTelemetryEndpoint covering all three priority tiers (explicit config > localhost auto-detect > default)
  • deno check — passed
  • deno lint — passed
  • deno fmt --check — passed
  • deno run test — 2284/2284 passed

Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.014632.0-sha.8b27d8e5/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

macOS (Intel):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.014632.0-sha.8b27d8e5/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (x86_64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.014632.0-sha.8b27d8e5/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (aarch64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.014632.0-sha.8b27d8e5/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260227.012355.0-sha.e2553ee0

27 Feb 01:24
Immutable release. Only release title and notes can be modified.
e2553ee

Choose a tag to compare

What's Changed

  • fix: findBySpec() and findByTag() return only latest version (#502)

Summary

Fixes #497

data.findBySpec() and data.findByTag() iterated all versions of each data entry and returned every match. When used in a workflow forEach, this produced duplicate expanded steps with identical names, which corrupted the topological sort dependency graph and triggered a spurious "Cyclic dependency detected" error.

Both functions now use getLatestVersionSync() to return only the most recent version of each data entry — matching the pattern already established by data.latest().

What changed

findBySpec() (src/domain/expressions/model_resolver.ts)

  • Replaced listVersionsSync() + version loop with a single getLatestVersionSync() call per data entry
  • Added a seen Set for cross-coordinate deduplication (keyed by modelType:modelId:dataName, without version)

findByTag() (src/domain/expressions/model_resolver.ts)

  • Same fix: replaced version iteration with getLatestVersionSync()
  • Fixed the dedup key to exclude version — the old key (modelType:modelId:dataName:version) only deduplicated the exact same version appearing under different coordinates, not different versions of the same entry

Why this is correct

  1. findBySpec and findByTag are discovery functions — they find distinct data entries matching criteria, not enumerate version histories. The docs describe them as "Find all data matching a tag" and "Find all data from a specific output spec", where "all" refers to distinct entries, not all versions of each.
  2. Version access has its own APIdata.version() and data.listVersions() exist specifically for accessing version history.
  3. latest() sets the precedent — it already uses getLatestVersionSync(). The findBy* functions are semantically a filtered variant of latest() applied across multiple entries.
  4. The only consumers are CEL expressions in workflows — specifically in forEach expansions where you want one step per logical data entry, not one step per version.

Binary testing

Verified the fix end-to-end using the compiled swamp binary at /tmp/fix-497:

Setup

  • Initialized a fresh swamp repo
  • Created 3 command/shell model instances: service-a, service-b, service-c
  • Ran each model's execute method 3 times, creating 3 versions of result data per model (9 total data versions)

Test 1: findBySpec with single model

Created a workflow with forEach over data.findBySpec("service-a", "result"):

  • Result: forEach expanded to 1 step (latest version only), workflow ran and succeeded
  • Before fix: would expand to 3+ duplicate steps, causing cyclic dependency error

Test 2: findByTag across all models

Created a workflow with forEach over data.findByTag("specName", "result"):

  • Result: forEach expanded to 3 steps (one per model, latest version only), all succeeded
  • Before fix: would expand to 9+ steps with triplicate names, causing cyclic dependency error

Test plan

  • deno check — type checking passes
  • deno lint — linting passes
  • deno fmt — formatting passes
  • deno run test — 2248 tests pass (0 failed), including 2 new unit tests and 1 updated integration test
  • deno run compile — binary recompiles successfully
  • End-to-end binary testing with findBySpec forEach workflow
  • End-to-end binary testing with findByTag forEach workflow

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.012355.0-sha.e2553ee0/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

macOS (Intel):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.012355.0-sha.e2553ee0/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (x86_64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.012355.0-sha.e2553ee0/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (aarch64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.012355.0-sha.e2553ee0/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260227.010910.0-sha.c64caf75

27 Feb 01:10
Immutable release. Only release title and notes can be modified.
c64caf7

Choose a tag to compare

What's Changed

  • fix: extension pull writes workflows to configured workflowsDir (#501)

Summary

  • extension pull now writes workflow files to the configured workflowsDir (default: extensions/workflows/) instead of the previously hardcoded workflows/ directory

This is a follow-up to #500 which added extensions/workflows/ support. Without this fix, extension pull would write workflows to workflows/ while workflow search and workflow run look in extensions/workflows/, meaning pulled extension workflows wouldn't be discoverable.

What changed

In src/cli/commands/extension_pull.ts:

  • Added workflowsDir to the PullContext interface
  • The command action now resolves workflowsDir using resolveWorkflowsDir(marker) (same priority chain: SWAMP_WORKFLOWS_DIR env var > .swamp.yaml workflowsDir > default extensions/workflows)
  • The pullExtension() function uses the resolved workflowsDir for both conflict detection and file placement

Test plan

  • deno check passes
  • deno lint passes
  • deno fmt passes
  • All 2272 tests pass
  • Binary compiles successfully

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.010910.0-sha.c64caf75/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

macOS (Intel):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.010910.0-sha.c64caf75/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (x86_64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.010910.0-sha.c64caf75/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (aarch64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.010910.0-sha.c64caf75/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/