Files
tyx_AI_xhs/apps/api/src/model-contract-evidence.ts
T

138 lines
7.2 KiB
TypeScript

import type BetterSqlite3 from "better-sqlite3";
import { ModelConfigurationService, modelIds, type ModelConfigCandidate } from "./model-configuration.js";
const safeRefPattern = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,159}$/;
const requiredRatios = ["3:4", "1:1", "4:3", "9:16"];
const requiredAdapterErrors = [
"upstream_timeout", "upstream_failed", "safety_rejected", "gateway_balance_insufficient",
"gateway_contract_invalid", "reference_invalid", "unknown_retryable", "unknown_non_retryable",
];
const forbiddenKeyFragments = ["credential", "password", "prompt_text", "raw", "secret", "token"];
export interface ModelContractEvidenceInput {
evidence_hash: string;
evidence_ref: string;
matrix: unknown;
model_id: string;
verified_at: string;
verifier_ref: string;
}
function hasForbiddenEvidenceKey(value: unknown, depth = 0): boolean {
if (depth > 8 || !value || typeof value !== "object") return false;
if (Array.isArray(value)) return value.some((entry) => hasForbiddenEvidenceKey(entry, depth + 1));
return Object.entries(value).some(([key, entry]) => (
forbiddenKeyFragments.some((fragment) => key.toLowerCase().includes(fragment))
|| hasForbiddenEvidenceKey(entry, depth + 1)
));
}
function validateEvidence(input: ModelContractEvidenceInput) {
if (!modelIds.includes(input.model_id as typeof modelIds[number])) throw new Error("contract_evidence_model_invalid");
if (!safeRefPattern.test(input.evidence_hash) || !safeRefPattern.test(input.evidence_ref) || !safeRefPattern.test(input.verifier_ref)) {
throw new Error("contract_evidence_reference_invalid");
}
const verifiedAt = Date.parse(input.verified_at);
if (!Number.isFinite(verifiedAt) || new Date(verifiedAt).toISOString() !== input.verified_at) throw new Error("contract_evidence_time_invalid");
if (hasForbiddenEvidenceKey(input.matrix)) throw new Error("contract_evidence_sensitive_field");
if (!input.matrix || typeof input.matrix !== "object") throw new Error("contract_evidence_matrix_incomplete");
const matrix = input.matrix as Record<string, unknown>;
const passedSingleOutput = (value: unknown) => Boolean(value && typeof value === "object"
&& "status" in value && value.status === "passed" && "outputs" in value && value.outputs === 1);
const ratios = Array.isArray(matrix.ratios) ? matrix.ratios as Array<Record<string, unknown>> : [];
const ratioNames = ratios.filter(passedSingleOutput).map((entry) => entry.ratio).toSorted();
const executionModes = Array.isArray(matrix.execution_modes) ? matrix.execution_modes : [];
const errorMapping = Array.isArray(matrix.error_mapping) ? matrix.error_mapping : [];
const executionComplete = executionModes.includes("sync") || (executionModes.includes("async") && executionModes.includes("poll"));
if (matrix.model_id !== input.model_id || !passedSingleOutput(matrix.pure_text) || !passedSingleOutput(matrix.reference_image)
|| JSON.stringify(ratioNames) !== JSON.stringify([...requiredRatios].toSorted()) || !executionComplete
|| !requiredAdapterErrors.every((category) => errorMapping.includes(category))) {
throw new Error("contract_evidence_matrix_incomplete");
}
return verifiedAt;
}
function editableCandidates(models: ReturnType<ModelConfigurationService["read"]>["models"]): ModelConfigCandidate[] {
return models.map(({ config_version: _configVersion, runtime_availability: _runtime, ...candidate }) => structuredClone(candidate));
}
export class ModelContractEvidenceService {
readonly database: BetterSqlite3.Database;
private readonly clock: () => number;
private readonly models: ModelConfigurationService;
constructor(input: { clock?: () => number; database: BetterSqlite3.Database; models: ModelConfigurationService }) {
this.clock = input.clock ?? Date.now;
this.database = input.database;
this.models = input.models;
this.migrate();
}
recordVerified(input: {
actorId: string;
evidence: ModelContractEvidenceInput;
expectedConfigSetVersion: number;
idempotencyKey: string;
}) {
const verifiedAt = validateEvidence(input.evidence);
return this.database.transaction(() => {
const current = this.models.read();
if (current.config_set_version !== input.expectedConfigSetVersion) throw new Error("contract_evidence_config_set_conflict");
const existingHash = this.database.prepare("SELECT model_id FROM model_contract_evidence WHERE evidence_hash = ?").get(input.evidence.evidence_hash) as { model_id: string } | undefined;
if (existingHash && existingHash.model_id !== input.evidence.model_id) throw new Error("contract_evidence_shared_between_models");
const candidates = editableCandidates(current.models);
const target = candidates.find((model) => model.model_id === input.evidence.model_id)!;
target.contract_validation_status = "verified";
target.contract_evidence_ref = input.evidence.evidence_ref;
const configuration = this.models.replace({
actorId: input.actorId,
expectedConfigSetVersion: input.expectedConfigSetVersion,
idempotencyKey: input.idempotencyKey,
models: candidates,
});
const model = configuration.models.find((candidate) => candidate.model_id === input.evidence.model_id)!;
this.database.prepare(`
INSERT OR IGNORE INTO model_contract_evidence (
model_id, config_version, evidence_hash, evidence_ref, verifier_ref, verified_at, evidence_json, created_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`).run(
input.evidence.model_id, model.config_version, input.evidence.evidence_hash, input.evidence.evidence_ref,
input.evidence.verifier_ref, verifiedAt, JSON.stringify(input.evidence.matrix), this.clock(),
);
return { configuration, evidence: structuredClone(input.evidence), model };
}).immediate();
}
read(modelId: string, configVersion: number) {
return this.database.prepare(`
SELECT model_id, config_version, evidence_hash, evidence_ref, verifier_ref, verified_at, evidence_json
FROM model_contract_evidence WHERE model_id = ? AND config_version = ?
`).get(modelId, configVersion) as {
config_version: number; evidence_hash: string; evidence_json: string; evidence_ref: string;
model_id: string; verified_at: number; verifier_ref: string;
} | undefined;
}
private migrate() {
this.database.exec(`
CREATE TABLE IF NOT EXISTS model_contract_evidence (
model_id TEXT NOT NULL,
config_version INTEGER NOT NULL,
evidence_hash TEXT NOT NULL UNIQUE,
evidence_ref TEXT NOT NULL,
verifier_ref TEXT NOT NULL,
verified_at INTEGER NOT NULL,
evidence_json TEXT NOT NULL CHECK (json_valid(evidence_json)),
created_at INTEGER NOT NULL,
PRIMARY KEY (model_id, config_version),
FOREIGN KEY (model_id, config_version) REFERENCES model_config_versions(model_id, config_version)
);
CREATE TRIGGER IF NOT EXISTS model_contract_evidence_no_update BEFORE UPDATE ON model_contract_evidence
BEGIN SELECT RAISE(ABORT, 'model_contract_evidence_immutable'); END;
CREATE TRIGGER IF NOT EXISTS model_contract_evidence_no_delete BEFORE DELETE ON model_contract_evidence
BEGIN SELECT RAISE(ABORT, 'model_contract_evidence_immutable'); END;
`);
}
}