ci: add JSR publish workflow for @systeminit/swamp-lib#893
ci: add JSR publish workflow for @systeminit/swamp-lib#893johnrwatson wants to merge 9 commits intomainfrom
Conversation
Introduces a WebSocket API server for remote workflow and model method execution, plus a standalone TypeScript client published to JSR as @systeminit/swamp-lib. - `swamp serve --port --host --repo-dir` starts an HTTP+WS server - Multiplexed request/response protocol with cancellation support - Per-model locking for concurrent workflow isolation - Client supports callback-based and AsyncIterable streaming APIs - Health check endpoint at GET /health Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ethod_output events Wraps method.execute() with console interception so that extension models using console.log have their output streamed through the event system, matching the behavior of out-of-process drivers. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The withConsoleCapture wrapper on the raw driver path caused concurrent forEach iterations to stomp on each other's console references. Removing it — in-process console.log output flows to the host process stdout which is sufficient for swamp serve. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ty warning - Fix libswamp import boundary: serve modules now import from src/libswamp/mod.ts instead of internal paths - Add AGPLv3 license headers to all packages/client/*.ts files - Add security warning when binding to non-loopback addresses - Remove bare console.log from onListen (logger.info covers it) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- serializer_test.ts: serializeEvent, serializeSwampError, jsonSafeClone - protocol_test.ts: type validation, discriminated unions, JSON round-trips - stream_test.ts: SwampClientError, withDefaults, consumeStream, result - client_test.ts: connect/close lifecycle, workflowRun, error handling, AsyncIterable streaming, socket close rejection Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ests
- Add Zod validation for incoming WebSocket messages (rejects malformed
payloads at the boundary instead of crashing deep in libswamp)
- Fix information disclosure: unknown request types no longer echo
user-supplied input in error messages
- Add JSON output mode for swamp serve (emits structured
{"status":"listening",...} and {"status":"stopped"} events)
- Add connection_test.ts (13 tests: validation, dispatch, cancel, dupes)
- Add serve_test.ts (5 tests: command name, options, description)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Publishes packages/client to JSR on merge to main when files in packages/client/ change. Uses OIDC token auth (no secrets needed). Version is auto-generated from date + commit count. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
CI Security Review
Critical / High
None.
Medium
.github/workflows/publish-client.yml:32,36—actions/checkout@v4anddenoland/setup-deno@v2are pinned by tag, not full commit SHA. A compromised tag could inject malicious code into the publish pipeline. Both are from trusted publishers (GitHub and Deno), which lowers the risk. Suggested fix: Pin to full SHAs (e.g.,actions/checkout@<sha>) for defense-in-depth, or accept the risk given the trusted publishers.
Low
.github/workflows/publish-client.yml:18-19— Permissions are declared at workflow level rather than job level. Since there is only one job, this has no practical security impact, but job-level scoping is the recommended pattern for maintainability as jobs are added.
Verdict
PASS — No critical or high severity findings. The workflow uses safe triggers (pull_request on closed, not pull_request_target), checks out main (not PR HEAD), has no LLM prompt injection surface, uses OIDC for secretless JSR publishing, and interpolates only self-generated data. The medium finding on SHA pinning is a hardening recommendation, not a blocking issue.
There was a problem hiding this comment.
CLI UX Review
Blocking
None.
Suggestions
-
serve.ts:113 —
logger.info("Shutting down...")runs in both log and JSON modes (noelse). In JSON mode this likely goes to stderr via LogTape so it's harmless, but addingelsewould make the intent explicit and match the pattern used in theonListenblock above. -
serve.ts:53-56 — The non-loopback security warning uses
logger.warnwhich in JSON mode goes to stderr rather than stdout. Consider also emitting a JSON warning object to stdout (e.g.{"warning": "binding to non-loopback address, no auth enforced", "host": ...}) so scripted consumers can detect it.
Verdict
PASS — serve command follows existing flag conventions (--repo-dir, consistent with other commands), both log and JSON output modes are properly handled, defaults are sensible (port 9090, host 127.0.0.1), and help text is clear.
There was a problem hiding this comment.
Code Review
Blocking Issues
None.
Suggestions
-
Layering:
src/serve/connection.ts:44imports from../cli/repo_context.ts— Thesrc/serve/module importsacquireModelLocksfrom the CLI layer. From a DDD perspective,src/serve/is an application-level boundary (likesrc/cli/) and should not depend on sibling application layers. Consider extractingacquireModelLocksintosrc/domain/orsrc/infrastructure/so bothsrc/cli/andsrc/serve/can depend on it without circular layering. Not blocking since it's functional and the function has no CLI-specific dependencies, but it creates an unfortunate dependency direction. -
no-explicit-anycount inpackages/client/client.ts— There are 4deno-lint-ignore no-explicit-anydirectives in client.ts. These are understandable given the genericPendingRequestpattern, but theevent.error as anycast at line 358 could be tightened toas { code?: string; message?: string; details?: unknown }to avoid the suppression. -
packages/client/deno.jsonmissingimportsfor@std/assert— The test files import@std/assertwhich resolves through the workspace root. This works for development, but when the package is consumed standalone (or tests are run in thepackages/client/directory outside the workspace), the dependency won't resolve. Consider adding adevImportsorimportsentry if JSR supports it, or document that tests must be run from the workspace root. -
Protocol type duplication —
packages/client/protocol.tsandsrc/serve/protocol.tsdefine the same wire types independently. This is documented as intentional (zero CLI dependency for the published client), which is a reasonable tradeoff. Consider adding a comment insrc/serve/protocol.tsnoting the client-side mirror exists, so future changes to the server protocol prompt an update to the client types as well. -
src/serve/connection.ts:44—acquireModelLocksimport from../cli/— Already covered in suggestion 1, but worth noting this also meanssrc/serve/tests may transitively pull in CLI initialization code, which could make tests slower or more fragile.
Overall: Well-structured PR with good test coverage across all new modules. License headers present on all files. libswamp imports are correct (via mod.ts). The AnyOptions pattern follows existing codebase conventions. Security considerations are handled (loopback default, non-loopback warning). The WebSocket protocol design with multiplexed request IDs, cancellation support, and Zod validation is solid.
Summary
Adds a GitHub Actions workflow that publishes the
@systeminit/swamp-libclient package to JSR when files inpackages/client/change on merge to main.0.YYYYMMDD.Nworkflow_dispatchfor manual publishingTest plan
packages/client/changesdeno publishsucceeds with OIDC auth🤖 Generated with Claude Code