From 654ae60a2687e1ef9661064e4072035200ee35c7 Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Thu, 5 Feb 2026 17:53:19 -0800 Subject: [PATCH 1/5] test(alfred-live): add JSONL fuzz + alfredctl integration tests --- alfred-live/CHANGELOG.md | 7 ++ alfred-live/test/unit/alfredctl.test.js | 55 +++++++++ .../test/unit/command-envelope.test.js | 114 ++++++++++++++++++ 3 files changed, 176 insertions(+) create mode 100644 alfred-live/test/unit/alfredctl.test.js diff --git a/alfred-live/CHANGELOG.md b/alfred-live/CHANGELOG.md index 91eade3..5ef0e1d 100644 --- a/alfred-live/CHANGELOG.md +++ b/alfred-live/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added + +- JSONL command channel fuzz-style test for randomized junk input handling. +- `alfredctl` JSONL output integration test against the command channel. + ## [0.10.0] - 2026-02-04 ### Added diff --git a/alfred-live/test/unit/alfredctl.test.js b/alfred-live/test/unit/alfredctl.test.js new file mode 100644 index 0000000..6ad130e --- /dev/null +++ b/alfred-live/test/unit/alfredctl.test.js @@ -0,0 +1,55 @@ +import { describe, it, expect } from 'vitest'; +import { spawnSync } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; + +import { Adaptive } from '../../src/adaptive.js'; +import { CommandRouter } from '../../src/router.js'; +import { ConfigRegistry } from '../../src/registry.js'; +import { executeCommandLine } from '../../src/command-envelope.js'; + +const alfredctlPath = fileURLToPath(new URL('../../bin/alfredctl.js', import.meta.url)); + +function runAlfredctl(args) { + return spawnSync(process.execPath, [alfredctlPath, ...args], { + encoding: 'utf8', + }); +} + +describe('alfredctl', () => { + it('emits JSONL commands executable by the command channel', () => { + const output = runAlfredctl(['write', 'retry/count', '5', '--id', 'cmd-1']); + + expect(output.status).toBe(0); + expect(output.stdout).not.toBe(''); + + const line = output.stdout.trim(); + const envelope = JSON.parse(line); + expect(envelope).toEqual({ + id: 'cmd-1', + cmd: 'write_config', + args: { path: 'retry/count', value: '5' }, + }); + + const registry = new ConfigRegistry(); + const retryCount = new Adaptive(3); + registry.register('retry/count', retryCount, { + parse: (value) => { + const parsed = Number(value); + if (!Number.isFinite(parsed)) { + throw new Error('retry/count must be numeric'); + } + return parsed; + }, + format: (value) => value.toString(), + }); + + const router = new CommandRouter(registry); + const resultLine = executeCommandLine(router, line); + expect(resultLine.ok).toBe(true); + if (!resultLine.ok) return; + const result = JSON.parse(resultLine.data); + expect(result.ok).toBe(true); + expect(result.data.path).toBe('retry/count'); + expect(result.data.formatted).toBe('5'); + }); +}); diff --git a/alfred-live/test/unit/command-envelope.test.js b/alfred-live/test/unit/command-envelope.test.js index 53adce4..09bcea0 100644 --- a/alfred-live/test/unit/command-envelope.test.js +++ b/alfred-live/test/unit/command-envelope.test.js @@ -13,6 +13,38 @@ function parseJsonLine(line) { return JSON.parse(line); } +function mulberry32(seed) { + let state = seed >>> 0; + return () => { + state += 0x6d2b79f5; + let result = Math.imul(state ^ (state >>> 15), 1 | state); + result ^= result + Math.imul(result ^ (result >>> 7), 61 | result); + return ((result ^ (result >>> 14)) >>> 0) / 4294967296; + }; +} + +function randomInt(rand, min, max) { + return Math.floor(rand() * (max - min + 1)) + min; +} + +function randomString(rand, length) { + const alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_/:.*'; + let value = ''; + for (let i = 0; i < length; i += 1) { + value += alphabet[randomInt(rand, 0, alphabet.length - 1)]; + } + return value; +} + +function randomPath(rand) { + const segments = randomInt(rand, 1, 4); + const parts = []; + for (let i = 0; i < segments; i += 1) { + parts.push(randomString(rand, randomInt(rand, 1, 8))); + } + return parts.join('/'); +} + describe('command envelope', () => { it('round-trips encode/decode', () => { const envelope = { @@ -86,4 +118,86 @@ describe('command envelope', () => { expect(result.ok).toBe(false); expect(result.error.code).toBe('INVALID_COMMAND'); }); + + it('handles randomized JSONL and junk input without throwing', () => { + const registry = new ConfigRegistry(); + const counter = new Adaptive(1); + registry.register('retry/count', counter, { + parse: (value) => { + const parsed = Number(value); + if (!Number.isFinite(parsed)) { + throw new Error('retry/count must be numeric'); + } + return parsed; + }, + format: (value) => value.toString(), + }); + const router = new CommandRouter(registry); + + const rand = mulberry32(0xade1f00d); + const generators = [ + (i) => + JSON.stringify({ + id: `cmd-${i}`, + cmd: 'read_config', + args: { path: rand() > 0.6 ? 'retry/count' : randomPath(rand) }, + }), + (i) => + JSON.stringify({ + id: `cmd-${i}`, + cmd: 'write_config', + args: { path: rand() > 0.6 ? 'retry/count' : randomPath(rand), value: '5' }, + }), + (i) => + JSON.stringify({ + id: `cmd-${i}`, + cmd: 'list_config', + args: rand() > 0.5 ? { prefix: randomPath(rand) } : {}, + }), + (i) => + JSON.stringify({ + id: '', + cmd: 'read_config', + args: { path: randomPath(rand) }, + }), + (i) => + JSON.stringify({ + id: `cmd-${i}`, + cmd: 'read_config', + args: { path: 123 }, + }), + (i) => + JSON.stringify({ + id: `cmd-${i}`, + cmd: 'read_config', + args: { path: 'retry/count', extra: true }, + }), + (i) => + JSON.stringify({ + id: `cmd-${i}`, + cmd: 'unknown', + args: {}, + }), + () => JSON.stringify({ foo: 'bar' }), + () => JSON.stringify([1, 2, 3]), + () => JSON.stringify(42), + () => '{ "bad": ', + () => '{', + () => 'not-json', + () => '', + () => ' ', + ]; + + for (let i = 0; i < 1000; i += 1) { + const generator = generators[randomInt(rand, 0, generators.length - 1)]; + const line = generator(i); + const resultLine = executeCommandLine(router, line); + expect(resultLine.ok).toBe(true); + if (!resultLine.ok) continue; + const result = parseJsonLine(resultLine.data); + expect(typeof result.id).toBe('string'); + expect(result.id.length).toBeGreaterThan(0); + expect(typeof result.ok).toBe('boolean'); + } + }); }); From a6e0e5645af6eb3396cf35f0f0ab3bfbe6e176ec Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Thu, 5 Feb 2026 17:55:39 -0800 Subject: [PATCH 2/5] fix(alfred-live): satisfy lint in fuzz test --- alfred-live/test/unit/command-envelope.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alfred-live/test/unit/command-envelope.test.js b/alfred-live/test/unit/command-envelope.test.js index 09bcea0..5b4dbb3 100644 --- a/alfred-live/test/unit/command-envelope.test.js +++ b/alfred-live/test/unit/command-envelope.test.js @@ -154,7 +154,7 @@ describe('command envelope', () => { cmd: 'list_config', args: rand() > 0.5 ? { prefix: randomPath(rand) } : {}, }), - (i) => + (_i) => JSON.stringify({ id: '', cmd: 'read_config', From 69add6d45b3337b351fe424594e6cb6632fe6406 Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Thu, 5 Feb 2026 17:57:52 -0800 Subject: [PATCH 3/5] release: bump to 0.10.1 --- CHANGELOG.md | 13 +++++++++++++ README.md | 2 +- alfred-live/CHANGELOG.md | 2 +- alfred-live/jsr.json | 2 +- alfred-live/package.json | 4 ++-- alfred/CHANGELOG.md | 6 ++++++ alfred/jsr.json | 2 +- alfred/package.json | 2 +- 8 files changed, 26 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f8d728..ee6f3cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.10.1] - 2026-02-06 (@git-stunts/alfred) + +### Changed + +- Version bump to keep lockstep alignment with the Alfred package family (no API changes). + ## [0.10.0] - 2026-02-04 (@git-stunts/alfred) ### Changed @@ -168,6 +174,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.10.1] - 2026-02-06 (@git-stunts/alfred-live) + +### Added + +- JSONL command channel fuzz-style test for randomized junk input handling. +- `alfredctl` JSONL output integration test against the command channel. + ## [0.10.0] - 2026-02-04 (@git-stunts/alfred-live) ### Added diff --git a/README.md b/README.md index 0993724..280bdc7 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Alfred is a **policy engine** for async resilience: composable, observable, test ## Latest Release -`v0.10.0` (2026-02-04) — JSONL command channel + `alfredctl` CLI for the control plane. +`v0.10.1` (2026-02-06) — JSONL command channel test coverage improvements. ## Package Badges diff --git a/alfred-live/CHANGELOG.md b/alfred-live/CHANGELOG.md index 5ef0e1d..9724bf3 100644 --- a/alfred-live/CHANGELOG.md +++ b/alfred-live/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [0.10.1] - 2026-02-06 ### Added diff --git a/alfred-live/jsr.json b/alfred-live/jsr.json index 5833f38..f2a7056 100644 --- a/alfred-live/jsr.json +++ b/alfred-live/jsr.json @@ -1,6 +1,6 @@ { "name": "@git-stunts/alfred-live", - "version": "0.10.0", + "version": "0.10.1", "description": "In-memory control plane for Alfred: adaptive values, config registry, command router.", "license": "Apache-2.0", "exports": { diff --git a/alfred-live/package.json b/alfred-live/package.json index 33e7d57..6854949 100644 --- a/alfred-live/package.json +++ b/alfred-live/package.json @@ -1,6 +1,6 @@ { "name": "@git-stunts/alfred-live", - "version": "0.10.0", + "version": "0.10.1", "description": "In-memory control plane for Alfred: adaptive values, config registry, command router.", "type": "module", "sideEffects": false, @@ -16,7 +16,7 @@ "alfredctl": "./bin/alfredctl.js" }, "dependencies": { - "@git-stunts/alfred": "0.10.0" + "@git-stunts/alfred": "0.10.1" }, "engines": { "node": ">=20.0.0" diff --git a/alfred/CHANGELOG.md b/alfred/CHANGELOG.md index 9d8e364..5380046 100644 --- a/alfred/CHANGELOG.md +++ b/alfred/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.10.1] - 2026-02-06 + +### Changed + +- Version bump to keep lockstep alignment with the Alfred package family (no API changes). + ## [0.10.0] - 2026-02-04 ### Changed diff --git a/alfred/jsr.json b/alfred/jsr.json index aae75e2..f944604 100644 --- a/alfred/jsr.json +++ b/alfred/jsr.json @@ -1,6 +1,6 @@ { "name": "@git-stunts/alfred", - "version": "0.10.0", + "version": "0.10.1", "description": "Production-grade resilience patterns for async ops: retry/backoff+jitter, circuit breaker, bulkhead, timeout.", "license": "Apache-2.0", "exports": { diff --git a/alfred/package.json b/alfred/package.json index 0699cd5..8bb2b4d 100644 --- a/alfred/package.json +++ b/alfred/package.json @@ -1,6 +1,6 @@ { "name": "@git-stunts/alfred", - "version": "0.10.0", + "version": "0.10.1", "description": "Production-grade resilience patterns for async ops: retry/backoff+jitter, circuit breaker, bulkhead, timeout.", "type": "module", "sideEffects": false, From 71b54cb4080eac7cb090c2f397637289d4a48568 Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Thu, 5 Feb 2026 17:59:19 -0800 Subject: [PATCH 4/5] chore: update pnpm lockfile --- pnpm-lock.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c182906..548098c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,7 +29,7 @@ importers: alfred-live: dependencies: '@git-stunts/alfred': - specifier: 0.10.0 + specifier: 0.10.1 version: link:../alfred devDependencies: '@eslint/js': From 1f36535e7a48e806ca4c61555430563541db91a3 Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Thu, 5 Feb 2026 18:00:34 -0800 Subject: [PATCH 5/5] chore: link workspace packages for pnpm --- .npmrc | 1 + 1 file changed, 1 insertion(+) create mode 100644 .npmrc diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..30ab299 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +link-workspace-packages=true