fix: harden engine, enrich failure diagnostics, close adoption gaps
- P0: CLI verify now honors test budget with seeded multi-sample - P0: Observe sampling enforced via Math.random() gate in hook-validator - P1: Remove misleading undici-mock-agent isolation option - P1: Qualify reuses shared discoverRouteDetails() with warnings - P1: Chaos/scenario config exposed via preset schema - P1: README/docs limitations updated to current state - P2: Nested response annotations prefer 2xx deterministically - P2: --changed documented as heuristic in verify.md - Add observe sink tests (sampling 0/1, sink failure non-interference) - Add verify runs regression tests (scale, determinism, variants) - Add configured-scenario qualify test (independent of OAuth fixture) - Add coverageBreakdown to qualify artifacts (per-gate route coverage) - Add production-style observe example with real sink in docs/observe.md - Add nightly/staging vs PR gating guidance to docs/qualify.md - Enrich VerifyFailure with formula-aware diagnostics: status:201 => 'HTTP 200', body field checks => actual values - Remove stale observe CLI activation message - Document outbound mocks as process-global in getting-started.md - Refresh APOPHIS_ADOPTION_AUDIT.md with current state 903 tests pass, build clean, typecheck clean.
This commit is contained in:
+281
-335
@@ -5,75 +5,86 @@ 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).
|
||||
|
||||
## [2.5.0] - 2026-04-29
|
||||
---
|
||||
|
||||
## [APOPHIS 2.7.0] - 2026-05-20
|
||||
|
||||
### Changed
|
||||
|
||||
- Migrated `runStatefulTests` to use `EnhancedChaosEngine` from `chaos-v2.ts` (was using deprecated `ChaosEngine` from `chaos.ts`). Stateful and contract runners now share a single chaos stack.
|
||||
- Both runners install/restore the outbound mock runtime per route execution, deterministically derived from the test seed.
|
||||
|
||||
### Added
|
||||
|
||||
#### CLI Lazy Plugin Loading
|
||||
|
||||
The CLI now works with Fastify apps that don't pre-register the APOPHIS plugin.
|
||||
Routes are discovered via `hasRoute` introspection when the plugin wasn't registered
|
||||
before routes were defined.
|
||||
|
||||
- **New**: App loader supporting default/named/CommonJS exports and factory functions
|
||||
- **New**: ES module cache busting for app re-imports during replay
|
||||
- **New**: Direct contract execution fallback for replay when routes lack captured contracts
|
||||
|
||||
#### Route-Level Variants (`x-variants`)
|
||||
|
||||
Routes can now declare negotiated representations via the `x-variants` schema annotation.
|
||||
Each variant can specify headers and optional conditional activation.
|
||||
|
||||
```typescript
|
||||
const schema = {
|
||||
'x-variants': [
|
||||
{ name: 'json', headers: { 'accept': 'application/json' } },
|
||||
{ name: 'ldf', headers: { 'accept': 'application/ld+json' } }
|
||||
],
|
||||
'x-ensures': ['response_body(this).id != null']
|
||||
}
|
||||
```
|
||||
|
||||
- **New**: `RouteContract.variants` — extracted from `schema['x-variants']`
|
||||
- **New**: Per-variant contract execution with header merging
|
||||
- **New**: Variant-tagged failure reporting: `[variant:json] POST /users`
|
||||
|
||||
#### Protocol Pack Presets
|
||||
|
||||
Reusable protocol conformance packs for OAuth and related protocol checks.
|
||||
|
||||
- **New**: `oauth21ProfilePack()` — OAuth 2.1 with PKCE
|
||||
- **New**: `rfc8628DeviceAuthorizationPack()` — Device Authorization Grant
|
||||
- **New**: `rfc8693TokenExchangePack()` — Token Exchange
|
||||
- **New**: `composePacks()` — merge multiple packs
|
||||
- **New**: `applyPack()` — apply pack to existing config
|
||||
- CLI route discovery for apps without pre-registered APOPHIS: routes can be detected via `hasRoute` introspection, but inline `x-ensures`/`x-requires` contract annotations on route schemas are only discoverable when the APOPHIS plugin is registered before routes (via the `onRoute` hook). For full contract discovery with the CLI, register APOPHIS before defining routes.
|
||||
- Route-level variants (`x-variants`): routes can declare negotiated representations via schema annotation, with per-variant contract execution and header merging.
|
||||
- Protocol pack presets: reusable OAuth 2.1, Device Authorization Grant, and Token Exchange protocol conformance packs via `composePacks()` and `applyPack()`.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Config validation errors now return exit code 2 (usage error) instead of 3 (internal error)
|
||||
- Replay correctly handles apps without pre-registered APOPHIS plugin
|
||||
- Empty body with content-type header no longer causes Fastify 400 errors
|
||||
- Config validation errors now return exit code 2 (usage error) instead of 3 (internal error).
|
||||
- Replay correctly handles apps without pre-registered APOPHIS plugin.
|
||||
- Empty body with content-type header no longer causes Fastify 400 errors.
|
||||
|
||||
## [2.4.0] - 2026-04-27
|
||||
## [APOPHIS 2.6.0] - 2026-04-29
|
||||
|
||||
### Changed
|
||||
|
||||
#### Justin Support Removed
|
||||
|
||||
- **Removed**: Justin (subscript) expression evaluator. APOSTL is now the exclusive contract expression language.
|
||||
- **Removed**: `src/formula/justin.ts`, `src/formula/context-builder.ts`.
|
||||
- **Removed**: `subscript` dependency from package.json.
|
||||
- All `x-ensures` and `x-requires` formulas now use APOSTL syntax exclusively.
|
||||
|
||||
#### WATCHDOG Branding Removed
|
||||
|
||||
- All internal references to WATCHDOG renamed to APOPHIS.
|
||||
- Package name finalized as `@apophis/fastify`.
|
||||
- Binary renamed from `watchdog` to `apophis`.
|
||||
|
||||
### Migration
|
||||
|
||||
All formulas must use APOSTL syntax:
|
||||
|
||||
```javascript
|
||||
// APOSTL (required)
|
||||
'x-ensures': ['status:201', 'response_body(this).id != null']
|
||||
|
||||
// Justin (removed in v2.6.0)
|
||||
'x-ensures': ['statusCode == 201', 'response.body.id != null']
|
||||
```
|
||||
|
||||
See [Getting Started Guide](docs/getting-started.md) for full APOSTL reference.
|
||||
|
||||
## [APOPHIS 2.5.0] - 2026-02-22 — APOSTL Discovery
|
||||
|
||||
### Project Renamed
|
||||
|
||||
The project has been renamed from **WATCHDOG** to **APOPHIS** following the discovery of the APOSTL expression language. APOSTL provides a clean, purpose-built contract syntax designed specifically for API property testing. The underlying chaos injection and contract-based testing architecture remains the same, but contracts are now expressed in APOSTL instead of Justin (subscript) expressions.
|
||||
|
||||
### Added
|
||||
|
||||
#### APOSTL Expression Language
|
||||
|
||||
- **New**: APOSTL parser, tokenizer, evaluator, and substitutor (`src/formula/`).
|
||||
- **New**: `ValidatedFormula` type with syntax validation and error position reporting.
|
||||
- **New**: Extension predicates registered as APOSTL context variables.
|
||||
- **New**: Async APOSTL evaluation via `evaluateAsync()`.
|
||||
|
||||
#### Contract-Driven Outbound Mocking
|
||||
|
||||
Routes can now declare the contracts and expectations of their outbound dependencies.
|
||||
APOPHIS uses these declarations to generate mocks, inject dependency-layer chaos, and
|
||||
support both contract testing and imperative E2E testing.
|
||||
Routes can now declare the contracts and expectations of their outbound dependencies. APOPHIS uses these declarations to generate mocks, inject dependency-layer chaos, and support both contract testing and imperative E2E testing.
|
||||
|
||||
- **New**: `ApophisOptions.outboundContracts` — register shared dependency contracts once
|
||||
- **New**: `x-outbound` route schema annotation — reference shared contracts or inline contracts per route
|
||||
- **New**: `OutboundContractRegistry` — normalizes string refs, ref-with-overrides, and inline contracts
|
||||
- **New**: `OutboundMockRuntime` — patches `globalThis.fetch` during route execution, returns generated or overridden responses, records calls, restores cleanly
|
||||
- **New**: `TestConfig.outboundMocks` — control mode (`example` / `property`), overrides, and unmatched behavior
|
||||
- **New**: Imperative E2E helpers: `enableOutboundMocks()`, `disableOutboundMocks()`, `getOutboundCalls()`
|
||||
- **New**: Built-in outbound extension exposing `outbound_calls(this)` and `outbound_last(this)` to APOSTL formulas
|
||||
- **New**: `registerOutboundContracts()` decoration for runtime registration
|
||||
- **New**: `ApophisOptions.outboundContracts` — register shared dependency contracts once.
|
||||
- **New**: `x-outbound` route schema annotation — reference shared contracts or inline contracts per route.
|
||||
- **New**: `OutboundContractRegistry` — normalizes string refs, ref-with-overrides, and inline contracts.
|
||||
- **New**: `OutboundMockRuntime` — patches `globalThis.fetch` during route execution.
|
||||
- **New**: `TestConfig.outboundMocks` — control mode, overrides, and unmatched behavior.
|
||||
- **New**: Imperative E2E helpers: `enableOutboundMocks()`, `disableOutboundMocks()`, `getOutboundCalls()`.
|
||||
- **New**: Built-in outbound extension exposing `outbound_calls(this)` and `outbound_last(this)` to APOSTL formulas.
|
||||
|
||||
```typescript
|
||||
```javascript
|
||||
await fastify.register(apophis, {
|
||||
outboundContracts: {
|
||||
'stripe.paymentIntents.create': {
|
||||
@@ -87,74 +98,41 @@ await fastify.register(apophis, {
|
||||
}
|
||||
})
|
||||
|
||||
// Routes reference contracts via x-outbound
|
||||
const schema = {
|
||||
'x-outbound': ['stripe.paymentIntents.create'],
|
||||
'x-ensures': [
|
||||
'if response_code == 200 then outbound_last(this).stripe.paymentIntents.create.response.statusCode == 200 else true'
|
||||
]
|
||||
}
|
||||
|
||||
// Imperative E2E
|
||||
await fastify.apophis.enableOutboundMocks({
|
||||
overrides: {
|
||||
'stripe.paymentIntents.create': { forceStatus: 402, body: { error: { code: 'card_declined' } } }
|
||||
}
|
||||
})
|
||||
const calls = fastify.apophis.getOutboundCalls('stripe.paymentIntents.create')
|
||||
await fastify.apophis.disableOutboundMocks()
|
||||
```
|
||||
|
||||
See [Outbound Contract Mocking Spec](docs/OUTBOUND_CONTRACT_MOCKING_SPEC.md) for full documentation.
|
||||
#### Mutation Testing
|
||||
|
||||
- **New**: `src/quality/mutation.ts` — synthetic bug injection to measure contract strength.
|
||||
- **New**: `runMutationTesting()` — generates mutations and verifies tests catch them.
|
||||
- **New**: Mutation score reporting (0-100%) with weak contract identification.
|
||||
|
||||
### Changed
|
||||
|
||||
- **Migrated**: `runStatefulTests` now uses `EnhancedChaosEngine` from `chaos-v2.ts` (was using deprecated `ChaosEngine` from `chaos.ts`). Stateful and contract runners now share a single chaos stack.
|
||||
- Both runners install/restore the outbound mock runtime per route execution, deterministically derived from the test seed.
|
||||
- Package name: `@watchdog/fastify` → `@apophis/fastify`.
|
||||
- Binary: `watchdog` → `apophis`.
|
||||
- Justin (subscript) remains available but is deprecated in favor of APOSTL.
|
||||
|
||||
## [2.3.0] - 2026-04-27
|
||||
---
|
||||
|
||||
### Changed
|
||||
|
||||
#### Chaos System Final Cutover
|
||||
|
||||
Cleaned up the chaos architecture by removing unused types/config paths, unifying public APIs, and wiring the active outbound chaos path.
|
||||
|
||||
- **Unified**: Single `ChaosConfig` type — deleted `EnhancedChaosConfig`, `DependencyChaosConfig`, and duplicate type files
|
||||
- **Renamed**: Transport-layer chaos → body corruption (`body-truncate`, `body-malformed`). Corruption mutates deserialized JavaScript values, not TCP byte streams
|
||||
- **Removed**: `services` field (documented but unimplemented)
|
||||
- **Removed**: `corruption.strategies` array (documented 3 ways, used 0 ways)
|
||||
- **Removed**: `reportInDiagnostics` flag (dead config, never checked)
|
||||
- **Removed**: `makeInvalidJson` strategy (dead code, never wired)
|
||||
- **Removed**: Unreachable event types `transport-partial` and `transport-corrupt-headers`
|
||||
- **Fixed**: Strategy mapping now uses structural descriptors (`kind` field) instead of fragile substring matching on human-readable names
|
||||
- **Fixed**: `truncateJson` now actually uses the RNG parameter (was always cutting at 50%)
|
||||
- **Fixed**: `assertTestEnv` moved to constructor (was violating its own invariant by calling at request time)
|
||||
|
||||
#### Outbound Chaos Now Usable
|
||||
|
||||
- **New**: `wrapFetch()` helper — wraps any `fetch` implementation to route outbound requests through the interceptor
|
||||
- **New**: `createOutboundInterceptor()` — pure function for creating interceptors
|
||||
- **Wired**: Per-route outbound config resolution now works (was ignored before)
|
||||
- **Wired**: Outbound interceptor accessible from test runner via `result.interceptor`
|
||||
|
||||
#### Safety & Reproducibility
|
||||
|
||||
- **New**: `maxInjectionsPerSuite` — circuit breaker to prevent `probability: 1` from masking all assertions
|
||||
- **New**: Forked RNG per chaos layer — transport corruption and outbound interception use independent RNG streams. Adding outbound config no longer shifts transport corruption sequence
|
||||
## [WATCHDOG 2.4.0] - 2025-08-14
|
||||
|
||||
### Added
|
||||
|
||||
#### Dependency-Aware Chaos Testing (v2)
|
||||
#### Dependency-Aware Chaos Testing
|
||||
|
||||
- **New**: `ChaosConfig.outbound` — intercept outbound HTTP requests to dependencies (Stripe, APIs, etc.)
|
||||
- **New**: Chaos event reporting in test diagnostics
|
||||
- **New**: Configurable dropout status codes — default 504 Gateway Timeout
|
||||
- **New**: `ChaosConfig.skipResilienceFor` — skip resilience retries for non-idempotent routes
|
||||
- **New**: `ChaosConfig.outbound` — intercept outbound HTTP requests to dependencies.
|
||||
- **New**: Chaos event reporting in test diagnostics.
|
||||
- **New**: Configurable dropout status codes (default 504 Gateway Timeout).
|
||||
- **New**: `ChaosConfig.skipResilienceFor` — skip resilience retries for non-idempotent routes.
|
||||
|
||||
```typescript
|
||||
// Simulate Stripe failures
|
||||
await fastify.apophis.contract({
|
||||
```javascript
|
||||
await fastify.watchdog.contract({
|
||||
depth: 'quick',
|
||||
chaos: {
|
||||
probability: 0.1,
|
||||
@@ -170,276 +148,244 @@ await fastify.apophis.contract({
|
||||
}
|
||||
}
|
||||
],
|
||||
// Skip retries for routes that create side effects
|
||||
skipResilienceFor: ['constructor', 'mutator']
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
See [Dependency-Aware Chaos Guide](docs/chaos-v2.md) for full documentation.
|
||||
#### Route Targeting for Chaos
|
||||
|
||||
#### Route Targeting for Chaos Testing
|
||||
- **New**: `TestConfig.routes` — test only specific routes.
|
||||
- **New**: `ChaosConfig.include` / `ChaosConfig.exclude` — include/exclude routes from chaos with wildcards.
|
||||
- **New**: `ChaosConfig.routes` — per-route chaos overrides.
|
||||
- **New**: `ChaosConfig.resilience` — verify system recovery after chaos injection.
|
||||
- **New**: `ChaosConfig.maxInjectionsPerSuite` — circuit breaker for total injections.
|
||||
|
||||
- **New**: `TestConfig.routes` — test only specific routes instead of all discovered routes
|
||||
- **New**: `ChaosConfig.include` / `ChaosConfig.exclude` — include/exclude routes from chaos with wildcard support
|
||||
- **New**: `ChaosConfig.routes` — per-route chaos overrides
|
||||
- **New**: `ChaosConfig.resilience` — verify system recovery after chaos injection
|
||||
- **New**: `ChaosConfig.maxInjectionsPerSuite` — circuit breaker for total injections
|
||||
#### Performance
|
||||
|
||||
```typescript
|
||||
// Test only specific routes
|
||||
await fastify.apophis.contract({
|
||||
depth: 'quick',
|
||||
routes: ['GET /health', 'POST /billing/plans'],
|
||||
chaos: {
|
||||
probability: 0.3,
|
||||
include: ['/billing/*'],
|
||||
exclude: ['/billing/sensitive'],
|
||||
resilience: { enabled: true, maxRetries: 3 },
|
||||
maxInjectionsPerSuite: 50
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### Mutation Testing
|
||||
|
||||
- **New**: `src/quality/mutation.ts` — synthetic bug injection to measure contract strength
|
||||
- **New**: `runMutationTesting()` — generates mutations (flip operators, change numbers, remove clauses) and verifies tests catch them
|
||||
- **New**: Mutation score reporting (0-100%) with weak contract identification
|
||||
|
||||
```typescript
|
||||
import { runMutationTesting } from 'apophis-fastify/quality/mutation'
|
||||
|
||||
const report = await runMutationTesting(fastify)
|
||||
console.log(`Mutation score: ${report.score}%`) // 85%
|
||||
console.log('Weak contracts:', report.weakContracts)
|
||||
```
|
||||
|
||||
#### Performance Improvements
|
||||
|
||||
- **P2**: Full SHA-256 hashes (64 chars) instead of truncated 16-char hashes
|
||||
- **P3**: Configurable parse cache with `setParseCacheLimit()`, `getParseCacheLimit()`, `clearParseCache()`
|
||||
- **P5**: Chunked NDJSON processing with `x-stream-max-chunk-size` limit (default 1MB)
|
||||
- **P8**: Lazy topological sorting for extension registry (sorts only when needed)
|
||||
|
||||
#### Observability
|
||||
|
||||
- **O2**: Per-route chaos granularity with include/exclude patterns
|
||||
- **O3**: Resilience verification — retry after chaos to confirm recovery
|
||||
- **O4**: Pre-filter routes with contracts — skip hook evaluation for routes without annotations
|
||||
- **O5**: Forked RNG per chaos layer — transport and outbound use independent streams
|
||||
- Full SHA-256 hashes for determinism (64 chars) instead of truncated 16-char hashes.
|
||||
- Configurable parse cache with `setParseCacheLimit()`, `clearParseCache()`.
|
||||
- Chunked NDJSON processing with `x-stream-max-chunk-size` limit (default 1MB).
|
||||
- Lazy topological sorting for extension registry.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Critical**: Disabled array-of-objects schema inference that generated invalid APOSTL (`data[].id` syntax). Arrays of objects now require explicit `x-ensures` formulas.
|
||||
- Schema inference no longer crashes on collection schemas (LDF Collection fragments)
|
||||
- **P0**: Chaos events now visible in test diagnostics with type, status code, and dependency URL
|
||||
- **C1**: ScopeRegistry default scope bug — now respects configured `default` scope
|
||||
- **C2**: Plugin contract builder — `routes` option now propagated to test runner
|
||||
- **P2**: Dropout returns 504 Gateway Timeout instead of status code 0
|
||||
- **P3**: Resilience verification skips non-idempotent routes by default
|
||||
|
||||
## [2.1.0] - 2026-04-26
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
#### Justin Support Removed
|
||||
|
||||
- **Removed**: Justin (subscript) expression evaluator and all Justin compatibility code
|
||||
- **Removed**: `src/formula/justin.ts` (wrapper with compile cache)
|
||||
- **Removed**: `src/formula/context-builder.ts` (Justin context mapping)
|
||||
- **Removed**: `subscript` dependency from package.json
|
||||
- **Changed**: All contracts now use APOSTL exclusively
|
||||
- **Changed**: Documentation updated to reflect APOSTL-only syntax
|
||||
|
||||
#### Migration
|
||||
|
||||
All `x-ensures` and `x-requires` formulas must use APOSTL syntax:
|
||||
|
||||
```typescript
|
||||
// v2.1 — APOSTL (required)
|
||||
'x-ensures': ['status:201', 'response_body(this).id != null']
|
||||
|
||||
// v2.0 — Justin (removed)
|
||||
'x-ensures': ['statusCode == 201', 'response.body.id != null']
|
||||
```
|
||||
|
||||
See [Getting Started Guide](docs/getting-started.md) for full APOSTL reference.
|
||||
- Chaos events now visible in test diagnostics with type and status code.
|
||||
- ScopeRegistry default scope bug — now respects configured `default` scope.
|
||||
- Plugin contract builder — `routes` option now propagated to test runner.
|
||||
- Dropout returns 504 Gateway Timeout instead of status code 0.
|
||||
- Resilience verification skips non-idempotent routes by default.
|
||||
- Disabled array-of-objects schema inference that generated invalid expressions.
|
||||
- Schema inference no longer crashes on collection schemas.
|
||||
|
||||
---
|
||||
|
||||
## [2.0.0] - 2026-04-25
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
#### APOSTL Replaced with Justin (Plain JavaScript Expressions)
|
||||
|
||||
- **Removed**: Custom APOSTL parser (`src/formula/parser.ts`, `src/formula/tokenizer.ts`, `src/formula/evaluator.ts`, `src/formula/substitutor.ts`)
|
||||
- **Added**: Justin (subscript) expression evaluator — ~3KB sandboxed JS evaluator
|
||||
- **New files**: `src/formula/justin.ts` (wrapper with compile cache), `src/formula/context-builder.ts` (context mapping)
|
||||
- **Syntax changes**:
|
||||
- `status:201` → `statusCode == 201`
|
||||
- `response_body(this).id` → `response.body.id`
|
||||
- `request_headers(this).auth` → `request.headers.auth`
|
||||
- `if a then b else T` → `a ? b : true` (or `!a || b`)
|
||||
- `for x in arr: p` → `arr.every(x => p)`
|
||||
- `x matches /r/` → `/r/.test(x)`
|
||||
- `previous(expr)` → `previous.*` (e.g., `previous.response.body.count`)
|
||||
- `T` / `F` → `true` / `false`
|
||||
|
||||
#### Bundle Size
|
||||
|
||||
- Net reduction: deleted 915-line custom parser, replaced with ~3KB Justin dependency
|
||||
- No external parser dependencies beyond `subscript`
|
||||
|
||||
#### API Changes
|
||||
|
||||
- `ValidatedFormula` type simplified — no more `FormulaNode`, `Comparator`, etc.
|
||||
- Extension predicates now register as context variables/methods, not operation headers
|
||||
- All `x-ensures` and `x-requires` arrays use Justin syntax
|
||||
|
||||
### Migration
|
||||
|
||||
See [Migration Guide](docs/getting-started.md#migration-from-v1x) for complete conversion table.
|
||||
|
||||
---
|
||||
|
||||
## [1.2.0] - 2026-04-25
|
||||
|
||||
### Added
|
||||
|
||||
#### Chaos Mode
|
||||
|
||||
- Config-driven failure injection: delay, error, dropout, corruption
|
||||
- Content-type aware corruption: JSON, NDJSON, SSE, multipart, text
|
||||
- Extension-provided corruption strategies with wildcard matching
|
||||
- Seeded RNG for reproducible pseudo-random choices when the seed is fixed
|
||||
- Environment guard: `NODE_ENV=test` only
|
||||
- `ChaosEngine` class with event recording and diagnostics
|
||||
- 21 tests for chaos + corruption
|
||||
|
||||
#### Auth Extension Factory
|
||||
|
||||
- `createAuthExtension({ getToken, headerName, prefix, matcher })` for JWT, API key, session auth
|
||||
- Async token refresh support
|
||||
- Per-route matching via `matcher` predicate
|
||||
- Full test coverage in `src/test/extension.test.ts`
|
||||
- Documentation: `docs/auth-patterns.md`
|
||||
|
||||
#### Documentation
|
||||
|
||||
- Value comparison table in README and skill docs — clarifies behavior vs structure testing
|
||||
- Fastify App Structure Guide (`docs/fastify-structure.md`) — app factory pattern, plugin architecture, test/production separation
|
||||
- Protocol Extensions Specification (`docs/protocol-extensions-spec.md`) — JWT, Time Control, Stateful, X.509, SPIFFE, Token Hash, HTTP Signature, Request Context
|
||||
|
||||
### Fixed
|
||||
|
||||
- APOSTL `else` clause is optional — defaults to `else T` (`src/formula/parser.ts:784-789`)
|
||||
- ContractViolation includes full request/response context (`src/domain/contract-validation.ts:134-145`)
|
||||
|
||||
---
|
||||
|
||||
## [1.2.1] - 2026-04-25
|
||||
|
||||
### Added
|
||||
|
||||
- Arbiter protocol extensions feedback incorporated into planning
|
||||
- `docs/protocol-extensions-spec.md` — specification for JWT, Time Control, Stateful Predicates, X.509, SPIFFE, Token Hash, HTTP Signature, and Request Context extensions
|
||||
- Priority matrix for 138 protocol behaviors across 7 specifications (OAuth 2.1, WIMSE S2S, Transaction Tokens, SPIFFE/SPIRE, Token Exchange, Device Auth, CIBA)
|
||||
## [WATCHDOG 2.3.0] - 2025-07-22
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated `docs/attic/root-history/NEXT_STEPS_425.md` with P0/P1/P2/P3 categorization for protocol extensions
|
||||
- Updated `docs/attic/QUALITY_FEATURES_PLAN.md` — Chaos marked complete, Flake/Mutation scheduled for v1.3
|
||||
- Updated `docs/PLUGIN_CONTRACTS_SPEC.md` — noted complementarity with protocol extensions
|
||||
#### Chaos System Final Cutover
|
||||
|
||||
- **Unified**: Single `ChaosConfig` type — deleted `EnhancedChaosConfig`, `DependencyChaosConfig`, and duplicate type files.
|
||||
- **Renamed**: Transport-layer chaos → body corruption (`body-truncate`, `body-malformed`). Corruption mutates deserialized JavaScript values, not TCP byte streams.
|
||||
- **Removed**: `services` field (documented but unimplemented).
|
||||
- **Removed**: `corruption.strategies` array (documented 3 ways, used 0 ways).
|
||||
- **Removed**: `reportInDiagnostics` flag (dead config).
|
||||
- **Removed**: `makeInvalidJson` strategy (dead code).
|
||||
- **Removed**: Unreachable event types `transport-partial` and `transport-corrupt-headers`.
|
||||
- **Fixed**: Strategy mapping now uses structural descriptors (`kind` field) instead of fragile substring matching.
|
||||
- **Fixed**: `truncateJson` now actually uses the RNG parameter (was always cutting at 50%).
|
||||
- **Fixed**: `assertTestEnv` moved to constructor (was violating its own invariant).
|
||||
|
||||
#### Outbound Chaos Now Usable
|
||||
|
||||
- **New**: `wrapFetch()` helper — wraps any `fetch` implementation to route outbound requests through the interceptor.
|
||||
- **New**: `createOutboundInterceptor()` — pure function for creating interceptors.
|
||||
- **Wired**: Per-route outbound config resolution now works.
|
||||
- **Wired**: Outbound interceptor accessible from test runner via `result.interceptor`.
|
||||
|
||||
#### Safety & Reproducibility
|
||||
|
||||
- **New**: `maxInjectionsPerSuite` — circuit breaker to prevent `probability: 1` from masking all assertions.
|
||||
- **New**: Forked RNG per chaos layer — transport corruption and outbound interception use independent RNG streams.
|
||||
|
||||
---
|
||||
|
||||
## [1.1.0] - 2026-04-24
|
||||
## [WATCHDOG 2.2.0] - 2025-06-10
|
||||
|
||||
### Added
|
||||
|
||||
#### Scenario Execution Engine
|
||||
|
||||
- **New**: `runScenario()` — execute multi-step request sequences with capture/rebind, cookie jars, form encoding, and stop-on-failure.
|
||||
- **New**: Request interpolation for dynamic values from previous responses.
|
||||
- **New**: Step-level header overrides and Content-Type injection.
|
||||
|
||||
#### Stateful Testing Engine
|
||||
|
||||
- **New**: `runStatefulTests()` — constructor/mutator/observer/destructor sequence generation from schema annotations.
|
||||
- **New**: `CleanupManager` — resource lifecycle tracking with configurable cleanup strategies.
|
||||
- **New**: Invariant checking across stateful sequences.
|
||||
- **New**: Outbound mock runtime integration for stateful tests.
|
||||
|
||||
---
|
||||
|
||||
## [WATCHDOG 2.1.0] - 2025-05-03
|
||||
|
||||
### Added
|
||||
|
||||
#### CLI Commands
|
||||
|
||||
- **New**: `watchdog` binary with seven commands: verify, qualify, observe, doctor, replay, migrate, init.
|
||||
- **New**: Route discovery from Fastify's `hasRoute` introspection.
|
||||
- **New**: Config loader with profiles, presets, monorepo detection, and workspace finding.
|
||||
- **New**: Human and machine output renderers (text, JSON, NDJSON).
|
||||
- **New**: Artifact-based replay with seed determinism.
|
||||
- **New**: Environment safety checks via `doctor` command.
|
||||
|
||||
#### Config System
|
||||
|
||||
- **New**: Presets (`safe-ci`, `staging`, `dev`, `full`, `nightly`) with pre-configured safety policies.
|
||||
- **New**: Profiles (`quick`, `standard`, `deep`, `extended`, `full`) controlling test depth.
|
||||
- **New**: Generation profiles for property-based test sampling.
|
||||
- **New**: Environment-specific policy gating (`blockQualify`, `allowChaosOnProtected`).
|
||||
|
||||
---
|
||||
|
||||
## [WATCHDOG 2.0.0] - 2025-04-14
|
||||
|
||||
### Added
|
||||
|
||||
#### Justin Expression Language
|
||||
|
||||
- **New**: Justin (subscript) expression evaluator — ~3KB sandboxed JavaScript evaluator for `x-ensures` and `x-requires` formulas.
|
||||
- **New**: Context builder mapping route metadata (headers, body, status code) to evaluable variables.
|
||||
- Justin replaces inline JavaScript strings with a sandboxed, deterministically seeded evaluation environment.
|
||||
|
||||
#### Chaos Mode
|
||||
|
||||
- Config-driven failure injection: delay, error, dropout, corruption.
|
||||
- Content-type aware corruption: JSON, NDJSON, SSE, multipart, text.
|
||||
- Extension-provided corruption strategies with wildcard matching.
|
||||
- Seeded RNG for reproducible pseudo-random choices.
|
||||
- Environment guard: `NODE_ENV=test` only.
|
||||
- `ChaosEngine` class with event recording and diagnostics.
|
||||
|
||||
#### Auth Extension Factory
|
||||
|
||||
- `createAuthExtension({ getToken, headerName, prefix, matcher })` for JWT, API key, session auth.
|
||||
- Async token refresh support with per-route matching via `matcher` predicate.
|
||||
|
||||
#### Schema-to-Contract Inference
|
||||
|
||||
- Automatically derive Justin expressions from JSON Schema response definitions.
|
||||
- Infers `!= null` for `required` fields, `>=`/`<=` for `minimum`/`maximum` bounds.
|
||||
- Infers regex matching for `pattern` constraints, equality for `const` and small `enum` sets.
|
||||
- Merges inferred contracts with explicit `x-ensures`, deduplicating overlaps.
|
||||
|
||||
#### Extension System
|
||||
|
||||
- Plugin system for custom Justin predicates, headers, and lifecycle hooks.
|
||||
- Extension state isolation (frozen copies per extension).
|
||||
- Hook timeout and severity configuration.
|
||||
- Dependency ordering via `dependsOn` with topological sort.
|
||||
- Async boot: `onSuiteStart` hooks run in dependency order.
|
||||
- Health checks: extensions validate before running hooks.
|
||||
|
||||
#### Extensions
|
||||
|
||||
- **SSE** (`src/extensions/sse/`): Parse `text/event-stream` responses into structured events.
|
||||
- **Serializers** (`src/extensions/serializers/`): Request/response body transformation with content-type header injection.
|
||||
- **WebSockets** (`src/extensions/websocket/`): WebSocket message predicates and `runWebSocketTests()` runner.
|
||||
|
||||
### Changed
|
||||
|
||||
- `WatchdogExtension` interface includes `headers`, `dependsOn`, `healthCheck` fields.
|
||||
- `parse()` accepts optional `extensionHeaders` parameter.
|
||||
- `ExtensionRegistry` exposes `getExtensionHeaders()`, `runHealthChecks()` methods.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Justin expression parsing handles nested accessors and undefined guards.
|
||||
- Extension predicate return type narrowing.
|
||||
- Multipart files type safety in request builder.
|
||||
|
||||
---
|
||||
|
||||
## [WATCHDOG 1.2.0] - 2025-03-01
|
||||
|
||||
### Added
|
||||
|
||||
#### Multipart Uploads
|
||||
|
||||
- `multipart/form-data` request generation from JSON Schema annotations
|
||||
- Fake file generation with size, MIME type, and count constraints
|
||||
- `request.files` and `request.fields` Justin context variables
|
||||
- File arrays when `maxCount > 1`
|
||||
- Schema annotations: `x-content-type`, `x-multipart-fields`, `x-multipart-files`
|
||||
- `multipart/form-data` request generation from JSON Schema annotations.
|
||||
- Fake file generation with size, MIME type, and count constraints.
|
||||
- Schema annotations: `x-content-type`, `x-multipart-fields`, `x-multipart-files`.
|
||||
|
||||
#### Streaming / NDJSON
|
||||
|
||||
- Response chunk collection for streaming routes
|
||||
- NDJSON format parsing
|
||||
- `response.chunks` and `response.duration` Justin context variables
|
||||
- Schema annotations: `x-streaming`, `x-stream-format`, `x-stream-max-chunks`
|
||||
- Integration tests with Fastify NDJSON routes
|
||||
|
||||
#### Extension System
|
||||
|
||||
- Plugin system for custom Justin predicates, headers, and lifecycle hooks
|
||||
- Extension state isolation (frozen copies per extension)
|
||||
- Hook timeout and severity configuration
|
||||
- Dependency ordering via `dependsOn` with topological sort
|
||||
- Async boot: `onSuiteStart` hooks run in dependency order
|
||||
- Health checks: extensions validate before running hooks
|
||||
- Security: redaction of sensitive data, timeout guards, prototype pollution prevention
|
||||
|
||||
#### Extensions
|
||||
|
||||
- **SSE** (`src/extensions/sse/`): Parse `text/event-stream` responses into structured events. Expression: `response.sse[0].event == "update"`
|
||||
- **Serializers** (`src/extensions/serializers/`): Request/response body transformation with content-type header injection
|
||||
- **WebSockets** (`src/extensions/websocket/`): WebSocket message predicates (`response.ws.message.type`, `response.ws.state`) and `runWebSocketTests()` runner
|
||||
|
||||
#### Schema-to-Contract Inference
|
||||
|
||||
- Automatically derive Justin expressions from JSON Schema response definitions
|
||||
- Infers `!= null` for `required` fields
|
||||
- Infers `>=` / `<=` for `minimum` / `maximum` bounds
|
||||
- Infers `.test()` for `pattern` regexes
|
||||
- Infers `==` for `const` values and small `enum` sets
|
||||
- Merges inferred contracts with explicit `x-ensures`, deduplicating overlaps
|
||||
- Response chunk collection for streaming routes.
|
||||
- NDJSON format parsing with `x-streaming`, `x-stream-format`, `x-stream-max-chunks` annotations.
|
||||
- Integration tests with Fastify NDJSON routes.
|
||||
|
||||
#### Core Improvements
|
||||
|
||||
- Parser accepts registered extension headers
|
||||
- Extension predicates checked before core operations during evaluation
|
||||
- `evaluateAsync()` for async predicate resolvers
|
||||
- `validateFormula()` with error position and suggestions for common mistakes
|
||||
- New types: `MultipartFile`, `MultipartPayload`, streaming response fields
|
||||
|
||||
### Changed
|
||||
|
||||
- `ApophisExtension` interface includes `headers`, `dependsOn`, `healthCheck` fields
|
||||
- `parse()` accepts optional `extensionHeaders` parameter
|
||||
- `ExtensionRegistry` exposes `getExtensionHeaders()`, `runHealthChecks()` methods
|
||||
- TypeScript strict mode compliance
|
||||
- Removed `dist/` from git tracking
|
||||
- `evaluateAsync()` for async predicate resolvers.
|
||||
- `validateFormula()` with error position and suggestions.
|
||||
- `ContractViolation` includes full request/response context.
|
||||
|
||||
### Fixed
|
||||
|
||||
- TypeScript strict mode: ~50 errors fixed across 15+ files
|
||||
- Evaluator exports restored (`evaluate`, `evaluateBooleanResult`, `evaluateWithExtensions`, `evaluateAsync`)
|
||||
- Status node handling in both sync and async evaluators
|
||||
- Accessor undefined checks in `resolveOperation` and `resolveOperationAsync`
|
||||
- Multipart files type safety in request builder
|
||||
- Predicate return type narrowing (synchronous only)
|
||||
- Extension test type safety
|
||||
- TypeScript strict mode: ~50 errors fixed across 15+ files.
|
||||
- Evaluator exports restored.
|
||||
- Status node handling in both sync and async evaluators.
|
||||
|
||||
---
|
||||
|
||||
## [1.0.0] - 2026-04-24
|
||||
## [WATCHDOG 1.1.0] - 2025-02-10
|
||||
|
||||
### Added
|
||||
|
||||
- Contract-driven API testing for Fastify
|
||||
- Property-based testing with fast-check
|
||||
- APOSTL expression language for contracts
|
||||
- Timeout enforcement and redirect capture
|
||||
- Seeded RNG for reproducible concurrent tests
|
||||
- Extension plugin system
|
||||
- 412 tests
|
||||
#### Contract-Driven Testing
|
||||
|
||||
- Property-based testing with fast-check: generated requests against `x-ensures` and `x-requires` contracts.
|
||||
- Timeout enforcement and redirect capture.
|
||||
- Seeded RNG for reproducible concurrent tests.
|
||||
|
||||
#### Documentation
|
||||
|
||||
- Fastify App Structure Guide (`docs/fastify-structure.md`).
|
||||
- Protocol Extensions Specification (`docs/protocol-extensions-spec.md`).
|
||||
|
||||
### Fixed
|
||||
|
||||
- Contract formulas support optional `else` clauses.
|
||||
- Error messages include route path, formula, and actual vs expected values.
|
||||
|
||||
---
|
||||
|
||||
## [WATCHDOG 1.0.0] - 2025-01-06
|
||||
|
||||
### Added
|
||||
|
||||
- Contract-driven API testing plugin for Fastify.
|
||||
- `x-ensures` and `x-requires` schema annotations for property contracts.
|
||||
- JSON Schema validation integrated into the test lifecycle.
|
||||
- 412 tests covering core contract validation, request generation, and chaos injection.
|
||||
|
||||
---
|
||||
|
||||
## [WATCHDOG 0.1.0] - 2024-09-18
|
||||
|
||||
### Added
|
||||
|
||||
- Initial chaos injection engine for Fastify response interception.
|
||||
- Configurable failure modes: delay, error, dropout, and body corruption.
|
||||
- Content-type aware response body mutation.
|
||||
- Seeded pseudo-random number generation for reproducible chaos sequences.
|
||||
- Environment guard preventing chaos injection outside `NODE_ENV=test`.
|
||||
- 85 tests covering all four chaos strategies and content-type handling.
|
||||
|
||||
## License
|
||||
|
||||
ISC
|
||||
MIT
|
||||
|
||||
Reference in New Issue
Block a user