Releases: systeminit/swamp
swamp 20260317.001439.0-sha.adf8dd48
What's Changed
- feat: automatic extension resolution with trusted collectives (#725)
Summary
Closes #665
When users clone a repo with model or vault configurations referencing extension types that aren't installed locally, commands fail with cryptic "Unknown model type" or "Unsupported vault type" errors. This PR adds lazy auto-resolution: when swamp encounters an unknown type whose collective is on a trusted allowlist, it transparently searches the extension registry, installs the matching extension, hot-loads it into the live registries, and continues execution — no manual swamp extension pull needed.
What changes
- New
trustedCollectivesconfig in.swamp.yaml— defaults to["swamp", "si"]so official extensions auto-resolve out of the box. Users can add more collectives or set[]to disable. ExtensionAutoResolverdomain service — standalone service with port interfaces (ExtensionLookupPort,ExtensionInstallerPort,AutoResolveOutputPort) that keeps the domain layer clean of CLI/presentation imports.resolveModelType()/resolveVaultType()helper functions — drop-in replacements formodelRegistry.get()at 7 choke points across CLI commands and the workflow execution service.- Hot-loading support —
UserModelLoader.loadModels()andUserVaultLoader.loadVaults()gain askipAlreadyRegisteredoption so re-running model discovery after install doesn't error on already-registered types. - Vault auto-resolution in
VaultService.fromRepository()— resolves missing@-prefixed vault types beforeregisterVault(), keepingregisterVault()itself sync. - Clear UX output — users always see what's happening: searching, installing, installed (with model count), or actionable error messages for not-found/network failures. Both log and JSON output modes supported.
Design decisions
-
Standalone helper, not embedded in registries —
ModelRegistryandVaultTypeRegistryremain pure sync data structures. Resolution is a domain service that choke points call explicitly. This is more DDD-aligned: the registry is a repository (stores/retrieves), resolution is a domain service (orchestrates). -
Port interfaces for clean architecture — The domain service defines
ExtensionLookupPort,ExtensionInstallerPort, andAutoResolveOutputPortinterfaces. Concrete adapters in the CLI layer wire the HTTP client,installExtension(), model loaders, and output renderers. This keeps domain → CLI/presentation dependency arrows pointing the right direction. -
Two-step type-to-extension resolution — First tries direct lookup by progressively stripping trailing segments (e.g.,
@swamp/aws/ec2/instance→ try@swamp/aws/ec2, then@swamp/aws). Falls back to search with collective filter if direct lookup fails. This handles both exact extension names and partial matches. -
Always install latest — Auto-resolution always installs the latest version unless the user has explicitly pinned via
extension pull @name@version. This is the right default for trusted collectives where you control releases. -
Re-entrancy guard — A
Set<string>of types currently being resolved prevents infinite loops if transitive dependencies trigger further resolution. -
Collective not allowlisted = silent skip — No auto-resolution is attempted for non-allowlisted collectives. The existing "Unknown model type" error shows as-is. This is intentional — we don't want to suggest the feature exists for untrusted collectives.
User impact
- Zero-friction onboarding: Clone a repo that uses
@swamp/digitalocean/droplet, runswamp model method run, and it just works — the extension installs automatically on first use. - No breaking changes: Existing repos work identically. The default
trustedCollectives: ["swamp", "si"]only kicks in for@swamp/*and@si/*types. Users who don't use extensions see no difference. - Explicit opt-out: Set
trustedCollectives: []in.swamp.yamlto disable entirely. - Transparent operation: Every auto-resolution step is logged so users understand why a command takes longer the first time.
Testing
Automated tests (14 new)
- Skips non-allowlisted collectives (silent, no output)
- Resolves via direct lookup (strips segments correctly)
- Tries intermediate candidates before shorter ones (
@swamp/aws/ec2before@swamp/aws) - Falls back to search when direct lookup fails
- Shows
notFoundoutput when nothing matches - Shows
networkErroroutput on fetch failures (TypeError, timeouts) - Re-entrancy guard prevents infinite loops
- Handles non-
@prefixed types (e.g.,swamp/echo/v2) - Skips types without a collective (single-word types)
resolveModelTypereturns existing definitions without resolverresolveModelTypereturns undefined for unknown types without resolverresolveVaultTypereturns true for existing vault typesresolveVaultTypereturns false for unknown types without resolverresolveVaultTypeskips non-@types
Manual end-to-end test
Compiled the binary, initialized a fresh repo in /tmp, and ran:
swamp model create @swamp/digitalocean/droplet my-droplet
Result: auto-resolved @swamp/digitalocean from the registry, installed @swamp/digitalocean@2026.03.16.1, hot-loaded 32 models, and successfully created the my-droplet definition — all in one command with clear status output:
INF extension·auto-resolve Extension type "@swamp/digitalocean/droplet" not found locally, searching registry...
INF extension·auto-resolve Found extension "@swamp/digitalocean" (DigitalOcean infrastructure models)
INF extension·auto-resolve Installing "@swamp/digitalocean"@"2026.03.16.1"...
INF extension·auto-resolve Installed "@swamp/digitalocean"@"2026.03.16.1" (32 models registered)
Created: my-droplet (@swamp/digitalocean/droplet)
Full suite
All 3018 tests pass (14 new + 3004 existing), including architecture boundary and DDD layer rule tests.
Verification
deno check— passesdeno lint— passesdeno fmt— passesdeno run test— 3018 passed, 0 faileddeno run compile— binary compiled successfully
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260317.001439.0-sha.adf8dd48/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/v20260317.001439.0-sha.adf8dd48/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/v20260317.001439.0-sha.adf8dd48/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/v20260317.001439.0-sha.adf8dd48/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260316.225105.0-sha.210fdbaf
What's Changed
- fix: resolve dual Zod instance in compiled binary (#724)
Summary
Fixes #723.
In the compiled binary (deno compile), extension bundles with externalized import { z } from "npm:zod@4" resolve to a different Zod module instance than swamp's own embedded Zod. This causes instanceof z.ZodType checks, z.toJSONSchema(), and _zod property lookups to fail — even though both are the same Zod version (4.3.6). Works fine in dev mode (deno run) because Deno resolves both to the same cached module.
Problem
The --external npm:zod@4 flag in deno bundle was meant to make extensions share swamp's Zod instance. In dev mode, the dynamic import("npm:zod@4") in the bundle resolves to the same cached module. But in the compiled binary, deno compile resolves it to a separate module instance, breaking:
schema instanceof z.ZodType→falsez.toJSONSchema(schema)→Cannot read properties of undefined (reading '_zod')- Any cross-instance Zod operations (
.passthrough(),.default(), etc.)
Fix
Post-process extension bundles to replace external Zod imports with references to swamp's Zod instance exposed via globalThis.__swamp_zod:
Before (bundle output):
import { z } from "npm:zod@4";After (rewritten):
const { z } = globalThis.__swamp_zod;Changes
src/domain/models/bundle.ts: AddinstallZodGlobal()(setsglobalThis.__swamp_zodto swamp's Zod module) andrewriteZodImports()(regex replaces Zod imports — handles named, aliased, star imports, versioned/unversioned specifiers). Applied inbundleExtension()for non-selfContained bundles.src/domain/models/user_model_loader.ts: CallinstallZodGlobal()before loading bundles; applyrewriteZodImportsat import-time for old cached bundles.src/domain/drivers/user_driver_loader.ts: Same pattern.src/domain/vaults/user_vault_loader.ts: Same pattern.src/domain/models/bundle_test.ts: 9 new tests — unit tests forrewriteZodImports(named, aliased, star, unversioned, multiple, non-zod untouched, idempotency, single-quoted) and end-to-end test verifyingz.toJSONSchema()works on bundled schemas.
Design decisions
- Rewrite at both bundle-time AND import-time: Bundle-time ensures new cached bundles are correct. Import-time catches old cached bundles that still have raw
importstatements. The rewrite is idempotent. - selfContained bundles are unaffected: They inline Zod and never have external imports.
- No changes to extension author workflow: Extensions still write
import { z } from "npm:zod@4"— the rewrite is transparent.
Alternatives rejected
- Stop externalizing Zod: Bloats every bundle by ~500KB and doesn't fix the problem — swamp's own code still uses its
zforinstanceof/toJSONSchema. - Duck-typing instead of
instanceof: Much larger change surface across 6+ files, fragile, loses Zod's built-in schema capabilities. - Import maps: Don't work in compiled binaries.
Test plan
-
deno check— type checking passes -
deno lint— linting passes -
deno fmt— formatting passes -
deno run test— 3004/3004 tests pass -
deno run compile— binary compiles - Standalone PoC validates approach in both dev mode and compiled binary
- Manual test with compiled binary:
swamp extension pull @dougschaefer/cisco-collaboration-endpoints→swamp model type describeshows JSON schema without_zoderrors
🤖 Generated with Claude Code
Co-authored-by: Douglas Schaefer dougschaefer6@users.noreply.github.com
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260316.225105.0-sha.210fdbaf/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/v20260316.225105.0-sha.210fdbaf/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/v20260316.225105.0-sha.210fdbaf/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/v20260316.225105.0-sha.210fdbaf/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260316.173839.0-sha.1abd500d
What's Changed
- feat: per-model datastore locks for concurrent model operations (#713)
Summary
Replaces the single global datastore lock with per-model locks so that operations on different models can run concurrently. This fixes the core issue where running model method run on one model blocked all other model operations, even on completely unrelated models.
- Introduces per-model lock files at
data/{modelType}/{modelId}/.lock— operations on different models no longer block each other - Refactors the sync coordinator from module-level singletons to a
Map<string, SyncEntry>supporting multiple concurrent locks - Adds model-scoped S3 sync (
pullChangedForModel) so S3 datastores only pull/push the relevant model's files - Extracts model references from workflows to acquire only the locks needed, with automatic fallback to global lock for dynamic CEL references
- Updates breakglass commands (
lock status,lock release --model) to show and manage per-model locks
Closes #706
Why per-model locks?
The global lock was designed for simplicity, but it creates an O(n) bottleneck for fleet operations. When managing 100+ VMs where each method call takes 30-90 seconds, a single global lock means:
- Before: Running
healthCheckonprod-vm-1blocks a quickcheckServiceondev-vm-2— the second command waits or times out - After: Both run concurrently because they acquire separate locks (
data/command/shell/prod-vm-1/.lockanddata/command/shell/dev-vm-2/.lock)
Structural commands (data gc, repo init, model create/delete) still use the global lock, and per-model lock acquisition checks for a held global lock first — so data integrity is preserved.
Impact on users
model method runon different models runs concurrently (the primary win)workflow runacquires only the locks for models referenced in the workflowmodel evaluateandworkflow evaluateuse per-model locks for single-model/workflow evaluation- S3 datastores benefit equally — model-scoped pull on lock acquisition, brief global lock only during push (seconds, not minutes)
- No breaking changes — the lock file format and CLI interface are unchanged; existing repos work without migration
- Deadlock prevention — locks are always acquired in sorted order when multiple models are involved
What changed
| File | Change |
|---|---|
datastore_sync_coordinator.ts |
Refactored to Map<string, SyncEntry> with named lock support |
repo_context.ts |
Added requireInitializedRepoUnlocked(), createModelLock(), acquireModelLocks() |
s3_cache_sync.ts |
Added pullChangedForModel() for model-scoped S3 sync |
model_method_run.ts |
Uses per-model lock instead of global lock |
workflow_run.ts |
Extracts model refs, acquires per-model locks (falls back to global for dynamic refs) |
model_evaluate.ts |
Single-model evaluate uses per-model lock; --all keeps global |
workflow_evaluate.ts |
Single-workflow evaluate uses per-model lock; --all keeps global |
datastore_lock.ts |
lock status scans per-model locks; lock release --model added |
datastore_output.ts |
Shows lock scope (global vs per-model) in status output |
model_reference_extractor.ts |
New: extracts model references from workflows for lock targeting |
Test plan
- All 2968 existing tests pass
-
deno check,deno lint,deno fmtclean - New unit tests for coordinator, repo context helpers, and model reference extractor
- Manual verification: two concurrent
model method runon different models both proceed without blocking, each with its own.lockfile - Binary compiles successfully
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260316.173839.0-sha.1abd500d/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/v20260316.173839.0-sha.1abd500d/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/v20260316.173839.0-sha.1abd500d/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/v20260316.173839.0-sha.1abd500d/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260316.173713.0-sha.6dc69231
What's Changed
- docs: add z.infer type casts to readResource skill examples (#721)
Summary
- Update all
readResource()examples in theswamp-extension-modelskill to usez.infer<typeof Schema>casts instead of accessing properties on untypedRecord<string, unknown> - Fix outdated vault resolution docs in
api.md— vault expressions have been automatically resolved since #712, not "returned as-is" - Add
typealiases (VpcData,DropletData,CustomerData,BucketData) next to each Zod schema so the cast pattern is immediately visible
Why this is the right fix for #716
The issue asks for typed readResource() to eliminate as any casts in extension models. The root cause isn't a missing framework feature — it's that the skill examples Claude uses to generate extension models were accessing readResource() results without type narrowing. Since extension models already define Zod schemas for their resources, z.infer<typeof Schema> derives the TypeScript type at zero runtime cost (erased during bundling). The as cast is safe because data was already validated against that same schema on write.
By fixing the examples, Claude will generate typed code from the start, and extension authors won't hit the lint violations described in #716.
Closes #716
Test plan
- Verify examples in
api.md,examples.md, andscenarios.mdare consistent — everyreadResource()call has a correspondingz.infertype alias and cast - Verify vault resolution docs in
api.mdmatch actual behavior from #712
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260316.173713.0-sha.6dc69231/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/v20260316.173713.0-sha.6dc69231/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/v20260316.173713.0-sha.6dc69231/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/v20260316.173713.0-sha.6dc69231/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260316.163214.0-sha.4955f3d2
What's Changed
- fix: resolve multi-line imports in local_import_resolver (#719)
Summary
- Fix
IMPORT_PATTERNregex inlocal_import_resolver.tsto match across newlines (.*?→[\s\S]*?), resolving #715 - The clover code generator writes multi-line imports (e.g.,
import {\n createResource,\n deleteResource,\n} from "./_lib/aws.ts"), which the previous single-line regex silently skipped - This caused shared dependencies like
_lib/aws.tsto be excluded from extension packages duringswamp extension push, making all@swamp/aws/*extensions fail on pull withModule not founderrors
Changes
src/domain/models/local_import_resolver.ts: ChangedIMPORT_PATTERNfrom/(?:import|export)\s+.*?from\s+["'](\.\.?\/[^"']+)["']/gto/(?:import|export)\s+[\s\S]*?from\s+["'](\.\.?\/[^"']+)["']/gso the regex matches import/export statements that span multiple linessrc/domain/extensions/extension_import_resolver_test.ts: Added test case"resolveLocalImports resolves multi-line imports"that creates a_lib/aws.tsdependency imported via a multi-line import statement and verifies it is correctly discovered
Test plan
- New multi-line import test passes
- All 8 import resolver tests pass
- Full test suite passes (2979 tests)
-
deno check— type checking passes -
deno lint— linting passes -
deno fmt— formatting passes -
deno run compile— binary compiles successfully
Fixes #715
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260316.163214.0-sha.4955f3d2/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/v20260316.163214.0-sha.4955f3d2/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/v20260316.163214.0-sha.4955f3d2/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/v20260316.163214.0-sha.4955f3d2/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260316.161521.0-sha.7fc8a69a
What's Changed
- fix: skip auth check in
extension push --dry-run(#718)
Summary
swamp extension push --dry-runnow works without authentication- Auth loading (step 3) and collective membership validation (step 4) are wrapped in a
!dryRunguard so they only run for real pushes - Unblocks CI validation workflows that don't have swamp-club credentials
What changed
In src/cli/commands/extension_push.ts, the auth check and collective membership validation previously ran unconditionally before the dry-run exit point. Since --dry-run only builds the archive locally and never contacts the registry, authentication is unnecessary.
The credentials variable is now typed as | undefined and only populated when not in dry-run mode. The existing non-dry-run code paths (steps 13 and 17) already have their own if (!options.dryRun) guards, so credentials are never accessed in dry-run mode.
Trade-off
Dry-run will skip collective ownership validation. This is acceptable because:
- Dry-run validates the build, not permissions — its purpose is to verify the extension compiles and packages correctly
- The real push still enforces collective membership — no security bypass
- CI pipelines just need to verify the extension archives correctly without needing registry credentials
Test plan
-
deno checkpasses -
deno lintpasses -
deno fmtpasses -
deno run testpasses -
deno run compilepasses -
swamp extension push <manifest> --dry-runsucceeds without auth -
swamp extension push <manifest>without auth still fails with "Not authenticated"
Fixes #717
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260316.161521.0-sha.7fc8a69a/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/v20260316.161521.0-sha.7fc8a69a/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/v20260316.161521.0-sha.7fc8a69a/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/v20260316.161521.0-sha.7fc8a69a/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260315.010849.0-sha.5a9897f6
What's Changed
- feat: add --global-arg flag to model create command (#714)
Summary
- Adds a repeatable
--global-arg key=valueoption toswamp model create, eliminating the need to manually edit definition YAML to set globalArguments after creation - Reuses the existing
parseKeyValueInputsparser, supporting dot notation for nested objects (config.db.host=localhost), file references (key=@path), and values containing= - Validates provided args against the model type's globalArguments Zod schema (when one exists) at create time, failing early with clear errors
User impact
Before this change, setting globalArguments required a two-step workflow:
swamp model create aws/ec2 my-vpc
swamp model edit my-vpc # manually add globalArguments in YAMLNow it's a single command:
swamp model create aws/ec2 my-vpc \
--global-arg region=us-east-1 \
--global-arg config.timeout=30Nested objects are supported via dot notation:
--global-arg config.db.host=localhost --global-arg config.db.port=5432
# → { config: { db: { host: "localhost", port: "5432" } } }Invalid args are caught immediately:
swamp model create mytype foo --global-arg badformat
# Error: Invalid input format: "badformat". Expected key=value format.Test plan
-
deno check— type checking passes -
deno lint&&deno fmt— clean -
deno run test src/cli/commands/model_create_test.ts— 12 tests pass (9 new) -
deno run test— full suite passes (2978 tests) -
deno run compile— binary compiles - Manual testing: simple args, multiple args, nested dot notation, values with
=, error cases (missing=, empty key), default behavior without flag
Closes #699
🤖 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/v20260315.010849.0-sha.5a9897f6/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/v20260315.010849.0-sha.5a9897f6/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/v20260315.010849.0-sha.5a9897f6/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/v20260315.010849.0-sha.5a9897f6/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260315.000926.0-sha.aa09224f
What's Changed
- fix: resolve vault expressions in readResource() (#712)
Closes #710
Summary
- Add
resolveVaultRefsInData()to transparently resolve vault expression strings (${{ vault.get('...', '...') }}) back to their original secret values when reading resource data viareadResource() - Wire
VaultServiceandSecretRedactorfrom the execution context intocreateResourceReader()inRawExecutionDriver - Add runtime validation that
JSON.parsereturns a plain object (not an array or primitive), fixing a type safety gap where theRecord<string, unknown>return type was not enforced
Why this is needed
When extension models write resource data with sensitive fields, processSensitiveResourceData() replaces actual values with vault expression strings like ${{ vault.get('vault-name', 'key') }}. Before this change, readResource() returned these as literal strings, which meant:
- Leaked vault internals — extension authors had to know about and parse vault expression syntax
- Confusing errors — code expecting an API key got a template string instead, causing opaque failures downstream
- Broken round-trips — write a secret, read it back, get something completely different
How it works
- After JSON parsing,
resolveVaultRefsInData()recursively walks all string values in the result object - Strings matching the vault ref pattern (anchored regex — must be the entire string) are resolved via
VaultService.get() - Resolved secrets are registered with
SecretRedactor.addSecret()to prevent log leakage - Without a
VaultService, behavior is unchanged (backward compatible) - The JSON parse type safety fix ensures corrupted/manually-edited data files fail fast with a clear error instead of silently returning wrong types
Files changed
| File | Change |
|---|---|
src/domain/models/data_writer.ts |
Add resolveVaultRefsInData(), update createResourceReader() signature and implementation |
src/domain/drivers/raw_execution_driver.ts |
Pass vaultService and redactor to createResourceReader() |
src/domain/models/model.ts |
Update JSDoc to reflect new behavior |
src/domain/models/data_writer_test.ts |
10 new tests covering vault resolution, backward compat, type safety |
Test plan
-
deno check— type checking passes -
deno lint— no lint errors -
deno fmt— properly formatted -
deno run test— all 2967 tests pass (48 in data_writer_test.ts) -
deno run compile— binary compiles successfully
🤖 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/v20260315.000926.0-sha.aa09224f/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/v20260315.000926.0-sha.aa09224f/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/v20260315.000926.0-sha.aa09224f/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/v20260315.000926.0-sha.aa09224f/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260314.235538.0-sha.0cbfc0e8
What's Changed
- feat: add readResource() to MethodContext (#711)
Summary
- Add symmetric
readResource(instanceName, version?)convenience method toMethodContext, eliminating theJSON.parse(new TextDecoder().decode(context.dataRepository.getContent(...)))boilerplate every extension model repeats when reading stored data - Create
createResourceReader()factory indata_writer.tsalongside the existingcreateResourceWriter()for symmetry - Wire
readResourceinto the raw execution driver and add a stub in the Docker runner (returnsnullwith warning since containers lack host data access) - Update all extension model docs (API reference, SKILL.md, examples, scenarios) to use
readResourceas the preferred read path
User impact
Extension model authors can now read stored resource data with one call:
// Before (every model repeated this)
const content = await context.dataRepository.getContent(
context.modelType, context.modelId, "vpc");
if (!content) throw new Error("...");
const data = JSON.parse(new TextDecoder().decode(content));
// After
const data = await context.readResource!("vpc");
if (!data) throw new Error("...");Vault reference expressions are returned as-is (not resolved). The low-level getContent() remains available for binary data.
Test plan
- 4 unit tests for
createResourceReader(happy path, null for missing data, version passthrough, vault reference preservation) - 1 test verifying
readResourceis available on context during method execution -
deno checkpasses -
deno lintpasses -
deno fmtpasses - Full test suite passes (2957 tests, 0 failures)
-
deno run compilesucceeds
🤖 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/v20260314.235538.0-sha.0cbfc0e8/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/v20260314.235538.0-sha.0cbfc0e8/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/v20260314.235538.0-sha.0cbfc0e8/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/v20260314.235538.0-sha.0cbfc0e8/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260313.235152.0-sha.bb94615c
What's Changed
- fix: add 'install' as alias for 'extension pull' (#704)
Summary
- Add
.alias("install")to theextension pullcommand soswamp extension installworks as expected - AI agents naturally try
swamp extension installwhich previously failed with a confusing error
Closes #702
Test plan
-
deno checkpasses -
deno lintpasses -
swamp extension --helpshowspull, installin the command list -
swamp extension install <ext>routes to the pull handler
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260313.235152.0-sha.bb94615c/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/v20260313.235152.0-sha.bb94615c/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/v20260313.235152.0-sha.bb94615c/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/v20260313.235152.0-sha.bb94615c/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/