**Note**: Plugin contracts are complementary to Protocol Extensions (see `docs/protocol-extensions-spec.md`). Protocol extensions add domain-specific predicates (JWT, X.509, SPIFFE); plugin contracts add hook-phase behavioral contracts for Fastify plugins.
## 1. Overview
The Plugin Contract System enables Fastify plugins to declare APOPHIS contracts that are automatically merged into route contracts at test time. Plugins specify which hooks they participate in and what behavioral contracts they enforce at each phase of the request lifecycle.
**Key invariant**: Route contracts are the **composition** of route-level contracts plus all plugin contracts whose `appliesTo` pattern matches the route path.
## 2. Terminology
- **MUST**: Absolute requirement. Violation prevents system operation.
**Note**: Phase-aware testing requires hook interception. Since Fastify doesn't expose hook execution points, APOPHIS MAY approximate by testing all plugin contracts against the final response context, with phase noted in diagnostics.
### 3.9 Diagnostics and Reporting
Contract violations MUST include source attribution:
```typescript
interfaceContractViolation{
// ... existing fields ...
readonlysource:'route'|'plugin:name'
readonlyphase?: string// 'onRequest', 'onSend', etc.
-`src/types.ts:277-285` — Extend `TestSummary` with plugin metrics
-`src/domain/contract-validation.ts` — Add source attribution to violations
-`src/test/error-renderer.ts` — Show plugin source in failure output
## 6. Invariants
### 6.1 Registry Invariants
- **I1**: Plugin contract registration MUST be idempotent. Registering the same plugin twice with identical spec MUST NOT throw.
- **I2**: Plugin contract registration order MUST be deterministic and preserved in diagnostics.
- **I3**: The registry MUST NOT mutate plugin specs after registration. All returned specs MUST be deep copies.
### 6.2 Composition Invariants
- **I4**: Contract composition MUST be deterministic. Same route + same plugins = same composed contract.
- **I5**: Contract composition MUST be idempotent. Composing an already-composed route MUST produce identical results.
- **I6**: Plugin contracts MUST NOT override route contracts. If route and plugin declare the same formula, route's version takes precedence.
### 6.3 Execution Invariants
- **I7**: Plugin contracts MUST be tested even if route has no `x-plugins` annotation. `x-plugins` is for validation, not scoping.
- **I8**: Plugin contract failures MUST include plugin name in diagnostics. Users MUST know which plugin's contract failed.
- **I9**: Plugin contract warnings (missing plugins, pattern mismatches) MUST NOT fail the test suite. They are informational only.
## 7. Backward Compatibility
- Routes without `x-plugins` still receive matching plugin contracts; this is additive validation behavior and must be called out in migration notes.
- Plugins without `registerPluginContracts()` MUST NOT cause errors.
- Existing `TestSuite` and `TestResult` types MUST remain compatible. New fields are optional.
## 8. Open Questions
1.**Phase interception**: Can we actually test onRequest/onSend contracts separately without monkey-patching Fastify? If not, we test all plugin contracts against final context with phase noted.
2.**Plugin versioning**: Should contracts include version constraints? e.g., `@fastify/auth@^4.0.0` contracts.
3.**Conditional contracts**: Should plugins declare contracts conditionally based on configuration? e.g., auth plugin with `optional: true` mode.
4.**Performance**: Composing contracts for 10K routes with 20 plugins. O(n*m) is acceptable but should be cached.