Skip to content

Releases: systeminit/swamp

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/

swamp 20260227.010040.0-sha.9cd80fb4

27 Feb 01:01
Immutable release. Only release title and notes can be modified.
9cd80fb

Choose a tag to compare

What's Changed

  • feat: add extension workflows support (extensions/workflows/) (#500)

Summary

Adds support for discovering and executing workflow YAML files from extensions/workflows/, mirroring the existing extensions/models/ pattern. This means extensions installed from third-party sources can now ship workflow files that are automatically discoverable and executable without manually copying them into .swamp/workflows/.

  • Extension workflows are read-only — they cannot be deleted or overwritten via CLI commands
  • Extension workflows are transparently merged with user workflows — workflow search, workflow run, and all other workflow commands see them alongside regular workflows
  • Name conflicts are resolved by giving priority to primary workflows in .swamp/workflows/ — extension workflows with the same name are hidden
  • The workflows directory is configurable via workflowsDir in .swamp.yaml or the SWAMP_WORKFLOWS_DIR environment variable (default: extensions/workflows)

What this means for users

Previously, if you installed an extension that included workflow YAML files, you had to manually copy those files into your .swamp/workflows/ directory. Now, extensions can place their workflows in extensions/workflows/ and they'll just work:

# Extension workflows are discoverable
swamp workflow search --json
# Returns workflows from both .swamp/workflows/ AND extensions/workflows/

# Extension workflows are executable
swamp workflow run my-extension-workflow
# Runs exactly like a regular workflow, creating runs in .swamp/workflow-runs/

# Extension workflows are read-only
swamp workflow delete my-extension-workflow
# Error: "Cannot delete extension workflow 'my-extension-workflow'. Extension workflows are read-only."

If you want to override an extension workflow, create one with the same name in .swamp/workflows/ — your version always takes priority.

Architecture: Composite Repository Pattern

Two new repository classes implement this transparently:

  1. ExtensionWorkflowRepository — Read-only repo that recursively discovers *.yaml workflows from the configured extensions directory. Broken YAML files are logged as warnings and skipped (resilient loading).
  2. CompositeWorkflowRepository — Wraps the existing YamlWorkflowRepository (primary/mutable) + ExtensionWorkflowRepository (secondary/read-only). All consumers use the WorkflowRepository interface, so this is a transparent change.

The RepositoryContext.workflowRepo type was widened from YamlWorkflowRepository to WorkflowRepository (the interface). This required no changes to downstream consumers since WorkflowExecutionService, SymlinkRepoIndexService, and all CLI commands already work against the interface.

Changes

New files

  • src/cli/resolve_workflows_dir.tsresolveWorkflowsDir() with priority: env var > config > default
  • src/infrastructure/persistence/extension_workflow_repository.ts — Read-only workflow repo
  • src/infrastructure/persistence/composite_workflow_repository.ts — Merges primary + extension repos

Modified files

  • src/infrastructure/persistence/repo_marker_repository.ts — Added workflowsDir?: string to RepoMarkerData
  • src/infrastructure/persistence/repository_factory.ts — Wired composite repo, widened workflowRepo type
  • src/cli/repo_context.ts — Resolves workflowsDir from marker and passes to factory
  • src/cli/completion_types.ts — Shell completions now include extension workflows
  • src/cli/mod.ts — Re-exports resolveWorkflowsDir
  • src/cli/commands/workflow_search.ts — Uses WorkflowRepository interface type
  • src/cli/commands/workflow_delete.ts — Guards against deleting extension-only workflows
  • src/cli/commands/workflow_edit.ts — Falls back to actual source file for extension workflows

Test plan

Automated tests (46 new tests, all 2272 pass)

resolveWorkflowsDir (5 tests in src/cli/mod_test.ts)

  • Returns default extensions/workflows with null marker
  • Returns default when marker has no workflowsDir
  • Uses marker.workflowsDir when set
  • SWAMP_WORKFLOWS_DIR env var takes priority over config
  • SWAMP_WORKFLOWS_DIR env var takes priority over default

ExtensionWorkflowRepository (10 tests in extension_workflow_repository_test.ts)

  • Discovers YAML workflows from a directory
  • Discovers workflows in subdirectories (recursive walk)
  • Returns empty array for empty directory
  • Returns empty array for non-existent directory
  • Skips broken YAML files with warnings (resilient loading)
  • findByName returns matching workflow
  • findByName returns null for non-existent name
  • findById returns matching workflow
  • save() rejects with UserError (read-only enforcement)
  • delete() rejects with UserError (read-only enforcement)

CompositeWorkflowRepository (11 tests in composite_workflow_repository_test.ts)

  • findByName checks primary first (precedence)
  • findByName falls back to extension
  • findByName returns null when not found in either
  • findById checks primary first (precedence)
  • findById falls back to extension
  • findAll deduplicates by name, primary wins
  • save delegates to primary
  • delete delegates to primary
  • Works with null extension repo (backwards compatibility)
  • nextId delegates to primary
  • getPath delegates to primary

Manual end-to-end testing (verified with compiled binary)

  1. Created a fresh swamp repo with swamp repo init
  2. Created a command/shell model named hello
  3. Created extensions/workflows/greet.yaml with a workflow that runs echo 'Hello from extension workflow!'
  4. swamp workflow search --json — confirmed the greet workflow appears in results with correct name, description, and job count
  5. swamp workflow search --json greet — confirmed exact-match returns full workflow details
  6. swamp workflow run greet — confirmed the workflow executes successfully, runs the shell command, prints "Hello from extension workflow!", saves data outputs, and completes with status "succeeded"

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.010040.0-sha.9cd80fb4/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.010040.0-sha.9cd80fb4/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.010040.0-sha.9cd80fb4/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.010040.0-sha.9cd80fb4/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260227.003433.0-sha.349ae436

27 Feb 00:35
Immutable release. Only release title and notes can be modified.
349ae43

Choose a tag to compare

What's Changed

  • feat: add integrity verification to swamp extension pull (#498)

Summary

Part 2 of extension integrity verification. Part 1 (swamp-club PR #105) added server-side SHA-256 checksum computation during extension push and a GET .../checksum API endpoint. This adds client-side verification to swamp extension pull so the CLI computes the SHA-256 of the downloaded archive, fetches the expected checksum from the server, and compares them — blocking extraction on mismatch.

Why this design

API client: getChecksum(name, version) returns string | null

The nullable return keeps backward compatibility with extensions published before Part 1 added checksum support. A 404 or a null checksum in the response both gracefully degrade to an "unverified" warning rather than failing the pull. This matches the existing getLatestVersion() and getExtension() 404→null pattern in the same client class.

Verification placement: after download, before extraction

The integrity check runs after the archive bytes are fully downloaded and before any extraction happens. This ensures a tampered archive is never written to disk. It also means verifyChecksum() throws a UserError that the existing command error flow already handles — no new error rendering needed.

Reuse of existing utilities

computeChecksum (domain/models) and verifyChecksum (domain/update/integrity) already exist and are tested. No new crypto code was introduced.

Output: verified vs unverified

Two states, not three — mismatch is a hard failure (thrown UserError), so it never reaches the render function. "verified" logs at info level, "unverified" logs at warn level to flag legacy extensions that lack checksums.

Verified output

$ swamp extension pull @stack72/system-extensions
00:23:44.618   info    extension·pull       Pulling "@stack72/system-extensions"@"2026.02.26.2"
00:23:44.623   info    extension·pull       Description: "Few system extensions for system and disk usage"
00:23:47.026   info    extension·pull       Identity verified: "@stack72/system-extensions"@"2026.02.26.2"
00:23:47.050   info    extension·pull       Pulled "@stack72/system-extensions"@"2026.02.26.2"
00:23:47.050   info    extension·pull       Extracted 4 files:
00:23:47.051   info    extension·pull         "extensions/models/system_usage.ts"
00:23:47.051   info    extension·pull         "extensions/models/disk_usage.ts"
00:23:47.051   info    extension·pull         ".swamp/bundles/system_usage.js"
00:23:47.051   info    extension·pull         ".swamp/bundles/disk_usage.js"

Changes

File Change
src/infrastructure/http/extension_api_client.ts Add getChecksum() method
src/presentation/output/extension_pull_output.ts Add renderExtensionPullIntegrity() render function
src/cli/commands/extension_pull.ts Wire integrity verification into pull flow

Test plan

  • deno check — type checking passes
  • deno lint — linting passes
  • deno fmt — formatting passes
  • deno run test — all 2246 tests pass
  • Manual: swamp extension pull @stack72/system-extensions shows "Identity verified"

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260227.003433.0-sha.349ae436/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.003433.0-sha.349ae436/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.003433.0-sha.349ae436/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.003433.0-sha.349ae436/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260226.231547.0-sha.8c92b192

26 Feb 23:16
Immutable release. Only release title and notes can be modified.
8c92b19

Choose a tag to compare

What's Changed

  • feat: add swamp extension push command for publishing extensions (#494)

Summary

Add the swamp extension push command, which packages and publishes extension models and workflows to the swamp registry.

  • Extension push command — manifest-driven three-phase push workflow (initiate, upload to S3, confirm) with --dry-run and -y flags
  • Manifest validation — Zod-based schema validation with clear error messages for missing fields, bad CalVer versions, reserved namespaces, and missing models/workflows
  • Safety analyzer — scans all files before push; blocks eval(), new Function(), symlinks, hidden files, oversized files; warns on Deno.Command(), long lines, base64 blobs
  • Import/dependency resolution — auto-discovers local TypeScript imports and workflow-to-model dependencies
  • Bundling — compiles each model entry point to standalone JS via deno bundle
  • Version management — pre-flight CalVer version check with interactive bump on duplicates
  • ExtensionApiClient — HTTP client for the registry API with HTML error page detection (returns clean message instead of raw HTML dump)
  • Skill documentation — publishing guide added to swamp-extension-model skill with full manifest schema, examples, push workflow, safety rules, and common errors

Files changed

  • src/cli/commands/extension.ts — Extension command group
  • src/cli/commands/extension_push.ts — Push command implementation
  • src/cli/mod.ts — Register extension command
  • src/cli/unknown_command_handler.ts — Extension subcommand suggestions
  • src/domain/extensions/extension_manifest.ts — Manifest schema and parser
  • src/domain/extensions/extension_safety_analyzer.ts — Safety analysis rules
  • src/domain/extensions/extension_import_resolver.ts — Local import resolver
  • src/domain/extensions/extension_dependency_resolver.ts — Workflow dependency resolver
  • src/domain/models/calver.ts — CalVer bump() support
  • src/infrastructure/http/extension_api_client.ts — Registry HTTP client
  • src/presentation/output/extension_push_output.ts — Push output rendering
  • .claude/skills/swamp-extension-model/SKILL.md — Publishing section added
  • .claude/skills/swamp-extension-model/references/publishing.md — Detailed publishing reference

Test plan

  • All 2229 tests pass (deno run test)
  • deno check passes
  • deno lint passes
  • deno fmt --check passes
  • Integration tests: CLI help, manifest validation errors, auth errors, safety hard errors
  • Unit tests: manifest parsing, safety analyzer, import resolver, dependency resolver, CalVer, API client, push output rendering
  • Manifest validation runs before auth check so CI tests pass without credentials

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260226.231547.0-sha.8c92b192/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/v20260226.231547.0-sha.8c92b192/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/v20260226.231547.0-sha.8c92b192/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/v20260226.231547.0-sha.8c92b192/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260226.230901.0-sha.756f7276

26 Feb 23:10
Immutable release. Only release title and notes can be modified.
756f727

Choose a tag to compare

What's Changed

  • feat: add swamp extension pull command for downloading extensions (#496)

Summary

Adds the swamp extension pull command, the counterpart to swamp extension push, allowing users to download published extensions from the swamp.club registry into their local swamp repository.

Usage

swamp extension pull @namespace/name           # pulls latest version
swamp extension pull @namespace/name@version   # pulls specific version
swamp extension pull @namespace/name --force   # overwrites existing files

Key Design Decisions

  • Unauthenticated endpoints: All pull operations (search, metadata, version info, download) are unauthenticated — no swamp auth login required. This uses getExtension() (unauthenticated metadata endpoint that includes latestVersion) instead of getLatestVersion() (the /latest endpoint requires auth).

  • Server URL resolution: Uses SWAMP_CLUB_URL env var or defaults to https://swamp.club — does not depend on stored auth credentials for the server URL.

  • Models directory resolution: Uses resolveModelsDir() which respects SWAMP_MODELS_DIR env > .swamp.yaml modelsDir > default extensions/models — not hardcoded.

  • Circular import fix: Extracted resolveModelsDir() to src/cli/resolve_models_dir.ts to break the circular import chain (extension_pull → mod → extension → extension_pull). The original mod.ts now imports and re-exports from the new file.

  • Safety analysis: Runs analyzeExtensionSafety() on all downloaded .ts files before extracting to disk. Hard errors (e.g., Deno.run, eval) abort the pull; warnings are displayed but don't block.

  • Recursive dependency pulling: If a manifest declares dependencies, each missing dependency is automatically pulled. Includes circular dependency guard (Set<string>) and max depth (10).

  • Concurrency-safe tracking: upstream_extensions.json tracks pulled extensions with lockfile-based read-modify-write protection and atomicWriteTextFile() for crash-safe writes.

  • Archive extraction mapping:

    • extension/models/*{modelsDir}/
    • extension/bundles/*.swamp/bundles/ (pre-compiled, immediately usable)
    • extension/workflows/*workflows/
    • extension/files/*{modelsDir}/ under extension subdirectory

New Files (6)

File Purpose
src/cli/commands/extension_pull.ts Command definition + orchestration (~380 lines)
src/cli/commands/extension_pull_test.ts Unit tests for parseExtensionRef()
src/cli/resolve_models_dir.ts Extracted to break circular import
src/presentation/output/extension_pull_output.ts Log + JSON rendering for all pull states
src/presentation/output/extension_pull_output_test.ts Render function tests
integration/extension_pull_test.ts CLI integration tests (help, invalid name, repo required, no auth required)

Modified Files (6)

File Change
src/cli/commands/extension.ts Wire up pull subcommand
src/cli/mod.ts Import + re-export resolveModelsDir from new location
src/cli/unknown_command_handler.ts Add pull to extension subcommand suggestions
src/infrastructure/http/extension_api_client.ts Add downloadArchive(), make apiKey optional on getDownloadUrl/getExtension
src/infrastructure/http/extension_api_client_test.ts Add downloadArchive connection failure test
integration/ddd_layer_rules_test.ts Bump presentation→infra violation ratchet 27→28

Test Plan

  • deno check passes
  • deno lint passes
  • deno fmt --check passes
  • All 2246 tests pass (deno run test)
  • deno run compile succeeds
  • swamp extension pull --help shows usage
  • swamp extension --help shows pull subcommand
  • Invalid name format gives clear error
  • Pull without initialized repo gives clear error
  • Pull does not require authentication (connection error, not auth error)

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260226.230901.0-sha.756f7276/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/v20260226.230901.0-sha.756f7276/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/v20260226.230901.0-sha.756f7276/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/v20260226.230901.0-sha.756f7276/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260226.174314.0-sha.0a12c9ff

26 Feb 17:44
Immutable release. Only release title and notes can be modified.
0a12c9f

Choose a tag to compare

What's Changed

  • fix: prevent deno.lock creation during extension bundling (#491)

Summary

Fixes #490.

PR #452 introduced deno bundle for extension model transpilation. The subprocess inherits the user's CWD and Deno's default lockfile behavior creates/updates a deno.lock in the user's project root — polluting their repo with an unexpected file.

Why --no-lock is the right fix

The --no-lock flag tells Deno to skip lockfile auto-discovery entirely. This is correct for swamp's bundling use case because:

  1. Bundling is a build step, not a dependency install. Swamp bundles extensions at startup as an internal transpilation step. The user didn't ask Deno to manage their dependencies — swamp is doing it behind the scenes. Creating a deno.lock in their project is a side effect they never opted into.

  2. npm resolution still works without a lockfile. Packages are fetched from the registry and fully inlined into the bundle. The lockfile only pins versions across runs — it doesn't enable resolution.

  3. Version pinning is handled at the source level instead. Since there's no lockfile, users should pin explicit versions in their import specifiers (e.g., npm:lodash-es@4.17.21). The skill docs and examples are updated to reflect this guidance. This is actually more explicit and portable than relying on a lockfile that lives outside the extension source.

  4. Alternative approaches are worse:

    • Setting cwd on the subprocess to a temp dir would break relative imports in extensions
    • Cleaning up deno.lock after bundling is fragile and races with other processes
    • Using --lock=<temp-path> still creates a lockfile (just elsewhere) for no benefit

Changes

  • src/domain/models/bundle.ts — Add --no-lock to deno bundle args
  • src/domain/models/bundle_test.ts — Add test: bundles npm imports successfully and verifies no deno.lock is created in the source directory
  • .claude/skills/swamp-extension-model/SKILL.md — Add version pinning rule to Key Rules section
  • .claude/skills/swamp-extension-model/references/examples.md — Pin versions in import examples table and text analyzer example

Test plan

  • New test passes: deno run test src/domain/models/bundle_test.ts (4/4 pass)
  • Full test suite passes: 2167 passed, 0 failed
  • deno check — type checking passes
  • deno lint — linting passes
  • deno fmt — formatting passes
  • deno run compile — binary compiles successfully

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260226.174314.0-sha.0a12c9ff/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/v20260226.174314.0-sha.0a12c9ff/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/v20260226.174314.0-sha.0a12c9ff/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/v20260226.174314.0-sha.0a12c9ff/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/