/** * Plugin contract discipline bundle (H2). * * Three primitives that close the "freeform receipt" gap and give every plugin * solver the same proof-carrying guarantees the engine's ContractedSimulation * provides — without importing the engine or duplicating its machinery: * * 1. **PluginSolverContract** — declarative pre/inv/post clause declarations * (JSON-safe data, not functions). The manifest references one by * `manifest.solverContract: string`. Registered in `PluginSolverContractRegistry`. * * 2. **SolverReceiptSchemaRegistry** — maps receipt schema IDs to validator * functions. A plugin registers a schema; the engine validates solver output * before issuing a receipt. Without a registered schema the receipt is * labeled `freeform: true` (honest) — no silent pass-through. * * 3. **wrapSolverInContract** — wraps a plugin solver function so that: * - preconditions are checked before the solver runs, * - postconditions are checked after, * - output is validated against the registered receipt schema. * Returns a `WrappedSolverResult` that carries all violations + the * schema verdict — ready to feed into a `DomainSimulationReceipt`. * * ## Domain-neutrality * * No domain nouns appear here. Plugins supply their own clause IDs, * descriptions, and validator logic. Core only provides the skeleton. * * ## Design alignment * * - `PluginContractClause.severity` mirrors `ContractClause.severity` in engine. * - `PluginClauseViolation` mirrors `ClauseViolation` in engine. * - `PluginClauseContext` is simpler than engine's `ClauseContext` — no * `getField`/`simTime` (those are engine-side FEM solver concepts). Plugin * clauses observe `config` (pre-solve) and `result` (post-solve). */ export type PluginClauseKind = 'precondition' | 'invariant' | 'postcondition'; /** * A single clause in a `PluginSolverContract`. * * Intentionally JSON-serializable: no function references. Evaluators * (the actual booleans) are registered separately via * `PluginSolverContractRegistry.registerEvaluator()`. */ export interface PluginContractClause { /** Stable identifier — appears in violation records and the receipt witness. */ id: string; kind: PluginClauseKind; /** Human-readable description of what this clause asserts. */ description: string; /** 'error' (default) blocks `contractVerified: true`; 'warning' does not. */ severity?: 'error' | 'warning'; /** * For `'invariant'` clauses: evaluate every N solver steps. Default: 1. * Ignored for preconditions / postconditions. */ cadence?: number; } /** * Declarative plugin solver contract — the plugin-scope equivalent of * `ContractClause` sets in `SimulationContract`. * * JSON-serializable: `manifest.solverContract` references this by `id`. */ export interface PluginSolverContract { /** * Stable contract identifier. Must match `manifest.solverContract`. * Convention: `"-v"` (e.g., `"retail-ecommerce-v1"`). */ id: string; /** Plugin that owns this contract. Must match `manifest.id`. */ pluginId: string; /** Optional human-readable summary of what this solver proves. */ description?: string; /** Clauses checked before the solver runs. Failure aborts execution. */ preconditions: PluginContractClause[]; /** Clauses checked during each solver step. */ invariants: PluginContractClause[]; /** Clauses checked after the solver completes. */ postconditions: PluginContractClause[]; } /** * Read-only context supplied to a clause evaluator function. * * Simpler than engine's `ClauseContext` (no `getField`/`simTime`) — plugin * clauses observe configuration (pre-solve) and solver output (post-solve). */ export interface PluginClauseContext, TResult = unknown> { /** Frozen solver configuration (always available). */ readonly config: Readonly; /** * Solver result — `undefined` for preconditions, populated for postconditions. * Use optional chaining: `ctx.result?.fieldName`. */ readonly result: Readonly | undefined; /** The owning plugin's manifest id. */ readonly pluginId: string; } /** Evaluator function type — returns true when the clause HOLDS. */ export type PluginClauseEvaluator, TResult = unknown> = (ctx: PluginClauseContext) => boolean; /** A single clause violation recorded by `wrapSolverInContract`. */ export interface PluginClauseViolation { clauseId: string; kind: PluginClauseKind; severity: 'error' | 'warning'; message: string; pluginId: string; } /** A single schema validation failure. */ export interface SchemaViolation { field: string; message: string; } /** Result of running a `ReceiptSchemaValidator`. */ export interface SchemaValidationResult { valid: boolean; violations: SchemaViolation[]; } /** Validates a solver's raw result object against a declared output shape. */ export type ReceiptSchemaValidator = (result: unknown) => SchemaValidationResult; /** A registered receipt schema entry. */ export interface SolverReceiptSchemaEntry { /** Stable schema identifier. Must match `manifest.receiptSchema`. */ id: string; /** Plugin that registered this schema. */ pluginId: string; /** Optional description of what fields the schema expects. */ description?: string; validate: ReceiptSchemaValidator; } /** * Return type of a solver wrapped by `wrapSolverInContract`. * * Carry this into `DomainSimulationReceipt.resultSummary` (or a subtype): * it records both the solver's native output AND the contract proof state. */ export interface WrappedSolverResult { /** Raw solver output. */ result: TResult; /** * True iff all error-severity preconditions and postconditions passed. * Warning-severity violations don't flip this to false. */ contractVerified: boolean; /** * Result of schema validation. * - `true` → schema registered and validation passed. * - `false` → schema registered but validation failed. * - `null` → no schema registered (freeform; manifest.receiptSchema absent). */ schemaValid: boolean | null; /** Schema ID that was used (null when freeform). */ schemaId: string | null; /** All clause violations (error + warning). */ clauseViolations: PluginClauseViolation[]; /** Schema validation violations (empty when schemaValid !== false). */ schemaViolations: SchemaViolation[]; /** Contract ID applied to this result (null when no contract). Provenance for ComposedReceipt. */ contractId: string | null; /** Plugin that owns the contract (null when no contract). Provenance for ComposedReceipt. */ pluginId: string | null; } /** * Registry of `PluginSolverContract` declarations and their associated * clause evaluators. * * Plugins call `registerContract()` during plugin registration (before scene * init) to declare what their solver proves. `wrapSolverInContract` consults * this registry at call time. */ export declare class PluginSolverContractRegistry { private readonly contracts; private readonly evaluators; /** * Register a solver contract declaration. * * @throws If a contract with the same `id` is already registered by a * different plugin. */ registerContract(contract: PluginSolverContract): void; /** * Register a clause evaluator function for a specific clause within a contract. * * @param contractId The contract's `id`. * @param clauseId The clause's `id` within that contract. * @param evaluator Returns true when the clause holds. */ registerEvaluator(contractId: string, clauseId: string, evaluator: PluginClauseEvaluator): void; /** Look up a registered contract by id. Returns undefined if not found. */ getContract(id: string): PluginSolverContract | undefined; /** Look up a clause evaluator. Returns undefined if not registered. */ getEvaluator(contractId: string, clauseId: string): PluginClauseEvaluator | undefined; /** All registered contracts (for introspection). */ allContracts(): ReadonlyArray; /** Count of registered contracts. */ get size(): number; } /** * Registry of receipt schema validators. * * Plugins call `registerSchema()` to declare the expected shape of their * solver output. The engine validates `result` against the schema before * issuing a receipt. A solver without a registered schema emits a receipt * marked `freeform: true` — no silent pass. */ export declare class SolverReceiptSchemaRegistry { private readonly schemas; /** * Register a receipt schema. * * @throws If a schema with the same `id` is registered by a different plugin. */ registerSchema(entry: SolverReceiptSchemaEntry): void; /** Look up a schema by id. Returns undefined → caller labels receipt freeform. */ getSchema(id: string): SolverReceiptSchemaEntry | undefined; /** * Validate `result` against the schema registered under `schemaId`. * Returns `null` if no schema is registered (freeform). */ validate(schemaId: string, result: unknown): SchemaValidationResult | null; /** All registered schemas (for introspection). */ allSchemas(): ReadonlyArray; /** Count of registered schemas. */ get size(): number; } /** Global contract registry — one table per process. */ export declare const globalContractRegistry: PluginSolverContractRegistry; /** Global schema registry — one table per process. */ export declare const globalSchemaRegistry: SolverReceiptSchemaRegistry; /** * Register a plugin solver contract into the global registry. * * Call during plugin registration alongside `registerPluginTraits`. */ export declare function registerPluginContract(contract: PluginSolverContract): void; /** * Register a clause evaluator for a contract clause into the global registry. * * @example * ```ts * registerContractClauseEvaluator('retail-ecommerce-v1', 'positive_demand', (ctx) => { * const cfg = ctx.config as { annualDemand?: number }; * return typeof cfg.annualDemand === 'number' && cfg.annualDemand > 0; * }); * ``` */ export declare function registerContractClauseEvaluator(contractId: string, clauseId: string, evaluator: PluginClauseEvaluator): void; /** * Register a receipt schema into the global schema registry. * * @example * ```ts * registerReceiptSchema({ * id: 'retail-ecommerce-receipt-v1', * pluginId: 'retail-ecommerce', * description: 'EOQ result shape', * validate: (result) => { * const r = result as Record; * const violations = []; * if (typeof r?.eoq !== 'number' || r.eoq <= 0) * violations.push({ field: 'eoq', message: 'must be a positive number' }); * return { valid: violations.length === 0, violations }; * }, * }); * ``` */ export declare function registerReceiptSchema(entry: SolverReceiptSchemaEntry): void; /** * Wrap a plugin solver function with contract + schema enforcement. * * The returned function: * 1. Checks registered preconditions against `config`. Aborts with a * `WrappedSolverResult` (result: undefined, contractVerified: false) if * any error-severity precondition fires — the solver never runs. * 2. Runs the solver. * 3. Checks registered postconditions against `{ config, result }`. * 4. Validates `result` against the registered receipt schema (if any). * 5. Returns `WrappedSolverResult` carrying all violations + the schema verdict. * * @param solver The raw plugin solver function. * @param contractId Contract id to look up in `globalContractRegistry`. * If not registered the solver runs unguarded (no clauses). * @param schemaId Receipt schema id to look up in `globalSchemaRegistry`. * If omitted or unregistered, `schemaValid: null` (freeform). * @param registry Contract registry to use (defaults to `globalContractRegistry`). * @param schemaReg Schema registry to use (defaults to `globalSchemaRegistry`). */ export declare function wrapSolverInContract, TResult>(solver: (config: TConfig) => TResult, contractId: string | undefined, schemaId: string | undefined, registry?: PluginSolverContractRegistry, schemaReg?: SolverReceiptSchemaRegistry): (config: TConfig) => WrappedSolverResult;