Files
apophis-fastify/docs/attic/root-history/NEXT_STEPS_426.md
T

15 KiB

NEXT_STEPS_426.md — Post-v2.x APOSTL Restoration & Remaining Work

Status: v2.2 Complete (2026-04-27)

Test count: 503 passing, 0 failures
New in v2.x: Justin removed, APOSTL restored as primary and only contract language, cross-operation behavioral contracts re-enabled, all documentation updated

Completed (v2.x)

Justin Removal & APOSTL Restoration

  • Removed subscript dependency from package.json
  • Deleted src/formula/justin.ts — Justin wrapper with compile cache
  • Deleted src/formula/context-builder.ts — EvalContext → Justin context mapping
  • Restored APOSTL types in src/types.ts
  • Updated src/infrastructure/hook-validator.ts — APOSTL-only evaluation
  • Updated src/domain/contract-validation.ts — APOSTL-only evaluation
  • Updated src/domain/schema-to-contract.ts — generates APOSTL syntax
  • Updated src/domain/error-suggestions.ts — matches APOSTL syntax
  • Restored parser/evaluator files for APOSTL
  • Hand-converted all test schema annotations (~40 test files) from Justin back to APOSTL
  • Fixed APOSTL cross-operation support (pure GET calls, previous(...), guarded prefetch)
  • Fixed validateRouteContracts to iterate fastify.routes directly
  • Fixed build errors across all modules
  • Fixed runtime validation: Dynamic contract lookup from routeContractStore at request time

Behavioral Contract Documentation

  • README.md — v2.x rewrite with behavioral contract focus
  • docs/getting-started.md — behavioral examples + APOSTL reference
  • docs/PLUGIN_CONTRACTS_SPEC.md — APOSTL syntax
  • docs/extensions/QUICK-REFERENCE.md — APOSTL extension predicates
  • docs/extensions/EXTENSION-PLUGIN-SYSTEM.md — APOSTL predicate examples
  • skills.md — behavioral contract focus
  • CHANGELOG.md — v2.1.0 section documenting Justin removal

Critical Safety Fixes (from Expert Assessments)

  • C1: Chaos two-level probability bug removed
  • C2: Math.random() in corruption — now requires injected RNG
  • C3: Seed collision — FNV-1a hash combine
  • H1: Hook validator 500s — formulas validated at registration time
  • H2: env-guard runtime throws — now validated at plugin registration
  • P4: Promise.race leak — timer cleanup added
  • P9: safe-regex false positives — actual execution timeout test added
  • P11: PARAM_PATTERN injection — URL encoding + validation added

Architecture Extraction

  • src/test/command-generator.ts
  • src/test/precondition-checker.ts
  • src/test/result-deduplicator.ts
  • src/test/route-filter.ts
  • src/test/plugin-contract-composer.ts
  • src/test/result-formatter.ts
  • src/test/api-operations.ts (shared between petit and stateful runners)
  • src/plugin/swagger.ts
  • src/plugin/spec-builder.ts
  • src/plugin/contract-builder.ts
  • src/plugin/stateful-builder.ts
  • src/plugin/check-builder.ts
  • src/plugin/cleanup-builder.ts

Remaining from v1.3 (Carried Forward)

Medium Priority

  • F6: CI/CD examples (docs/ci-cd.md) — GitHub Actions, GitLab CI, CircleCI workflows

Quality Features

  • Flake Detection — Auto-rerun failing tests with varied seeds
  • Mutation Testing (src/quality/mutation.ts) — Synthetic bug injection, contract strength scoring

Performance & Implementation (John Carmack)

  • P2: hashSchema truncated to 16 chars — use full SHA-256 (64 hex chars)
  • P3: PARSE_CACHE Map has no TTL — add LRU cache with configurable max size
  • P5: Streaming NDJSON loads entire response — add chunked processing with limits
  • P6: request-builder.ts uses Math.random() fallback — deterministic fallback + warning (already clean in production)
  • P8: topologicalSort re-sorts on every register() — lazy sorting

Observability (Charity Majors)

  • O1: Zero OpenTelemetry integration — add tracing, metrics, correlation (deferred — not appropriate for test framework)
  • O2: No per-route chaos granularity — route overrides, include/exclude patterns
  • O3: No resilience verification after chaos — recovery check post-injection
  • O4: Runtime hooks evaluate on every request — pre-filter routes with contracts
  • O5: Arbiter Bug #1 — ScopeRegistry default scope ignored configured default
  • O6: Arbiter Bug #2 — routes option dropped in plugin contract builder

Type Safety (Uncle Bob)

  • T1: OperationHeader union with string — use branded type for extensions
  • T2: RequestStructure.body?: unknown — discriminated union for body types

Category Inference (Martin Fowler)

  • Cat1: Hardcoded exact paths miss prefixed variants — regex/prefix matching

