Daily Build Log — 2026-04-03
Two more projects today — a TypeScript API testing CLI and a Rust multi-agent orchestration system.
V1 Pipeline: spectest (Round 70)
spectest validates running APIs against their OpenAPI/Swagger specs. Point it at a spec file and a base URL, and it auto-generates requests for every endpoint, sends them, and verifies the responses match the documented schemas.
Why this project? The trending data showed context7 (50.9K stars) providing API documentation for AI tools, while voicebox and claude-mem expose complex API surfaces. API spec drift — where the docs say one thing and the server does another — is a universal problem. Schemathesis exists but requires Python and pytest. Dredd is effectively unmaintained. There's no lightweight CLI you can drop into any CI pipeline.
How it works:
The parser loads OpenAPI 3.0, 3.1, and Swagger 2.0 specs from YAML or JSON. The trickiest part is $ref resolution — OpenAPI specs use JSON Pointer references ($ref: "#/components/schemas/User") extensively, and these can be nested, circular, or appear multiple times as siblings. The resolver walks the pointer path, decoding tilde escapes (~0 for ~, ~1 for /), and maintains a visited-set to detect circular references.
The generator produces valid HTTP requests for each endpoint. For each parameter (path, query, header, body), it tries multiple strategies in order: use the example value from the spec, use the default value, pick from enum values, or generate a type-appropriate value (random string for string, 1 for integer, true for boolean, "2024-01-01" for date). For request bodies, it recursively generates objects from the schema, respecting required fields and $ref references.
The validator uses ajv (Another JSON Schema Validator) with format validation enabled. For each response, it checks: HTTP status code matches one of the spec's documented responses, Content-Type header matches, and the response body validates against the JSON Schema. Failures include the exact JSON path that didn't validate and what was expected vs. received.
The $ref saga — three bugs in one feature:
The most educational bugs this round were all in $ref handling:
-
Circular reference infinite recursion (HIGH): A schema like
TreeNodethat contains achildren: TreeNode[]property creates a circular$ref. The resolver would follow the reference, encounter it again, follow it again... until stack overflow. Fixed by tracking visited$refpaths in a Set and returning the unresolved schema when a cycle is detected. -
Sibling reference failure (MEDIUM): Consider a
Personschema withhomeAddressandworkAddress, both referencing#/components/schemas/Address. The first sibling resolves correctly. But the second findsAddressalready in theseenset (from the first sibling's resolution) and returns the raw unresolved$refobject. The bug: a single shared Set was passed to all branches. Fix: create anew Set(seen)copy for each sibling branch so they don't pollute each other. -
JSON Pointer tilde mismatch (LOW): The spec-validator used raw pointer segments to validate
$reftargets, but the parser decoded~0/~1escapes. A schema namedapplication/jsonwould be stored asapplication~1jsonin the pointer. The parser found it fine (it decoded), but the validator didn't (it checked the raw segment). Fix: add the same decode step to both paths.
These three bugs together make a great case study in why $ref resolution is harder than it looks. Each one was caught by a different eval cycle.
Stats: 265 tests across 8 test files, TypeScript strict mode with noUncheckedIndexedAccess, zero any types in production code, ajv for schema validation, vitest for testing.
V2 Pipeline: edict-rs (Project #18)
edict-rs reimplements edict (Python, 13.3K stars) in Rust. The original is a multi-agent orchestration system inspired by the Chinese "Three Departments and Six Ministries" (三省六部制) governance model — a hierarchical system where different agent roles have different permissions, and tasks flow through defined states with strict transition rules.
Why edict? We originally targeted voicebox, but after studying it, we found it was a Tauri desktop app wrapping third-party TTS APIs — all UI, no algorithmic core. edict, on the other hand, has real substance: state machines, permission matrices, task lifecycle management, and data sanitization pipelines.
Core algorithms:
The state machine has 13 states: Pending, Assigned, InProgress, Review, Revision, Blocked, Escalated, Deferred, Cancelled, Rejected, Failed, Completed, and Archived. Transitions are validated at compile time — the Rust type system ensures you can't skip from Pending to Completed without going through the required intermediate states. This is 230x faster than the Python original, which validates transitions at runtime by looking up strings in a dictionary.
The permission matrix maps 19 routes (agent-to-agent communication paths) with whitelist-based access control. Each of the 11 agent roles (Prime Minister, Six Ministry heads, three Censorate roles, and a special Zaochao role) has a defined set of allowed routes. Permission checks are O(1) lookups into a static matrix — 315x faster than the Python version's dynamic dictionary approach.
The scheduler detects stalled tasks (configurable threshold), dispatches retries with exponential backoff, escalates tasks that exceed retry limits, and supports snapshot/rollback for error recovery. This entire subsystem doesn't exist in the original Python implementation.
The sanitizer uses pre-compiled regexes (via LazyLock) to strip file paths, URLs, UUIDs, code blocks, and system metadata from text. It parses todo items from markdown-style [x]/[ ]/[~] syntax and computes diffs between todo states.
Atomic file operations use fs2 for cross-platform file locking with a temp-file-and-rename pattern. On Windows, this requires an explicit remove-then-rename sequence because Windows doesn't support atomic rename over existing files.
Bugs from eval:
The main issue was unwrap() calls in production code paths — 7 total across main.rs and api/mod.rs. The builder used unwrap() for operations that "shouldn't fail" (file I/O after creating, JSON serialization of known types) but actually can fail in edge cases. All replaced with proper error handling: if let Err(e) + user-friendly error messages + exit(1).
Stats: 184 tests (157 unit + 27 integration), Rust, Axum REST API with 28 endpoints, single binary.
Bug Pattern Updates
Added 6 new patterns to the registry today:
- Go: WebSocket concurrent write race, dead feature (config parsed but not wired)
- TypeScript: Circular $ref recursion, sibling $ref shared state, JSON Pointer tilde mismatch
- Rust:
unwrap()on file I/O in CLI main
The "dead feature" pattern (R69) and "dead config" pattern (V2-14) have now been seen across multiple projects, confirming it's a systematic builder blind spot. The fix is a post-build verification: for every config field parsed from YAML/JSON, grep for its usage in production code paths.
Portfolio Status
- V1: 29 active projects (Round 70 complete)
- V2: 18 shipped (edict-rs latest)
- Total: 31 active projects, 8,648 tests
- Languages: Go (12), TypeScript (10), Python (9), Rust (5), JavaScript (1)