OpenAPI 3 mock server built with Deno. Validates SDKs against OpenAPI specs with clear error attribution (SDK bug vs spec issue).
**ALWAYS use the scripts in scripts/ directory.
./scripts/bootstrap # Install dependencies, setup environment
./scripts/test # Run all tests
./scripts/lint # Lint code
./scripts/format # Format codeThese scripts handle all the necessary flags and environment setup automatically.
steady/
├── cmd/steady.ts # CLI entry point
├── src/ # Main server
│ ├── server.ts # HTTP server, request matching
│ ├── validator.ts # Request/response validation
│ ├── errors.ts # Error types
│ └── logging/ # Request logging utilities
├── packages/
│ ├── json-pointer/ # RFC 6901 implementation
│ │ ├── json-pointer.ts # resolve(), set(), escape/unescape
│ │ ├── rfc6901-validator.ts # Syntax validation
│ │ └── resolver.ts # Document reference resolver
│ ├── json-schema/ # JSON Schema 2020-12
│ │ ├── processor.ts # Schema analysis
│ │ ├── runtime-validator.ts # Data validation
│ │ ├── schema-registry.ts # Document-centric schema resolution
│ │ └── ref-resolver.ts # $ref resolution
│ └── openapi/ # OpenAPI 3.x parser
│ └── parser.ts # YAML/JSON parsing
├── sdk-tests/ # SDK test suites with their OpenAPI specs
│ └── cloudflare-python/ # Cloudflare spec at openapi-spec.yml
├── tests/edge-cases/ # Edge case tests
└── test-fixtures/
└── openapi-directory/ # Git submodule: 1970 real-world specs (99.5% pass)
Test specs: Real-world specs live in sdk-tests/ (e.g.,
sdk-tests/cloudflare-python/openapi-spec.yml). When looking for a spec file,
search the whole repo with glob, not just test-fixtures/.
Submodule: Run git submodule update --init to fetch test fixtures.
Stack: Deno 2.x, TypeScript strict mode, no frameworks
JSON Pointer (RFC 6901):
- Only
~0(tilde) and~1(slash) escaping - NO percent encoding - Percent-decoding happens at URI fragment layer (ref-resolver.ts:171)
- Array indices must be exact: "0", "1", "10" - reject "01", "1.5", "-1"
JSON Schema: 91.6% compliance (1151/1257 tests). Missing: $dynamicRef,
$dynamicAnchor. Full support for unevaluatedProperties/unevaluatedItems.
$ref Resolution: Handles URI fragment encoding. #/$defs/User%20Nameresolves
to key"User Name" (percent-decoded before JSON Pointer parsing).
- Read before modify - Never change code you haven't read
- No type hacks - No
as, no!assertions to silence errors - No silent failures - Never swallow errors or return fake success
- Test with red-green - Write failing test first, then fix
- Fail loudly - Invalid input = error, not silent pass
- No hacky solutions - Use standard libraries (e.g.,
@std/clifor arg parsing), don't reinvent the wheel with brittle manual implementations - No chained bash commands - When running commands (not writing scripts),
never use
|,&&, or||. Run each command as a separate, distinct invocation - No em-dashes - Never use
—(U+2014) anywhere in the codebase: comments, strings, messages, or docs. Em-dashes are non-ASCII and cause ByteString crashes when diagnostic messages flow into HTTP headers viaHeaders.set(). Use normal punctuation instead (periods, commas, semicolons). Two sentences are better than one long sentence joined by an em-dash.
Review a design/spec document by finding real-world patterns that stress it. Use
with: /design-review docs/diagnostics-spec.md
See .claude/skills/design-review/SKILL.md for full process.
Project context and working conventions. Read this before making changes.
See .claude/skills/steady-dev/SKILL.md for architecture, design philosophy,
and common task guides.
Simulate being a real SDK developer to find UX friction in Steady. Use with:
/user-experiment sink-python
See .claude/skills/user-experiment/SKILL.md for the full methodology.
INVESTIGATE BEFORE IMPLEMENTING: Always research the correct behavior first.
- Research specifications - Check OpenAPI spec, RFCs, and official docs
- Test actual behavior - Run code to see what happens, don't assume
- Verify assumptions - If uncertain, write a test to confirm behavior
- No hand-waving comments - Don't add comments like "intentional" or "future use" without verifying the behavior is correct
- No ignore flags as shortcuts - Never mark tests as
ignore: truewithout first verifying if the feature actually works
When behavior is undefined by spec:
- Research how PHP, Rails, Node.js, and other frameworks handle it
- Document the actual behavior differences
- Make an informed decision and document why
- Consider making it configurable if behavior varies significantly
Comments must be accurate:
- Don't claim behavior is "intentional" unless you verified it's correct
- Don't claim something is "for future use" - either use it or remove it
- Comments should explain WHY, not paper over uncertainty
RED-GREEN TESTING IS MANDATORY: Always write a failing test BEFORE fixing any issue - bugs, behavioral changes, or improvements found in code review.
- Write a test that exposes the issue (should fail - RED)
- Run the test to confirm it fails
- Implement the fix
- Run the test to confirm it passes (GREEN)
- Run all tests to ensure no regressions
This applies to:
- Bug fixes
- Behavioral changes (e.g., fixing RNG determinism)
- Edge case handling improvements
- Any change that modifies observable behavior
# Run all tests
./scripts/test
# Run specific test file
./scripts/test packages/json-pointer/json-pointer.test.ts
# Run with filter
./scripts/test --filter "RFC 6901"Tests must pass before committing. Use ./scripts/test to verify.
Use Deno's built-in snapshot testing for asserting complex structured output.
File snapshots (assertSnapshot): Store reference values in
__snapshots__/{test_file}.snap next to the test file. Requires
Deno.TestContext as the first argument.
import { assertSnapshot } from "@std/testing/snapshot";
Deno.test("example", async (t) => {
await assertSnapshot(t, { hello: "world", n: 123 });
});Inline snapshots (assertInlineSnapshot): Store the expected value directly
in the test source as a template literal string. No TestContext needed.
import { assertInlineSnapshot } from "@std/testing/unstable-snapshot";
Deno.test("example", () => {
assertInlineSnapshot(
{ hello: "world", n: 123 },
`
{
hello: "world",
n: 123,
}
`,
);
});Updating snapshots: Run tests with -- --update to regenerate snapshots
that have changed. For inline snapshots, the test source file itself is
rewritten in place (requires --allow-read, --allow-write, --allow-run).
Pass -- --update --no-format to skip deno fmt after updating inline
snapshots.
./scripts/test -- --updateWhen to use which: Prefer assertSnapshot (file-based) for large or
frequently changing output. Prefer assertInlineSnapshot for small values where
seeing the expected value next to the assertion is clearer. Both are already
used in this codebase (see tests/integration/).
Errors must include:
- WHAT failed (specific validation/parsing error)
- WHERE (file:line or JSON path)
- WHY (root cause)
- HOW to fix (actionable suggestion)
fix: Description of bug fix
feat: New feature
docs: Documentation only
test: Test additions/changes
refactor: Code restructuring
Working:
- HTTP server with path matching
- JSON Schema validation (runtime-validator.ts)
- Response generation from schemas/examples
- RFC 6901 JSON Pointer operations
- OpenAPI 3.x parsing
Test coverage gaps:
- response-generator.ts (limited tests)