New for v2.2: Arbiter Integration Stabilization (2026-04-27)

P0: Targeted Chaos Testing COMPLETE

  • Per-route include/exclude patterns for chaos injection
  • Route-level chaos config overrides global config
  • Resilience verification (retry after chaos injection)

P1: Arbiter Bug Fixes COMPLETE

  • Bug #1: ScopeRegistry default scope — now respects configured default scope
  • Bug #2: Plugin contract builder — routes option now propagated to test runner
  • Bug #3: Configurable invariants — deferred to v2.3

P2: Schema Inference Fixes COMPLETE

  • Disabled aggressive array-of-objects schema inference (was generating invalid [] accessors)
  • Reduced false-positive contract violations from inferred schemas

New for v2.1: Cross-Route Relationships (Arbiter Feedback)

Design Decision: Relationships are expressed as APOSTL predicates inside x-ensures. No new schema annotation needed — relationships are just postconditions.

schema: {
  'x-ensures': [
    // Parent consistency
    'response_body(this).tenantId == request_params(this).tenantId',
    // Hypermedia link validation
    'route_exists(response_body(this).controls.tenant.href) == true',
    // Relationship validation
    'relationship_valid("parent", request_params(this).tenantId, response_body(this).tenantId) == true'
  ]
}

P0: Core Relationship Predicates COMPLETE

R1: route_exists() Extension Predicate

File: src/extensions/relationships.ts
Description: Check that a URL resolves to a registered route.

// Basic: check route exists
'route_exists(response_body(this).controls.tenant.href) == true'

// With method check:
'route_exists(response_body(this).controls.edit.href, response_body(this).controls.edit.method) == true'

// Negative: ensure link is NOT a route (external URL)
'route_exists(response_body(this).externalUrl) == false'

Implementation:

  • Use discoverRoutes() to get all registered routes
  • Match concrete URLs against route patterns (/users/:id matches /users/user:alice)
  • Support method validation

Invariants:

  • MUST: Pattern matching handle :param syntax
  • MUST: Return false for unregistered routes, never throw
  • MUST: Cache route discovery results per test run
  • MAY NEVER: Match against routes registered after the check

R2: Route Pattern Matcher COMPLETE

File: src/infrastructure/route-matcher.ts
Description: Utility to match concrete URLs against Fastify route patterns.

function matchRoutePattern(pattern: string, concreteUrl: string): {
  matched: boolean
  params: Record<string, string>
}

// Example:
matchRoutePattern('/users/:id', '/users/user:alice')
// → { matched: true, params: { id: 'user:alice' } }

Invariants:

  • MUST: Support Fastify's :param and * wildcard syntax
  • MUST: Return extracted parameters
  • MUST: Handle trailing slashes consistently
  • MAY NEVER: Match partial segments (e.g., /users/:id should NOT match /users/admin/settings)

P1: Relationship & Cascade Validation COMPLETE

R3: relationship_valid() Extension Predicate

File: src/extensions/relationships.ts
Description: Validate parent-child consistency.

// Verify resource belongs to parent from path
'relationship_valid("parent", request_params(this).tenantId, response_body(this).tenantId) == true'

// Verify arbitrary relationship type
'relationship_valid("owner", request_params(this).userId, response_body(this).ownerId) == true'

Implementation:

  • Track resource creation/deletion in test state
  • Check that child resources reference existing parents

Invariants:

  • MUST: Track resource lifecycle across test commands
  • MUST: Support arbitrary relationship types (not hardcoded)
  • MAY NEVER: Report false positives due to test isolation issues

R4: cascade_valid() Extension Predicate

File: src/extensions/relationships.ts
Description: Verify that deleting a parent resource makes children inaccessible.

// After DELETE /tenants/:id, verify cascade
'cascade_valid("tenant", request_params(this).id, ["application", "user"]) == true'

Implementation:

  • Track DELETE operations in test state
  • For deleted resources, check child routes return 404
  • Accept array of child resource types to validate

Invariants:

  • MUST: Verify HTTP 404 for child resources after parent deletion
  • MUST: Support soft-delete (200 with deleted flag) vs hard-delete (404)
  • MUST: Accept list of child types to check
  • MAY NEVER: Assume all DELETEs are hard deletes

R5: Hypermedia Validation Phase COMPLETE

File: src/test/hypermedia-validator.ts
Description: Post-test validation that checks all hypermedia links across responses.

const result = await fastify.apophis.contract({ depth: 'standard' });

// Optional: Run hypermedia validation
const hypermediaReport = await fastify.apophis.validateHypermedia({
  checkLinks: true,        // verify hrefs resolve to routes
  checkDescriptors: true,  // verify action descriptors exist
  checkMethods: true,      // verify methods match route definitions
  checkRelationships: true // verify parent-child consistency
});

Output format:

{
  "brokenLinks": [
    {
      "route": "GET /users/user:alice",
      "control": "tenant",
      "href": "/tenants/tenant:acme",
      "issue": "route_not_found",
      "suggestion": "Route GET /tenants/:id is not registered"
    }
  ],
  "orphanResources": [
    {
      "route": "GET /applications/app:123",
      "field": "tenantId",
      "value": "tenant:deleted",
      "issue": "parent_not_found"
    }
  ]
}

Invariants:

  • MUST: Collect all hypermedia links from test responses
  • MUST: Validate links against registered routes
  • MUST: Report per-route summaries
  • MAY NEVER: Fail the main contract test suite due to hypermedia issues (separate report)

P2: Stateful Test Enhancement COMPLETE

R6: Automatic Path Substitution in Stateful Tests

File: src/domain/request-builder.ts
Description: Infer path parameters from previously created resources.

// Apophis generates:
// Step 1: POST /tenants → { id: 'tenant:acme' }
// Step 2: POST /tenants/tenant:acme/applications → { id: 'app:123' }
// Step 3: GET /tenants/tenant:acme/applications/app:123

Implementation :

  • Enhanced substitutePathParams() in src/domain/request-builder.ts
  • Supports patterns: tenantId, tenant_id, userId
  • Uses inferResourceTypeFromParam() to map param names to resource types
  • Falls back to arbitrary generation if no matching resource in state
  • Added test: stateful runner substitutes path params from resource state

Invariants:

  • MUST: Only substitute when resource type matches param name
  • MUST: Fall back to random/arbitrary generation if no matching resource
  • MUST: Not break existing stateful tests
  • MAY NEVER: Generate invalid URLs due to substitution errors

R7: Cascade Validation in Stateful Tests

File: src/test/cascade-validator.ts
Description: After DELETE commands, automatically verify children are inaccessible.

// Stateful test runs DELETE /tenants/tenant:acme
// Cascade validator then checks:
// - GET /tenants/tenant:acme → 404
// - GET /tenants/tenant:acme/applications → 404
// - GET /tenants/tenant:acme/users → 404

Implementation :

  • Created src/test/cascade-validator.ts with createCascadeValidator()
  • findChildRoutes() discovers nested routes under a parent pattern
  • validateAfterDelete() generates cascade checks with configurable depth
  • extractPathParamsFromUrl() extracts params for URL substitution
  • Added comprehensive tests for cascade validation

Invariants:

  • MUST: Only trigger after DELETE commands that return 2xx/204
  • MUST: Use route pattern matching to find child routes
  • MUST: Configurable (on/off, max depth)
  • MAY NEVER: Cause test suite to fail due to cascade check timing issues
  • MAY NEVER: Cause test suite to fail due to cascade check timing issues

Implementation Order

Phase 1: Foundation (P0) COMPLETE

  1. Create src/infrastructure/route-matcher.ts — pattern matching utility
  2. Create src/extensions/relationships.tsroute_exists() predicate
  3. Add tests for route pattern matcher
  4. Add tests for route_exists() predicate

Phase 2: Relationship Predicates (P1) COMPLETE

  1. Add relationship_valid() predicate
  2. Add cascade_valid() predicate
  3. Create src/test/hypermedia-validator.ts — collect and validate links
  4. Hypermedia validation via APOSTL route_exists() predicate (no imperative API needed)
  5. Add tests for all predicates and hypermedia validation

Phase 3: Stateful Enhancement (P2) COMPLETE

  1. Enhanced src/domain/request-builder.ts — automatic path substitution from resource state
  2. Created src/test/cascade-validator.ts — automatic cascade checks after DELETE
  3. Added tests for automatic path substitution
  4. Added tests for cascade validation

Phase 4: Integration & Polish COMPLETE

  1. Update documentation with relationship predicate examples
  2. Update FEEDBACK-cross-route-relationships.md with implementation status
  3. Performance testing with Arbiter's 30+ route families (deferred)
  4. Release v2.1

Metrics

Metric v2.0 v2.x v2.1
Tests passing 343 476 503
Contract language Justin APOSTL APOSTL
Cross-operation support
Cross-route predicates 0 0 3 (route_exists, relationship_valid, cascade_valid)
Hypermedia validation
Automatic path substitution
Cascade validation

Reference

  • Cross-Route Feedback: FEEDBACK-cross-route-relationships.md
  • Cross-Operation Feedback: FEEDBACK-cross-operation-expressiveness.md
  • Previous Steps: NEXT_STEPS_425.md
  • Plugin Contracts Spec: docs/PLUGIN_CONTRACTS_SPEC.md
  • Extension System: docs/extensions/EXTENSION-PLUGIN-SYSTEM.md
  • Arbiter Collaboration: Contact via GitHub issues/PRs