feat(ql3): establish 3.0 incubation baseline

This commit is contained in:
whyour
2026-08-12 00:25:26 +08:00
parent 4bf92dcfeb
commit c699c32461
2817 changed files with 779642 additions and 653 deletions
+331
View File
@@ -0,0 +1,331 @@
{
"name": "@qinglong/ai",
"version": "3.0.0-alpha.0",
"private": true,
"description": "QingLong 3.0 optional bounded model gateway and remote providers",
"license": "Apache-2.0",
"engines": {
"node": ">=24.18.0 <25"
},
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"require": "./dist/index.js",
"default": "./dist/index.js"
},
"./model": {
"types": "./dist/model-gateway/model.d.ts",
"require": "./dist/model-gateway/model.js",
"default": "./dist/model-gateway/model.js"
},
"./gateway": {
"types": "./dist/model-gateway/gateway.d.ts",
"require": "./dist/model-gateway/gateway.js",
"default": "./dist/model-gateway/gateway.js"
},
"./model-invocation": {
"types": "./dist/model-invocation/modelInvocation.d.ts",
"require": "./dist/model-invocation/modelInvocation.js",
"default": "./dist/model-invocation/modelInvocation.js"
},
"./model-invocation-resolution": {
"types": "./dist/model-invocation/modelInvocationResolution.d.ts",
"require": "./dist/model-invocation/modelInvocationResolution.js",
"default": "./dist/model-invocation/modelInvocationResolution.js"
},
"./durable-model-invocation": {
"types": "./dist/model-invocation/durableModelInvocationCoordinator.d.ts",
"require": "./dist/model-invocation/durableModelInvocationCoordinator.js",
"default": "./dist/model-invocation/durableModelInvocationCoordinator.js"
},
"./profile": {
"types": "./dist/profile/profileComposition.d.ts",
"require": "./dist/profile/profileComposition.js",
"default": "./dist/profile/profileComposition.js"
},
"./provider-credential": {
"types": "./dist/model-provider-credential/providerCredential.d.ts",
"require": "./dist/model-provider-credential/providerCredential.js",
"default": "./dist/model-provider-credential/providerCredential.js"
},
"./model-provider-credential-administration": {
"types": "./dist/model-provider-credential/modelProviderCredentialAdministration.d.ts",
"require": "./dist/model-provider-credential/modelProviderCredentialAdministration.js",
"default": "./dist/model-provider-credential/modelProviderCredentialAdministration.js"
},
"./model-provider-credential-catalog": {
"types": "./dist/model-provider-credential/modelProviderCredentialCatalog.d.ts",
"require": "./dist/model-provider-credential/modelProviderCredentialCatalog.js",
"default": "./dist/model-provider-credential/modelProviderCredentialCatalog.js"
},
"./postgres-model-provider-credential-storage": {
"types": "./dist/model-provider-credential/postgresModelProviderCredentialRepository.d.ts",
"require": "./dist/model-provider-credential/postgresModelProviderCredentialRepository.js",
"default": "./dist/model-provider-credential/postgresModelProviderCredentialRepository.js"
},
"./postgres-model-provider-credential-management-identity-ledger": {
"types": "./dist/model-provider-credential/postgresModelProviderCredentialManagementIdentityLedger.d.ts",
"require": "./dist/model-provider-credential/postgresModelProviderCredentialManagementIdentityLedger.js",
"default": "./dist/model-provider-credential/postgresModelProviderCredentialManagementIdentityLedger.js"
},
"./postgres-model-provider-credential-management-audit-query": {
"types": "./dist/model-provider-credential/postgresModelProviderCredentialManagementAuditQuery.d.ts",
"require": "./dist/model-provider-credential/postgresModelProviderCredentialManagementAuditQuery.js",
"default": "./dist/model-provider-credential/postgresModelProviderCredentialManagementAuditQuery.js"
},
"./model-provider-credential-test-connection": {
"types": "./dist/model-provider-credential/modelProviderCredentialTestConnection.d.ts",
"require": "./dist/model-provider-credential/modelProviderCredentialTestConnection.js",
"default": "./dist/model-provider-credential/modelProviderCredentialTestConnection.js"
},
"./postgres-model-provider-credential-test-connection": {
"types": "./dist/model-provider-credential/postgresModelProviderCredentialTestConnection.d.ts",
"require": "./dist/model-provider-credential/postgresModelProviderCredentialTestConnection.js",
"default": "./dist/model-provider-credential/postgresModelProviderCredentialTestConnection.js"
},
"./projected-model-provider-secret-material": {
"types": "./dist/model-provider-credential/projectedModelProviderSecretMaterial.d.ts",
"require": "./dist/model-provider-credential/projectedModelProviderSecretMaterial.js",
"default": "./dist/model-provider-credential/projectedModelProviderSecretMaterial.js"
},
"./projected-model-gateway-authority": {
"types": "./dist/model-gateway/projectedModelGatewayAuthority.d.ts",
"require": "./dist/model-gateway/projectedModelGatewayAuthority.js",
"default": "./dist/model-gateway/projectedModelGatewayAuthority.js"
},
"./usage-ledger": {
"types": "./dist/usage/usageLedger.d.ts",
"require": "./dist/usage/usageLedger.js",
"default": "./dist/usage/usageLedger.js"
},
"./usage-quota": {
"types": "./dist/usage/usageQuota.d.ts",
"require": "./dist/usage/usageQuota.js",
"default": "./dist/usage/usageQuota.js"
},
"./pricing": {
"types": "./dist/pricing/pricing.d.ts",
"require": "./dist/pricing/pricing.js",
"default": "./dist/pricing/pricing.js"
},
"./plugin-package-prompt-execution": {
"types": "./dist/prompt/pluginPackagePromptExecution.d.ts",
"require": "./dist/prompt/pluginPackagePromptExecution.js",
"default": "./dist/prompt/pluginPackagePromptExecution.js"
},
"./plugin-package-prompt-catalog": {
"types": "./dist/prompt/pluginPackagePromptCatalog.d.ts",
"require": "./dist/prompt/pluginPackagePromptCatalog.js",
"default": "./dist/prompt/pluginPackagePromptCatalog.js"
},
"./plugin-package-prompt-execution-inspection": {
"types": "./dist/prompt/pluginPackagePromptExecutionInspection.d.ts",
"require": "./dist/prompt/pluginPackagePromptExecutionInspection.js",
"default": "./dist/prompt/pluginPackagePromptExecutionInspection.js"
},
"./local-plugin-package-prompt-execution-inspection": {
"types": "./dist/prompt/localPluginPackagePromptExecutionInspectionRepository.d.ts",
"require": "./dist/prompt/localPluginPackagePromptExecutionInspectionRepository.js",
"default": "./dist/prompt/localPluginPackagePromptExecutionInspectionRepository.js"
},
"./plugin-package-prompt-executor": {
"types": "./dist/prompt/pluginPackagePromptExecutor.d.ts",
"require": "./dist/prompt/pluginPackagePromptExecutor.js",
"default": "./dist/prompt/pluginPackagePromptExecutor.js"
},
"./plugin-package-prompt-output-artifact": {
"types": "./dist/prompt-output/pluginPackagePromptOutputArtifact.d.ts",
"require": "./dist/prompt-output/pluginPackagePromptOutputArtifact.js",
"default": "./dist/prompt-output/pluginPackagePromptOutputArtifact.js"
},
"./plugin-package-prompt-output-completion": {
"types": "./dist/prompt-output/pluginPackagePromptOutputCompletion.d.ts",
"require": "./dist/prompt-output/pluginPackagePromptOutputCompletion.js",
"default": "./dist/prompt-output/pluginPackagePromptOutputCompletion.js"
},
"./plugin-package-prompt-output-read": {
"types": "./dist/prompt-output/pluginPackagePromptOutputRead.d.ts",
"require": "./dist/prompt-output/pluginPackagePromptOutputRead.js",
"default": "./dist/prompt-output/pluginPackagePromptOutputRead.js"
},
"./plugin-package-prompt-execution-output-read": {
"types": "./dist/prompt-output/pluginPackagePromptExecutionOutputRead.d.ts",
"require": "./dist/prompt-output/pluginPackagePromptExecutionOutputRead.js",
"default": "./dist/prompt-output/pluginPackagePromptExecutionOutputRead.js"
},
"./plugin-package-prompt-output-retention": {
"types": "./dist/prompt-output/pluginPackagePromptOutputRetention.d.ts",
"require": "./dist/prompt-output/pluginPackagePromptOutputRetention.js",
"default": "./dist/prompt-output/pluginPackagePromptOutputRetention.js"
},
"./plugin-package-prompt-output-key-retirement": {
"types": "./dist/prompt-output/key-management/pluginPackagePromptOutputKeyRetirement.d.ts",
"require": "./dist/prompt-output/key-management/pluginPackagePromptOutputKeyRetirement.js",
"default": "./dist/prompt-output/key-management/pluginPackagePromptOutputKeyRetirement.js"
},
"./plugin-package-prompt-output-key-rotation": {
"types": "./dist/prompt-output/key-management/pluginPackagePromptOutputKeyRotation.d.ts",
"require": "./dist/prompt-output/key-management/pluginPackagePromptOutputKeyRotation.js",
"default": "./dist/prompt-output/key-management/pluginPackagePromptOutputKeyRotation.js"
},
"./plugin-package-prompt-output-file-keyring": {
"types": "./dist/prompt-output/key-management/pluginPackagePromptOutputFileKeyring.d.ts",
"require": "./dist/prompt-output/key-management/pluginPackagePromptOutputFileKeyring.js",
"default": "./dist/prompt-output/key-management/pluginPackagePromptOutputFileKeyring.js"
},
"./plugin-package-prompt-output-keyring-manifest": {
"types": "./dist/prompt-output/key-management/pluginPackagePromptOutputKeyringManifest.d.ts",
"require": "./dist/prompt-output/key-management/pluginPackagePromptOutputKeyringManifest.js",
"default": "./dist/prompt-output/key-management/pluginPackagePromptOutputKeyringManifest.js"
},
"./plugin-package-prompt-output-external-custody": {
"types": "./dist/prompt-output/custody/pluginPackagePromptOutputExternalCustody.d.ts",
"require": "./dist/prompt-output/custody/pluginPackagePromptOutputExternalCustody.js",
"default": "./dist/prompt-output/custody/pluginPackagePromptOutputExternalCustody.js"
},
"./plugin-package-prompt-output-external-custody-bundle": {
"types": "./dist/prompt-output/custody/pluginPackagePromptOutputExternalCustodyBundle.d.ts",
"require": "./dist/prompt-output/custody/pluginPackagePromptOutputExternalCustodyBundle.js",
"default": "./dist/prompt-output/custody/pluginPackagePromptOutputExternalCustodyBundle.js"
},
"./plugin-package-prompt-output-external-recovery-authorization": {
"types": "./dist/prompt-output/custody/pluginPackagePromptOutputExternalRecoveryAuthorization.d.ts",
"require": "./dist/prompt-output/custody/pluginPackagePromptOutputExternalRecoveryAuthorization.js",
"default": "./dist/prompt-output/custody/pluginPackagePromptOutputExternalRecoveryAuthorization.js"
},
"./plugin-package-prompt-output-projected-keyring": {
"types": "./dist/prompt-output/key-management/pluginPackagePromptOutputProjectedKeyring.d.ts",
"require": "./dist/prompt-output/key-management/pluginPackagePromptOutputProjectedKeyring.js",
"default": "./dist/prompt-output/key-management/pluginPackagePromptOutputProjectedKeyring.js"
},
"./price-catalog": {
"types": "./dist/pricing/modelPriceCatalog.d.ts",
"require": "./dist/pricing/modelPriceCatalog.js",
"default": "./dist/pricing/modelPriceCatalog.js"
},
"./price-catalog-management": {
"types": "./dist/pricing/modelPriceCatalogManagement.d.ts",
"require": "./dist/pricing/modelPriceCatalogManagement.js",
"default": "./dist/pricing/modelPriceCatalogManagement.js"
},
"./local-price-catalog-storage": {
"types": "./dist/pricing/storage/localModelPriceCatalogRepository.d.ts",
"require": "./dist/pricing/storage/localModelPriceCatalogRepository.js",
"default": "./dist/pricing/storage/localModelPriceCatalogRepository.js"
},
"./postgres-price-catalog-storage": {
"types": "./dist/pricing/storage/postgresModelPriceCatalogRepository.d.ts",
"require": "./dist/pricing/storage/postgresModelPriceCatalogRepository.js",
"default": "./dist/pricing/storage/postgresModelPriceCatalogRepository.js"
},
"./model-invocation-migration": {
"types": "./dist/migration/modelInvocationMigration.d.ts",
"require": "./dist/migration/modelInvocationMigration.js",
"default": "./dist/migration/modelInvocationMigration.js"
},
"./local-feature-activation": {
"types": "./dist/feature-activation/localModelInvocationFeatureActivation.d.ts",
"require": "./dist/feature-activation/localModelInvocationFeatureActivation.js",
"default": "./dist/feature-activation/localModelInvocationFeatureActivation.js"
},
"./local-model-invocation-storage": {
"types": "./dist/model-invocation/localModelInvocationRepository.d.ts",
"require": "./dist/model-invocation/localModelInvocationRepository.js",
"default": "./dist/model-invocation/localModelInvocationRepository.js"
},
"./local-model-provider-credential-storage": {
"types": "./dist/model-provider-credential/localModelProviderCredentialRepository.d.ts",
"require": "./dist/model-provider-credential/localModelProviderCredentialRepository.js",
"default": "./dist/model-provider-credential/localModelProviderCredentialRepository.js"
},
"./local-plugin-package-prompt-admission-storage": {
"types": "./dist/prompt/localPluginPackagePromptAdmissionRepository.d.ts",
"require": "./dist/prompt/localPluginPackagePromptAdmissionRepository.js",
"default": "./dist/prompt/localPluginPackagePromptAdmissionRepository.js"
},
"./local-plugin-package-prompt-output-artifact-storage": {
"types": "./dist/prompt-output/storage/localPluginPackagePromptOutputArtifactRepository.d.ts",
"require": "./dist/prompt-output/storage/localPluginPackagePromptOutputArtifactRepository.js",
"default": "./dist/prompt-output/storage/localPluginPackagePromptOutputArtifactRepository.js"
},
"./local-plugin-package-prompt-execution-output-reference-storage": {
"types": "./dist/prompt-output/storage/localPluginPackagePromptExecutionOutputReferenceRepository.d.ts",
"require": "./dist/prompt-output/storage/localPluginPackagePromptExecutionOutputReferenceRepository.js",
"default": "./dist/prompt-output/storage/localPluginPackagePromptExecutionOutputReferenceRepository.js"
},
"./local-plugin-package-prompt-output-retention-storage": {
"types": "./dist/prompt-output/storage/localPluginPackagePromptOutputRetentionRepository.d.ts",
"require": "./dist/prompt-output/storage/localPluginPackagePromptOutputRetentionRepository.js",
"default": "./dist/prompt-output/storage/localPluginPackagePromptOutputRetentionRepository.js"
},
"./local-plugin-package-prompt-output-key-retirement-storage": {
"types": "./dist/prompt-output/storage/localPluginPackagePromptOutputKeyRetirementRepository.d.ts",
"require": "./dist/prompt-output/storage/localPluginPackagePromptOutputKeyRetirementRepository.js",
"default": "./dist/prompt-output/storage/localPluginPackagePromptOutputKeyRetirementRepository.js"
},
"./postgres-model-invocation-storage": {
"types": "./dist/model-invocation/postgresModelInvocationRepository.d.ts",
"require": "./dist/model-invocation/postgresModelInvocationRepository.js",
"default": "./dist/model-invocation/postgresModelInvocationRepository.js"
},
"./postgres-plugin-package-prompt-admission-storage": {
"types": "./dist/prompt/postgresPluginPackagePromptAdmissionRepository.d.ts",
"require": "./dist/prompt/postgresPluginPackagePromptAdmissionRepository.js",
"default": "./dist/prompt/postgresPluginPackagePromptAdmissionRepository.js"
},
"./postgres-plugin-package-prompt-output-artifact-storage": {
"types": "./dist/prompt-output/storage/postgresPluginPackagePromptOutputArtifactRepository.d.ts",
"require": "./dist/prompt-output/storage/postgresPluginPackagePromptOutputArtifactRepository.js",
"default": "./dist/prompt-output/storage/postgresPluginPackagePromptOutputArtifactRepository.js"
},
"./postgres-plugin-package-prompt-execution-output-reference-storage": {
"types": "./dist/prompt-output/storage/postgresPluginPackagePromptExecutionOutputReferenceRepository.d.ts",
"require": "./dist/prompt-output/storage/postgresPluginPackagePromptExecutionOutputReferenceRepository.js",
"default": "./dist/prompt-output/storage/postgresPluginPackagePromptExecutionOutputReferenceRepository.js"
},
"./postgres-plugin-package-prompt-output-retention-storage": {
"types": "./dist/prompt-output/storage/postgresPluginPackagePromptOutputRetentionRepository.d.ts",
"require": "./dist/prompt-output/storage/postgresPluginPackagePromptOutputRetentionRepository.js",
"default": "./dist/prompt-output/storage/postgresPluginPackagePromptOutputRetentionRepository.js"
},
"./postgres-plugin-package-prompt-output-key-retirement-storage": {
"types": "./dist/prompt-output/storage/postgresPluginPackagePromptOutputKeyRetirementRepository.d.ts",
"require": "./dist/prompt-output/storage/postgresPluginPackagePromptOutputKeyRetirementRepository.js",
"default": "./dist/prompt-output/storage/postgresPluginPackagePromptOutputKeyRetirementRepository.js"
},
"./postgres-plugin-package-prompt-output-key-rotation-storage": {
"types": "./dist/prompt-output/storage/postgresPluginPackagePromptOutputKeyRotationRepository.d.ts",
"require": "./dist/prompt-output/storage/postgresPluginPackagePromptOutputKeyRotationRepository.js",
"default": "./dist/prompt-output/storage/postgresPluginPackagePromptOutputKeyRotationRepository.js"
},
"./postgres-plugin-package-prompt-application": {
"types": "./dist/prompt/postgresPluginPackagePromptApplication.d.ts",
"require": "./dist/prompt/postgresPluginPackagePromptApplication.js",
"default": "./dist/prompt/postgresPluginPackagePromptApplication.js"
},
"./openai-compatible": {
"types": "./dist/model-gateway/openAiCompatibleProvider.d.ts",
"require": "./dist/model-gateway/openAiCompatibleProvider.js",
"default": "./dist/model-gateway/openAiCompatibleProvider.js"
}
},
"files": [
"dist/**/*.js",
"dist/**/*.d.ts"
],
"scripts": {
"build": "tsc -p tsconfig.json",
"check": "node ../../scripts/ql3-build-package-closure.cjs && tsc -p tsconfig.json --noEmit",
"test": "node ../../scripts/ql3-build-package-closure.cjs && node --test test/*.test.cjs"
},
"dependencies": {
"@qinglong/runtime-core": "workspace:*"
},
"devDependencies": {
"@qinglong/local-sqlite": "workspace:*",
"@types/node": "24.13.3",
"typescript": "5.9.3"
}
}
@@ -0,0 +1,654 @@
import { Buffer } from 'node:buffer';
import { createHash } from 'node:crypto';
import type { DatabaseSync } from 'node:sqlite';
import type { SecurityPrincipal } from '@qinglong/runtime-core/security';
import {
LOCAL_MODEL_INVOCATION_MIGRATION_PLAN_DIGEST,
assertLocalModelInvocationFeatureReady,
} from '../migration/modelInvocationMigration';
export const LOCAL_MODEL_INVOCATION_FEATURE_ID = 'model-invocation' as const;
export const LOCAL_MODEL_INVOCATION_FEATURE_TRANSITION_SCHEMA =
'qinglong/model-invocation-feature-transition@v1' as const;
export const LOCAL_MODEL_INVOCATION_FEATURE_STATES = [
'active',
'inactive',
] as const;
export const LOCAL_MODEL_INVOCATION_FEATURE_SAFETY_MODES = [
'fresh_database',
'backup_verified',
'preserve_existing',
] as const;
export type LocalModelInvocationFeatureState =
(typeof LOCAL_MODEL_INVOCATION_FEATURE_STATES)[number];
export type LocalModelInvocationFeatureSafetyMode =
(typeof LOCAL_MODEL_INVOCATION_FEATURE_SAFETY_MODES)[number];
export interface LocalModelInvocationFeatureTransitionCommand {
readonly featureId: typeof LOCAL_MODEL_INVOCATION_FEATURE_ID;
readonly expectedGeneration: number;
readonly expectedState: LocalModelInvocationFeatureState | null;
readonly state: LocalModelInvocationFeatureState;
readonly mutationId: string;
readonly requestId: string;
readonly expectedMigrationDigest: string;
readonly safety: Readonly<{
readonly mode: LocalModelInvocationFeatureSafetyMode;
readonly backupEvidenceDigest: string | null;
}>;
readonly principal: Readonly<SecurityPrincipal>;
readonly commandDigest: string;
}
export interface LocalModelInvocationFeatureTransition {
readonly schema: typeof LOCAL_MODEL_INVOCATION_FEATURE_TRANSITION_SCHEMA;
readonly featureId: typeof LOCAL_MODEL_INVOCATION_FEATURE_ID;
readonly generation: number;
readonly previousGeneration: number | null;
readonly state: LocalModelInvocationFeatureState;
readonly mutationId: string;
readonly requestId: string;
readonly expectedMigrationDigest: string;
readonly safety: Readonly<{
readonly mode: LocalModelInvocationFeatureSafetyMode;
readonly backupEvidenceDigest: string | null;
}>;
readonly changedByUserId: string;
readonly authenticationId: string;
readonly assurance: 'local_console';
readonly commandDigest: string;
readonly committedAtMs: number;
readonly transitionDigest: string;
}
export interface CommitLocalModelInvocationFeatureTransitionResult {
readonly status: 'created' | 'existing';
readonly transition: Readonly<LocalModelInvocationFeatureTransition>;
}
export interface LocalModelInvocationFeatureActivationRepositoryOptions {
readonly beforeMutation?: (
client: DatabaseSync,
command: Readonly<LocalModelInvocationFeatureTransitionCommand>,
) => void;
}
export class InvalidLocalModelInvocationFeatureTransitionError extends TypeError {
readonly code = 'LOCAL_MODEL_INVOCATION_FEATURE_TRANSITION_INVALID';
constructor(message: string) {
super(`Local ModelInvocation feature transition is invalid: ${message}`);
this.name = 'InvalidLocalModelInvocationFeatureTransitionError';
}
}
export class LocalModelInvocationFeatureTransitionConflictError extends Error {
readonly code = 'LOCAL_MODEL_INVOCATION_FEATURE_TRANSITION_CONFLICT';
constructor() {
super(
'Local ModelInvocation feature transition conflicts with durable state',
);
this.name = 'LocalModelInvocationFeatureTransitionConflictError';
}
}
export class LocalModelInvocationFeatureTransitionUnavailableError extends Error {
readonly code = 'LOCAL_MODEL_INVOCATION_FEATURE_TRANSITION_UNAVAILABLE';
constructor(options?: ErrorOptions) {
super('Local ModelInvocation feature transition is unavailable', options);
this.name = 'LocalModelInvocationFeatureTransitionUnavailableError';
}
}
const IDENTITY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
const DIGEST_PATTERN = /^[0-9a-f]{64}$/;
const COMMAND_DIGEST_DOMAIN = Buffer.from(
'qinglong/local-model-invocation-feature-command@v1\0',
'utf8',
);
const TRANSITION_DIGEST_DOMAIN = Buffer.from(
'qinglong/local-model-invocation-feature-transition@v1\0',
'utf8',
);
function invalid(message: string): never {
throw new InvalidLocalModelInvocationFeatureTransitionError(message);
}
function digest(value: unknown, label: string): string {
if (typeof value !== 'string' || !DIGEST_PATTERN.test(value)) {
return invalid(`${label} is invalid`);
}
return value;
}
function identity(value: unknown, label: string): string {
if (typeof value !== 'string' || !IDENTITY_PATTERN.test(value)) {
return invalid(`${label} is invalid`);
}
return value;
}
function generation(value: unknown, label: string, minimum: number): number {
if (
!Number.isSafeInteger(value) ||
(value as number) < minimum ||
(value as number) > 2_147_483_647
) {
return invalid(`${label} is invalid`);
}
return value as number;
}
function nonnegativeInteger(value: unknown, label: string): number {
if (!Number.isSafeInteger(value) || (value as number) < 0) {
return invalid(`${label} is invalid`);
}
return value as number;
}
function state(value: unknown): LocalModelInvocationFeatureState {
if (value !== 'active' && value !== 'inactive') {
return invalid('state is invalid');
}
return value;
}
function hash(domain: Buffer, value: object): string {
return createHash('sha256')
.update(domain)
.update(JSON.stringify(value), 'utf8')
.digest('hex');
}
function principalIdentity(principal: Readonly<SecurityPrincipal>): Readonly<{
changedByUserId: string;
authenticationId: string;
assurance: 'local_console';
}> {
if (
!principal ||
typeof principal !== 'object' ||
Array.isArray(principal) ||
principal.subject?.type !== 'user' ||
typeof principal.subject.id !== 'string' ||
principal.subject.id.length < 1 ||
principal.subject.id.length > 255 ||
!IDENTITY_PATTERN.test(principal.authenticationId) ||
principal.assurance !== 'local_console'
) {
return invalid('principal must be a local-console User');
}
return Object.freeze({
changedByUserId: principal.subject.id,
authenticationId: principal.authenticationId,
assurance: principal.assurance,
});
}
function safety(
value: LocalModelInvocationFeatureTransitionCommand['safety'],
targetState: LocalModelInvocationFeatureState,
): Readonly<LocalModelInvocationFeatureTransitionCommand['safety']> {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
return invalid('safety is invalid');
}
const actual = Object.keys(value).sort();
if (
actual.length !== 2 ||
actual[0] !== 'backupEvidenceDigest' ||
actual[1] !== 'mode'
) {
return invalid('safety shape is invalid');
}
const backupEvidenceDigest =
value.backupEvidenceDigest === null
? null
: digest(value.backupEvidenceDigest, 'backup evidence digest');
if (
(targetState === 'active' &&
!(
(value.mode === 'fresh_database' && backupEvidenceDigest === null) ||
(value.mode === 'backup_verified' && backupEvidenceDigest !== null)
)) ||
(targetState === 'inactive' &&
(value.mode !== 'preserve_existing' || backupEvidenceDigest !== null))
) {
return invalid('safety mode does not match the target state');
}
return Object.freeze({
mode: value.mode,
backupEvidenceDigest,
});
}
function commandDigestValue(
value: Omit<LocalModelInvocationFeatureTransitionCommand, 'commandDigest'>,
): object {
const principal = principalIdentity(value.principal);
return {
featureId: value.featureId,
expectedGeneration: value.expectedGeneration,
expectedState: value.expectedState,
state: value.state,
mutationId: value.mutationId,
requestId: value.requestId,
expectedMigrationDigest: value.expectedMigrationDigest,
safety: value.safety,
changedByUserId: principal.changedByUserId,
authenticationId: principal.authenticationId,
assurance: principal.assurance,
};
}
export function createLocalModelInvocationFeatureTransitionCommand(
value: Omit<LocalModelInvocationFeatureTransitionCommand, 'commandDigest'>,
): Readonly<LocalModelInvocationFeatureTransitionCommand> {
if (
value.featureId !== LOCAL_MODEL_INVOCATION_FEATURE_ID ||
(value.expectedState !== null &&
value.expectedState !== 'active' &&
value.expectedState !== 'inactive')
) {
return invalid('feature or expected state is invalid');
}
const normalizedState = state(value.state);
const expectedGeneration = generation(
value.expectedGeneration,
'expected generation',
0,
);
if (
(expectedGeneration === 0 && value.expectedState !== null) ||
(expectedGeneration > 0 && value.expectedState === null) ||
normalizedState === value.expectedState
) {
return invalid('state transition fence is invalid');
}
const normalized = Object.freeze({
featureId: LOCAL_MODEL_INVOCATION_FEATURE_ID,
expectedGeneration,
expectedState: value.expectedState,
state: normalizedState,
mutationId: identity(value.mutationId, 'mutation ID'),
requestId: identity(value.requestId, 'request ID'),
expectedMigrationDigest: digest(
value.expectedMigrationDigest,
'expected migration digest',
),
safety: safety(value.safety, normalizedState),
principal: value.principal,
});
return Object.freeze({
...normalized,
commandDigest: hash(COMMAND_DIGEST_DOMAIN, commandDigestValue(normalized)),
});
}
function transitionDigestValue(
value: Omit<LocalModelInvocationFeatureTransition, 'transitionDigest'>,
): object {
return { ...value };
}
function createTransition(
command: Readonly<LocalModelInvocationFeatureTransitionCommand>,
committedAtMs: number,
): Readonly<LocalModelInvocationFeatureTransition> {
if (!Number.isSafeInteger(committedAtMs) || committedAtMs < 0) {
return invalid('database clock is invalid');
}
const principal = principalIdentity(command.principal);
const value = Object.freeze({
schema: LOCAL_MODEL_INVOCATION_FEATURE_TRANSITION_SCHEMA,
featureId: LOCAL_MODEL_INVOCATION_FEATURE_ID,
generation: command.expectedGeneration + 1,
previousGeneration:
command.expectedGeneration === 0 ? null : command.expectedGeneration,
state: command.state,
mutationId: command.mutationId,
requestId: command.requestId,
expectedMigrationDigest: command.expectedMigrationDigest,
safety: command.safety,
...principal,
commandDigest: command.commandDigest,
committedAtMs,
});
return Object.freeze({
...value,
transitionDigest: hash(
TRANSITION_DIGEST_DOMAIN,
transitionDigestValue(value),
),
});
}
interface TransitionRow extends Record<string, unknown> {
readonly transitionJson: unknown;
}
function normalizeTransitionJson(
value: unknown,
): LocalModelInvocationFeatureTransition {
if (
typeof value !== 'string' ||
Buffer.byteLength(value, 'utf8') > 32 * 1024
) {
return invalid('durable transition JSON is invalid');
}
let parsed: unknown;
try {
parsed = JSON.parse(value);
} catch {
return invalid('durable transition JSON is invalid');
}
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
return invalid('durable transition JSON is invalid');
}
const candidate = parsed as Record<string, unknown>;
const expectedKeys = [
'assurance',
'authenticationId',
'changedByUserId',
'commandDigest',
'committedAtMs',
'expectedMigrationDigest',
'featureId',
'generation',
'mutationId',
'previousGeneration',
'requestId',
'safety',
'schema',
'state',
'transitionDigest',
].sort();
if (
Object.keys(candidate).sort().join('\0') !== expectedKeys.join('\0') ||
candidate.schema !== LOCAL_MODEL_INVOCATION_FEATURE_TRANSITION_SCHEMA ||
candidate.featureId !== LOCAL_MODEL_INVOCATION_FEATURE_ID ||
candidate.assurance !== 'local_console' ||
typeof candidate.changedByUserId !== 'string' ||
candidate.changedByUserId.length < 1 ||
candidate.changedByUserId.length > 255 ||
!IDENTITY_PATTERN.test(candidate.authenticationId as string) ||
!IDENTITY_PATTERN.test(candidate.mutationId as string) ||
!IDENTITY_PATTERN.test(candidate.requestId as string)
) {
return invalid('durable transition JSON is invalid');
}
const normalizedState = state(candidate.state);
const normalizedGeneration = generation(
candidate.generation,
'durable generation',
1,
);
const previousGeneration =
candidate.previousGeneration === null
? null
: generation(
candidate.previousGeneration,
'durable previous generation',
1,
);
const normalizedSafety = safety(
candidate.safety as LocalModelInvocationFeatureTransitionCommand['safety'],
normalizedState,
);
const normalized = Object.freeze({
schema: LOCAL_MODEL_INVOCATION_FEATURE_TRANSITION_SCHEMA,
featureId: LOCAL_MODEL_INVOCATION_FEATURE_ID,
generation: normalizedGeneration,
previousGeneration,
state: normalizedState,
mutationId: candidate.mutationId as string,
requestId: candidate.requestId as string,
expectedMigrationDigest: digest(
candidate.expectedMigrationDigest,
'durable expected migration digest',
),
safety: normalizedSafety,
changedByUserId: candidate.changedByUserId,
authenticationId: candidate.authenticationId as string,
assurance: 'local_console' as const,
commandDigest: digest(candidate.commandDigest, 'durable command digest'),
committedAtMs: nonnegativeInteger(
candidate.committedAtMs,
'durable committed time',
),
});
const transitionDigest = digest(
candidate.transitionDigest,
'durable transition digest',
);
if (
hash(TRANSITION_DIGEST_DOMAIN, transitionDigestValue(normalized)) !==
transitionDigest
) {
return invalid('durable transition digest is invalid');
}
return Object.freeze({ ...normalized, transitionDigest });
}
function sameCommand(
command: Readonly<LocalModelInvocationFeatureTransitionCommand>,
transition: Readonly<LocalModelInvocationFeatureTransition>,
): boolean {
const principal = principalIdentity(command.principal);
return (
transition.featureId === command.featureId &&
transition.generation === command.expectedGeneration + 1 &&
transition.previousGeneration ===
(command.expectedGeneration === 0 ? null : command.expectedGeneration) &&
transition.state === command.state &&
transition.mutationId === command.mutationId &&
transition.requestId === command.requestId &&
transition.expectedMigrationDigest === command.expectedMigrationDigest &&
JSON.stringify(transition.safety) === JSON.stringify(command.safety) &&
transition.changedByUserId === principal.changedByUserId &&
transition.authenticationId === principal.authenticationId &&
transition.assurance === principal.assurance &&
transition.commandDigest === command.commandDigest
);
}
export class LocalModelInvocationFeatureActivationRepository {
readonly #beforeMutation:
| LocalModelInvocationFeatureActivationRepositoryOptions['beforeMutation'];
constructor(
private readonly client: DatabaseSync,
options: LocalModelInvocationFeatureActivationRepositoryOptions = {},
) {
if (
!client ||
typeof client !== 'object' ||
!client.isOpen ||
typeof options !== 'object' ||
options === null ||
Array.isArray(options) ||
Object.keys(options).some((key) => key !== 'beforeMutation') ||
(options.beforeMutation !== undefined &&
typeof options.beforeMutation !== 'function')
) {
throw new TypeError(
'Local ModelInvocation feature activation repository options are invalid',
);
}
this.#beforeMutation = options.beforeMutation;
}
findCurrent(): Readonly<LocalModelInvocationFeatureTransition> | null {
assertLocalModelInvocationFeatureReady(this.client);
const row = this.client
.prepare(
`SELECT transition.transition_json AS "transitionJson"
FROM "ModelInvocationFeatureHead" head
JOIN "ModelInvocationFeatureTransitions" transition
ON transition.feature_id = head.feature_id
AND transition.generation = head.generation
AND transition.transition_digest = head.transition_digest
WHERE head.feature_id = ?`,
)
.get(LOCAL_MODEL_INVOCATION_FEATURE_ID) as TransitionRow | undefined;
return row ? normalizeTransitionJson(row.transitionJson) : null;
}
transition(
commandValue: Readonly<LocalModelInvocationFeatureTransitionCommand>,
): Readonly<CommitLocalModelInvocationFeatureTransitionResult> {
const command = createLocalModelInvocationFeatureTransitionCommand({
featureId: commandValue.featureId,
expectedGeneration: commandValue.expectedGeneration,
expectedState: commandValue.expectedState,
state: commandValue.state,
mutationId: commandValue.mutationId,
requestId: commandValue.requestId,
expectedMigrationDigest: commandValue.expectedMigrationDigest,
safety: commandValue.safety,
principal: commandValue.principal,
});
if (
commandValue.commandDigest !== command.commandDigest ||
command.expectedMigrationDigest !==
LOCAL_MODEL_INVOCATION_MIGRATION_PLAN_DIGEST
) {
throw new LocalModelInvocationFeatureTransitionConflictError();
}
assertLocalModelInvocationFeatureReady(this.client);
this.client.exec('BEGIN IMMEDIATE');
try {
this.#beforeMutation?.(this.client, command);
const replay = this.client
.prepare(
`SELECT transition_json AS "transitionJson"
FROM "ModelInvocationFeatureTransitions"
WHERE mutation_id = ?`,
)
.get(command.mutationId) as TransitionRow | undefined;
if (replay) {
const transition = normalizeTransitionJson(replay.transitionJson);
if (!sameCommand(command, transition)) {
throw new LocalModelInvocationFeatureTransitionConflictError();
}
this.client.exec('COMMIT');
return Object.freeze({ status: 'existing', transition });
}
const current = this.findCurrent();
if (
current?.generation !==
(command.expectedGeneration === 0
? undefined
: command.expectedGeneration) ||
(current?.state ?? null) !== command.expectedState
) {
throw new LocalModelInvocationFeatureTransitionConflictError();
}
const clock = this.client
.prepare(`SELECT CAST(unixepoch('subsec') * 1000 AS INTEGER) AS now`)
.get() as { readonly now?: unknown };
const transition = createTransition(
command,
nonnegativeInteger(clock.now, 'database clock'),
);
const transitionJson = JSON.stringify(transition);
this.client
.prepare(
`INSERT INTO "ModelInvocationFeatureTransitions" (
feature_id, generation, previous_generation, state,
mutation_id, request_id, expected_migration_digest,
safety_mode, backup_evidence_digest, changed_by_user_id,
authentication_id, assurance, command_digest,
transition_digest, committed_at_ms, transition_json
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(
transition.featureId,
transition.generation,
transition.previousGeneration,
transition.state,
transition.mutationId,
transition.requestId,
transition.expectedMigrationDigest,
transition.safety.mode,
transition.safety.backupEvidenceDigest,
transition.changedByUserId,
transition.authenticationId,
transition.assurance,
transition.commandDigest,
transition.transitionDigest,
transition.committedAtMs,
transitionJson,
);
if (command.expectedGeneration === 0) {
this.client
.prepare(
`INSERT INTO "ModelInvocationFeatureHead" (
feature_id, generation, state, transition_digest, updated_at_ms
) VALUES (?, ?, ?, ?, ?)`,
)
.run(
transition.featureId,
transition.generation,
transition.state,
transition.transitionDigest,
transition.committedAtMs,
);
} else {
const updated = this.client
.prepare(
`UPDATE "ModelInvocationFeatureHead"
SET generation = ?,
state = ?,
transition_digest = ?,
updated_at_ms = ?
WHERE feature_id = ?
AND generation = ?
AND state = ?`,
)
.run(
transition.generation,
transition.state,
transition.transitionDigest,
transition.committedAtMs,
transition.featureId,
command.expectedGeneration,
command.expectedState,
);
if (updated.changes !== 1) {
throw new LocalModelInvocationFeatureTransitionConflictError();
}
}
this.client.exec('COMMIT');
return Object.freeze({ status: 'created', transition });
} catch (error) {
if (this.client.isTransaction) this.client.exec('ROLLBACK');
if (
error instanceof InvalidLocalModelInvocationFeatureTransitionError ||
error instanceof LocalModelInvocationFeatureTransitionConflictError
) {
throw error;
}
throw new LocalModelInvocationFeatureTransitionUnavailableError({
cause: error instanceof Error ? error : undefined,
});
}
}
}
export function assertLocalModelInvocationFeatureActive(
client: DatabaseSync,
): Readonly<LocalModelInvocationFeatureTransition> {
const current = new LocalModelInvocationFeatureActivationRepository(
client,
).findCurrent();
if (current?.state !== 'active') {
throw new LocalModelInvocationFeatureTransitionUnavailableError();
}
return current;
}
+15
View File
@@ -0,0 +1,15 @@
export * from './model-gateway/model';
export * from './model-gateway/validation';
export * from './model-gateway/gateway';
export * from './model-invocation/modelInvocation';
export * from './model-invocation/modelInvocationResolution';
export * from './model-invocation/durableModelInvocationCoordinator';
export * from './profile/profileComposition';
export * from './model-provider-credential/providerCredential';
export * from './usage/usageLedger';
export * from './usage/usageQuota';
export * from './pricing/pricing';
export * from './pricing/modelPriceCatalog';
export * from './pricing/modelPriceCatalogManagement';
export * from './model-gateway/openAiCompatibleProvider';
export * from './prompt/postgresPluginPackagePromptApplication';
@@ -0,0 +1,68 @@
export const LOCAL_MODEL_INVOCATION_MIGRATION_STREAM_ID =
'ql3-ai-model-invocation-sqlite';
export const LOCAL_MODEL_INVOCATION_MIGRATION_ID = '9001-ai-model-invocations';
export const LOCAL_MODEL_INVOCATION_USAGE_MIGRATION_ID =
'9002-ai-model-usage-ledger';
export const LOCAL_MODEL_INVOCATION_QUOTA_MIGRATION_ID =
'9003-ai-model-usage-quota';
export const LOCAL_MODEL_INVOCATION_PRICING_MIGRATION_ID =
'9004-ai-model-pricing-snapshots';
export const LOCAL_MODEL_PRICE_CATALOG_MIGRATION_ID =
'9005-ai-model-price-catalog';
export const LOCAL_MODEL_PRICE_CATALOG_AUTHORIZATION_MIGRATION_ID =
'9006-ai-model-price-catalog-authorizations';
export const LOCAL_MODEL_INVOCATION_FEATURE_ACTIVATION_MIGRATION_ID =
'9007-ai-feature-activation';
export const LOCAL_PLUGIN_PACKAGE_PROMPT_ADMISSION_MIGRATION_ID =
'9008-ai-plugin-package-prompt-admissions';
export const LOCAL_PLUGIN_PACKAGE_PROMPT_FINALIZATION_MIGRATION_ID =
'9009-ai-plugin-package-prompt-finalizations';
export const LOCAL_PLUGIN_PACKAGE_PROMPT_OUTPUT_ARTIFACT_MIGRATION_ID =
'9010-ai-plugin-package-prompt-output-artifacts';
export const LOCAL_PLUGIN_PACKAGE_PROMPT_OUTPUT_TOMBSTONE_MIGRATION_ID =
'9011-ai-plugin-package-prompt-output-tombstones';
export const LOCAL_PLUGIN_PACKAGE_PROMPT_OUTPUT_KEY_RETIREMENT_MIGRATION_ID =
'9012-ai-plugin-package-prompt-output-key-retirements';
export const LOCAL_MODEL_PROVIDER_CREDENTIAL_CATALOG_MIGRATION_ID =
'9013-ai-model-provider-credential-catalog';
export const POSTGRES_MODEL_INVOCATION_MIGRATION_STREAM_ID =
'ql3-ai-model-invocation-postgresql';
export const POSTGRES_MODEL_INVOCATION_MIGRATION_ID =
'pg-9001-ai-model-invocations';
export const POSTGRES_MODEL_INVOCATION_USAGE_MIGRATION_ID =
'pg-9002-ai-model-usage-ledger';
export const POSTGRES_MODEL_INVOCATION_QUOTA_MIGRATION_ID =
'pg-9003-ai-model-usage-quota';
export const POSTGRES_MODEL_INVOCATION_PRICING_MIGRATION_ID =
'pg-9004-ai-model-pricing-snapshots';
export const POSTGRES_MODEL_PRICE_CATALOG_MIGRATION_ID =
'pg-9005-ai-model-price-catalog';
export const POSTGRES_MODEL_PRICE_CATALOG_AUTHORIZATION_MIGRATION_ID =
'pg-9006-ai-model-price-catalog-authorizations';
export const POSTGRES_PLUGIN_PACKAGE_PROMPT_ADMISSION_MIGRATION_ID =
'pg-9007-ai-plugin-package-prompt-admissions';
export const POSTGRES_PLUGIN_PACKAGE_PROMPT_FINALIZATION_MIGRATION_ID =
'pg-9008-ai-plugin-package-prompt-finalizations';
export const POSTGRES_PLUGIN_PACKAGE_PROMPT_OUTPUT_ARTIFACT_MIGRATION_ID =
'pg-9009-ai-plugin-package-prompt-output-artifacts';
export const POSTGRES_PLUGIN_PACKAGE_PROMPT_OUTPUT_TOMBSTONE_MIGRATION_ID =
'pg-9010-ai-plugin-package-prompt-output-tombstones';
export const POSTGRES_PLUGIN_PACKAGE_PROMPT_OUTPUT_KEY_RETIREMENT_MIGRATION_ID =
'pg-9011-ai-plugin-package-prompt-output-key-retirements';
export const POSTGRES_MODEL_PROVIDER_CREDENTIAL_CATALOG_MIGRATION_ID =
'pg-9012-ai-model-provider-credential-catalog';
export const POSTGRES_MODEL_PROVIDER_CREDENTIAL_MANAGEMENT_MIGRATION_ID =
'pg-9013-ai-model-provider-credential-management-boundary';
export const POSTGRES_MODEL_PROVIDER_CREDENTIAL_MANAGEMENT_IDENTITY_MIGRATION_ID =
'pg-9014-ai-model-provider-credential-management-identity-ledger';
export const POSTGRES_MODEL_PROVIDER_CREDENTIAL_TEST_CONNECTION_MIGRATION_ID =
'pg-9015-ai-model-provider-credential-test-connection';
export const POSTGRES_PLUGIN_PACKAGE_PROMPT_OUTPUT_KEY_ROTATION_MIGRATION_ID =
'pg-9016-ai-plugin-package-prompt-output-key-rotation';
export const POSTGRES_PLUGIN_PACKAGE_PROMPT_PRODUCT_AUTHORIZATION_MIGRATION_ID =
'pg-9017-ai-plugin-package-prompt-product-authorization';
export const POSTGRES_MODEL_INVOCATION_SCHEMA = 'ql3_ai';
export const LOCAL_MODEL_INVOCATION_MIGRATION_HISTORY_TABLE =
'QingLong3AiSchemaMigrations';
export const POSTGRES_MODEL_INVOCATION_MIGRATION_HISTORY_TABLE =
'ai_schema_migrations';
@@ -0,0 +1,259 @@
import type {
PostgresPool,
PostgresQueryable,
} from '@qinglong/runtime-core';
import {
MigrationStreamHistoryCorruptionError,
runMigrationStream,
type MigrationStreamDefinition,
type MigrationStreamRecord,
type MigrationStreamStore,
type MigrationStreamTransaction,
} from '@qinglong/runtime-core/migration-stream';
import {
POSTGRES_MODEL_INVOCATION_MIGRATION_STREAM_ID,
POSTGRES_MODEL_INVOCATION_MIGRATION_ID,
POSTGRES_MODEL_INVOCATION_USAGE_MIGRATION_ID,
POSTGRES_MODEL_INVOCATION_QUOTA_MIGRATION_ID,
POSTGRES_MODEL_INVOCATION_PRICING_MIGRATION_ID,
POSTGRES_MODEL_PRICE_CATALOG_MIGRATION_ID,
POSTGRES_MODEL_PRICE_CATALOG_AUTHORIZATION_MIGRATION_ID,
POSTGRES_PLUGIN_PACKAGE_PROMPT_ADMISSION_MIGRATION_ID,
POSTGRES_PLUGIN_PACKAGE_PROMPT_FINALIZATION_MIGRATION_ID,
POSTGRES_PLUGIN_PACKAGE_PROMPT_OUTPUT_ARTIFACT_MIGRATION_ID,
POSTGRES_PLUGIN_PACKAGE_PROMPT_OUTPUT_TOMBSTONE_MIGRATION_ID,
POSTGRES_PLUGIN_PACKAGE_PROMPT_OUTPUT_KEY_RETIREMENT_MIGRATION_ID,
POSTGRES_MODEL_PROVIDER_CREDENTIAL_CATALOG_MIGRATION_ID,
POSTGRES_MODEL_PROVIDER_CREDENTIAL_MANAGEMENT_MIGRATION_ID,
POSTGRES_MODEL_PROVIDER_CREDENTIAL_MANAGEMENT_IDENTITY_MIGRATION_ID,
POSTGRES_MODEL_PROVIDER_CREDENTIAL_TEST_CONNECTION_MIGRATION_ID,
POSTGRES_PLUGIN_PACKAGE_PROMPT_OUTPUT_KEY_ROTATION_MIGRATION_ID,
POSTGRES_PLUGIN_PACKAGE_PROMPT_PRODUCT_AUTHORIZATION_MIGRATION_ID,
POSTGRES_MODEL_INVOCATION_SCHEMA,
POSTGRES_MODEL_INVOCATION_MIGRATION_HISTORY_TABLE,
} from './identities';
import { postgresCatalogMigrations } from './postgres/catalog';
import { postgresCoreMigrations } from './postgres/core';
import { postgresCredentialMigrations } from './postgres/credential';
import {
postgresPromptBaseMigrations,
postgresPromptExtensionMigrations,
} from './postgres/prompt';
import { postgresUsagePricingMigrations } from './postgres/usagePricing';
import { historyRecord, type HistoryRow } from './shared';
const POSTGRES_HISTORY_IDENTITY = Object.freeze({
migrationIds: Object.freeze([
POSTGRES_MODEL_INVOCATION_MIGRATION_ID,
POSTGRES_MODEL_INVOCATION_USAGE_MIGRATION_ID,
POSTGRES_MODEL_INVOCATION_QUOTA_MIGRATION_ID,
POSTGRES_MODEL_INVOCATION_PRICING_MIGRATION_ID,
POSTGRES_MODEL_PRICE_CATALOG_MIGRATION_ID,
POSTGRES_MODEL_PRICE_CATALOG_AUTHORIZATION_MIGRATION_ID,
POSTGRES_PLUGIN_PACKAGE_PROMPT_ADMISSION_MIGRATION_ID,
POSTGRES_PLUGIN_PACKAGE_PROMPT_FINALIZATION_MIGRATION_ID,
POSTGRES_PLUGIN_PACKAGE_PROMPT_OUTPUT_ARTIFACT_MIGRATION_ID,
POSTGRES_PLUGIN_PACKAGE_PROMPT_OUTPUT_TOMBSTONE_MIGRATION_ID,
POSTGRES_PLUGIN_PACKAGE_PROMPT_OUTPUT_KEY_RETIREMENT_MIGRATION_ID,
POSTGRES_PLUGIN_PACKAGE_PROMPT_OUTPUT_KEY_ROTATION_MIGRATION_ID,
POSTGRES_MODEL_PROVIDER_CREDENTIAL_CATALOG_MIGRATION_ID,
POSTGRES_MODEL_PROVIDER_CREDENTIAL_MANAGEMENT_MIGRATION_ID,
POSTGRES_MODEL_PROVIDER_CREDENTIAL_MANAGEMENT_IDENTITY_MIGRATION_ID,
POSTGRES_MODEL_PROVIDER_CREDENTIAL_TEST_CONNECTION_MIGRATION_ID,
POSTGRES_PLUGIN_PACKAGE_PROMPT_PRODUCT_AUTHORIZATION_MIGRATION_ID,
]),
streamId: POSTGRES_MODEL_INVOCATION_MIGRATION_STREAM_ID,
dialect: 'postgresql' as const,
});
async function readPostgresHistory(
queryable: PostgresQueryable,
migrationId?: string,
): Promise<readonly MigrationStreamRecord[]> {
const result = await queryable.query<HistoryRow>(
`SELECT
migration_id AS "migrationId",
stream_id AS "streamId",
dialect,
checksum,
applied_at_ms AS "appliedAtMs"
FROM "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."${POSTGRES_MODEL_INVOCATION_MIGRATION_HISTORY_TABLE}"
WHERE stream_id = $1
${migrationId === undefined ? '' : 'AND migration_id = $2'}
ORDER BY migration_id`,
migrationId === undefined
? [POSTGRES_MODEL_INVOCATION_MIGRATION_STREAM_ID]
: [POSTGRES_MODEL_INVOCATION_MIGRATION_STREAM_ID, migrationId],
);
return result.rows.map((row) =>
historyRecord(row, POSTGRES_HISTORY_IDENTITY),
);
}
class PostgresModelInvocationMigrationStore
implements MigrationStreamStore<PostgresQueryable>
{
constructor(private readonly pool: PostgresPool) {}
async ensureHistory(): Promise<void> {
const result = await this.pool.query<Record<string, unknown>>(
`SELECT
to_regclass('ql3.schema_migrations') IS NOT NULL AS history,
to_regclass('ql3.runs') IS NOT NULL AS runs,
to_regclass('ql3.run_events') IS NOT NULL AS events,
to_regclass('ql3.step_runs') IS NOT NULL AS steps,
to_regclass('ql3.step_run_mutations') IS NOT NULL AS mutations`,
);
const row = result.rows[0];
if (
result.rows.length !== 1 ||
row?.history !== true ||
row.runs !== true ||
row.events !== true ||
row.steps !== true ||
row.mutations !== true
) {
throw new TypeError(
'PostgreSQL ModelInvocation feature requires the main migration stream',
);
}
await this.pool.query(
`CREATE SCHEMA IF NOT EXISTS "${POSTGRES_MODEL_INVOCATION_SCHEMA}"`,
);
await this.pool.query(
`REVOKE ALL ON SCHEMA "${POSTGRES_MODEL_INVOCATION_SCHEMA}" FROM PUBLIC`,
);
await this.pool.query(`
CREATE TABLE IF NOT EXISTS "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."${POSTGRES_MODEL_INVOCATION_MIGRATION_HISTORY_TABLE}" (
migration_id varchar(128) PRIMARY KEY,
stream_id varchar(64) NOT NULL
CONSTRAINT ql3_ai_schema_migrations_stream_check
CHECK (stream_id = '${POSTGRES_MODEL_INVOCATION_MIGRATION_STREAM_ID}'),
dialect varchar(16) NOT NULL
CONSTRAINT ql3_ai_schema_migrations_dialect_check
CHECK (dialect = 'postgresql'),
checksum char(64) NOT NULL
CONSTRAINT ql3_ai_schema_migrations_checksum_check
CHECK (checksum ~ '^[0-9a-f]{64}$'),
applied_at_ms bigint NOT NULL
CONSTRAINT ql3_ai_schema_migrations_applied_at_check
CHECK (applied_at_ms >= 0)
)
`);
await this.pool.query(
`REVOKE ALL ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."${POSTGRES_MODEL_INVOCATION_MIGRATION_HISTORY_TABLE}"
FROM PUBLIC`,
);
}
async listAll(): Promise<readonly MigrationStreamRecord[]> {
return readPostgresHistory(this.pool);
}
async findById(migrationId: string): Promise<MigrationStreamRecord | null> {
const rows = await readPostgresHistory(this.pool, migrationId);
if (rows.length > 1) {
throw new MigrationStreamHistoryCorruptionError(migrationId);
}
return rows[0] ?? null;
}
async transaction<T>(
work: (
transaction: MigrationStreamTransaction<PostgresQueryable>,
) => Promise<T>,
): Promise<T> {
const client = await this.pool.connect();
let began = false;
try {
await client.query('BEGIN');
began = true;
await client.query(`SELECT set_config('statement_timeout', $1, true)`, [
'15s',
]);
await client.query(`SELECT set_config('lock_timeout', $1, true)`, ['5s']);
await client.query(
`SELECT set_config('idle_in_transaction_session_timeout', $1, true)`,
['15s'],
);
await client.query(
'SELECT pg_advisory_xact_lock($1, $2)',
[0x514c, 0x0301],
);
const result = await work({
context: client,
findById: async (migrationId) => {
const rows = await readPostgresHistory(client, migrationId);
return rows[0] ?? null;
},
insert: async (record) => {
if (
record.streamId !== POSTGRES_MODEL_INVOCATION_MIGRATION_STREAM_ID ||
!POSTGRES_HISTORY_IDENTITY.migrationIds.includes(
record.migrationId,
) ||
record.dialect !== 'postgresql'
) {
throw new TypeError(
'PostgreSQL ModelInvocation migration identity is invalid',
);
}
await client.query(
`INSERT INTO "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."${POSTGRES_MODEL_INVOCATION_MIGRATION_HISTORY_TABLE}"
(migration_id, stream_id, dialect, checksum, applied_at_ms)
VALUES ($1, $2, $3, $4, $5)`,
[
record.migrationId,
record.streamId,
record.dialect,
record.checksum,
record.appliedAtMs,
],
);
},
});
await client.query('COMMIT');
began = false;
return result;
} catch (error) {
if (began) {
try {
await client.query('ROLLBACK');
} catch {
// Preserve the migration failure.
}
}
throw error;
} finally {
client.release();
}
}
}
export const postgresModelInvocationMigrationDefinition: MigrationStreamDefinition<PostgresQueryable> =
Object.freeze({
id: POSTGRES_MODEL_INVOCATION_MIGRATION_STREAM_ID,
dialect: 'postgresql',
migrationIdScheme: 'postgres-prefixed',
checksumScheme: 'sha256',
migrations: Object.freeze([
...postgresCoreMigrations,
...postgresUsagePricingMigrations,
...postgresCatalogMigrations,
...postgresPromptBaseMigrations,
...postgresCredentialMigrations,
...postgresPromptExtensionMigrations,
]),
});
export async function migratePostgresModelInvocationFeature(
pool: PostgresPool,
): Promise<void> {
await runMigrationStream({
stream: postgresModelInvocationMigrationDefinition,
store: new PostgresModelInvocationMigrationStore(pool),
});
}
@@ -0,0 +1,301 @@
import type { PostgresQueryable } from '@qinglong/runtime-core';
import {
POSTGRES_MODEL_PRICE_CATALOG_MIGRATION_ID,
POSTGRES_MODEL_PRICE_CATALOG_AUTHORIZATION_MIGRATION_ID,
POSTGRES_MODEL_INVOCATION_SCHEMA,
} from '../identities';
import { defineSqlMigration } from '../shared';
const POSTGRES_MODEL_PRICE_CATALOG_PUBLICATION_TABLE_SQL = `
CREATE TABLE "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_price_catalog_publications" (
provider varchar(128) NOT NULL,
model varchar(128) NOT NULL,
price_revision varchar(128) NOT NULL,
catalog_digest char(64) NOT NULL,
mutation_id varchar(128) NOT NULL UNIQUE,
command_digest char(64) NOT NULL,
publication_digest char(64) NOT NULL,
published_at_ms bigint NOT NULL,
published_by_user_id varchar(128) NOT NULL,
publication_json jsonb NOT NULL,
PRIMARY KEY (provider, model, price_revision),
CONSTRAINT ql3_model_price_catalog_publication_identity_check CHECK (
provider ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
model ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
price_revision ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
mutation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
published_by_user_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$'
),
CONSTRAINT ql3_model_price_catalog_publication_value_check CHECK (
published_at_ms >= 0 AND
catalog_digest ~ '^[0-9a-f]{64}$' AND
command_digest ~ '^[0-9a-f]{64}$' AND
publication_digest ~ '^[0-9a-f]{64}$'
),
CONSTRAINT ql3_model_price_catalog_publication_json_check CHECK (
jsonb_typeof(publication_json) = 'object' AND
octet_length(publication_json::text) BETWEEN 2 AND 24576 AND
publication_json @> jsonb_build_object(
'schema', 'qinglong/model-price-catalog-publication@v1',
'mutationId', mutation_id,
'publishedByUserId', published_by_user_id,
'commandDigest', command_digest,
'publicationDigest', publication_digest
) AND
publication_json->'entry' @> jsonb_build_object(
'schema', 'qinglong/model-price-catalog-entry@v1',
'provider', provider,
'model', model,
'priceRevision', price_revision,
'catalogDigest', catalog_digest,
'publishedAtMs', published_at_ms
)
)
)`;
const POSTGRES_MODEL_PRICE_CATALOG_HEAD_TABLE_SQL = `
CREATE TABLE "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_price_catalog_heads" (
provider varchar(128) NOT NULL,
model varchar(128) NOT NULL,
generation integer NOT NULL,
previous_head_digest char(64),
active_price_revision varchar(128),
active_catalog_digest char(64),
revoked_price_revision varchar(128),
revoked_catalog_digest char(64),
action varchar(16) NOT NULL,
mutation_id varchar(128) NOT NULL UNIQUE,
changed_by_user_id varchar(128) NOT NULL,
changed_at_ms bigint NOT NULL,
command_digest char(64) NOT NULL,
head_digest char(64) NOT NULL UNIQUE,
head_json jsonb NOT NULL,
PRIMARY KEY (provider, model, generation),
FOREIGN KEY (previous_head_digest)
REFERENCES "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_price_catalog_heads"
(head_digest) ON DELETE RESTRICT,
FOREIGN KEY (
provider, model, active_price_revision, active_catalog_digest
) REFERENCES "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_price_catalog_publications" (
provider, model, price_revision, catalog_digest
) ON DELETE RESTRICT,
FOREIGN KEY (
provider, model, revoked_price_revision, revoked_catalog_digest
) REFERENCES "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_price_catalog_publications" (
provider, model, price_revision, catalog_digest
) ON DELETE RESTRICT,
CONSTRAINT ql3_model_price_catalog_head_identity_check CHECK (
provider ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
model ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
mutation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
changed_by_user_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$'
),
CONSTRAINT ql3_model_price_catalog_head_value_check CHECK (
generation BETWEEN 1 AND 2147483647 AND
((generation = 1 AND previous_head_digest IS NULL) OR
(generation > 1 AND previous_head_digest IS NOT NULL)) AND
((active_price_revision IS NULL AND active_catalog_digest IS NULL) OR
(active_price_revision IS NOT NULL AND
active_catalog_digest IS NOT NULL)) AND
((revoked_price_revision IS NULL AND revoked_catalog_digest IS NULL) OR
(revoked_price_revision IS NOT NULL AND
revoked_catalog_digest IS NOT NULL)) AND
(revoked_price_revision IS NULL OR
revoked_price_revision <> active_price_revision) AND
action IN ('activate', 'deactivate', 'revoke') AND
((action = 'revoke') = (revoked_price_revision IS NOT NULL)) AND
changed_at_ms >= 0 AND
(previous_head_digest IS NULL OR
previous_head_digest ~ '^[0-9a-f]{64}$') AND
(active_catalog_digest IS NULL OR
active_catalog_digest ~ '^[0-9a-f]{64}$') AND
(revoked_catalog_digest IS NULL OR
revoked_catalog_digest ~ '^[0-9a-f]{64}$') AND
command_digest ~ '^[0-9a-f]{64}$' AND
head_digest ~ '^[0-9a-f]{64}$'
),
CONSTRAINT ql3_model_price_catalog_head_json_check CHECK (
jsonb_typeof(head_json) = 'object' AND
octet_length(head_json::text) BETWEEN 2 AND 24576 AND
head_json @> jsonb_build_object(
'schema', 'qinglong/model-price-catalog-head@v1',
'provider', provider,
'model', model,
'generation', generation,
'previousHeadDigest', previous_head_digest,
'activePriceRevision', active_price_revision,
'activeCatalogDigest', active_catalog_digest,
'revokedPriceRevision', revoked_price_revision,
'revokedCatalogDigest', revoked_catalog_digest,
'action', action,
'mutationId', mutation_id,
'changedByUserId', changed_by_user_id,
'changedAtMs', changed_at_ms,
'commandDigest', command_digest,
'headDigest', head_digest
)
)
)`;
const postgresModelPriceCatalogMigration =
defineSqlMigration<PostgresQueryable>(
POSTGRES_MODEL_PRICE_CATALOG_MIGRATION_ID,
[
POSTGRES_MODEL_PRICE_CATALOG_PUBLICATION_TABLE_SQL,
`CREATE UNIQUE INDEX ql3_model_price_catalog_publication_identity_digest_uidx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_price_catalog_publications"
(provider, model, price_revision, catalog_digest)`,
`CREATE UNIQUE INDEX ql3_model_price_catalog_publication_digest_uidx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_price_catalog_publications"
(publication_digest)`,
POSTGRES_MODEL_PRICE_CATALOG_HEAD_TABLE_SQL,
`CREATE INDEX ql3_model_price_catalog_head_current_idx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_price_catalog_heads"
(provider, model, generation DESC)`,
`CREATE UNIQUE INDEX ql3_model_price_catalog_revoked_revision_uidx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_price_catalog_heads"
(provider, model, revoked_price_revision)
WHERE revoked_price_revision IS NOT NULL`,
`GRANT USAGE ON SCHEMA "${POSTGRES_MODEL_INVOCATION_SCHEMA}"
TO ql3_admin`,
`REVOKE ALL ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_price_catalog_publications",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_price_catalog_heads"
FROM PUBLIC`,
`GRANT SELECT ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_price_catalog_publications",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_price_catalog_heads"
TO ql3_runtime`,
`GRANT SELECT, INSERT ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_price_catalog_publications",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_price_catalog_heads"
TO ql3_admin`,
],
(context, statement) => context.query(statement).then(() => undefined),
);
const POSTGRES_MODEL_PRICE_CATALOG_AUTHORIZATION_TABLE_SQL = `
CREATE TABLE "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_price_catalog_authorizations" (
authorization_id varchar(128) PRIMARY KEY,
request_id varchar(128) NOT NULL,
operation varchar(16) NOT NULL,
provider varchar(128) NOT NULL,
model varchar(128) NOT NULL,
price_revision varchar(128),
catalog_command_digest char(64) NOT NULL UNIQUE,
publication_digest char(64) UNIQUE,
head_digest char(64) UNIQUE,
result_digest char(64) NOT NULL UNIQUE,
user_id varchar(128) NOT NULL,
authentication_id varchar(128) NOT NULL,
assurance varchar(32) NOT NULL,
authenticated_at_ms bigint NOT NULL,
expires_at_ms bigint NOT NULL,
policy_revision varchar(128) NOT NULL,
policy_decision_digest char(64) NOT NULL,
decision_mode varchar(32) NOT NULL,
command_digest char(64) NOT NULL UNIQUE,
committed_at_ms bigint NOT NULL,
authorization_digest char(64) NOT NULL UNIQUE,
reasons_json jsonb NOT NULL,
authorization_json jsonb NOT NULL,
FOREIGN KEY (publication_digest)
REFERENCES "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_price_catalog_publications"
(publication_digest) ON DELETE RESTRICT,
FOREIGN KEY (head_digest)
REFERENCES "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_price_catalog_heads"
(head_digest) ON DELETE RESTRICT,
CONSTRAINT ql3_model_price_catalog_authorization_identity_check CHECK (
authorization_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
request_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
provider ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
model ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
(price_revision IS NULL OR
price_revision ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$') AND
user_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
authentication_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
policy_revision ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$'
),
CONSTRAINT ql3_model_price_catalog_authorization_value_check CHECK (
operation IN ('publish', 'activate', 'deactivate', 'revoke') AND
((operation = 'deactivate' AND price_revision IS NULL) OR
(operation <> 'deactivate' AND price_revision IS NOT NULL)) AND
((operation = 'publish' AND publication_digest IS NOT NULL AND
head_digest IS NULL AND result_digest = publication_digest) OR
(operation <> 'publish' AND publication_digest IS NULL AND
head_digest IS NOT NULL AND result_digest = head_digest)) AND
assurance IN ('multi_factor', 'hardware', 'local_console') AND
decision_mode IN ('human_confirmation', 'separation_of_duty') AND
authenticated_at_ms >= 0 AND
expires_at_ms > authenticated_at_ms AND
committed_at_ms >= authenticated_at_ms AND
committed_at_ms < expires_at_ms AND
committed_at_ms - authenticated_at_ms <= 300000 AND
catalog_command_digest ~ '^[0-9a-f]{64}$' AND
result_digest ~ '^[0-9a-f]{64}$' AND
policy_decision_digest ~ '^[0-9a-f]{64}$' AND
command_digest ~ '^[0-9a-f]{64}$' AND
authorization_digest ~ '^[0-9a-f]{64}$'
),
CONSTRAINT ql3_model_price_catalog_authorization_reasons_check CHECK (
jsonb_typeof(reasons_json) = 'array' AND
jsonb_array_length(reasons_json) BETWEEN 1 AND 8 AND
octet_length(reasons_json::text) BETWEEN 5 AND 2048
),
CONSTRAINT ql3_model_price_catalog_authorization_json_check CHECK (
jsonb_typeof(authorization_json) = 'object' AND
octet_length(authorization_json::text) BETWEEN 2 AND 32768 AND
authorization_json @> jsonb_build_object(
'schema', 'qinglong/model-price-catalog-authorization@v1',
'authorizationId', authorization_id,
'requestId', request_id,
'operation', operation,
'provider', provider,
'model', model,
'priceRevision', price_revision,
'catalogCommandDigest', catalog_command_digest,
'resultDigest', result_digest,
'decisionMode', decision_mode,
'commandDigest', command_digest,
'committedAtMs', committed_at_ms,
'authorizationDigest', authorization_digest
) AND
authorization_json->'principal' @> jsonb_build_object(
'subject', jsonb_build_object('type', 'user', 'id', user_id),
'authenticationId', authentication_id,
'assurance', assurance,
'authenticatedAtMs', authenticated_at_ms,
'expiresAtMs', expires_at_ms
) AND
authorization_json->'policy' @> jsonb_build_object(
'revision', policy_revision,
'decisionDigest', policy_decision_digest,
'reasons', reasons_json
)
)
)`;
const postgresModelPriceCatalogAuthorizationMigration =
defineSqlMigration<PostgresQueryable>(
POSTGRES_MODEL_PRICE_CATALOG_AUTHORIZATION_MIGRATION_ID,
[
POSTGRES_MODEL_PRICE_CATALOG_AUTHORIZATION_TABLE_SQL,
`CREATE INDEX ql3_model_price_catalog_authorization_target_idx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_price_catalog_authorizations"
(provider, model, operation, committed_at_ms DESC)`,
`REVOKE ALL ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_price_catalog_authorizations"
FROM PUBLIC`,
`GRANT SELECT, INSERT ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_price_catalog_authorizations"
TO ql3_admin`,
],
(context, statement) => context.query(statement).then(() => undefined),
);
export const postgresCatalogMigrations = Object.freeze([
postgresModelPriceCatalogMigration,
postgresModelPriceCatalogAuthorizationMigration,
]);
@@ -0,0 +1,249 @@
import type { PostgresQueryable } from '@qinglong/runtime-core';
import {
POSTGRES_MODEL_INVOCATION_MIGRATION_ID,
POSTGRES_MODEL_INVOCATION_SCHEMA,
} from '../identities';
import { defineSqlMigration } from '../shared';
const POSTGRES_START_TABLE_SQL = `
CREATE TABLE "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_starts" (
invocation_id varchar(128) PRIMARY KEY,
project_id varchar(128) NOT NULL,
run_id varchar(36) NOT NULL,
step_run_id varchar(128) NOT NULL,
trace_id varchar(128) NOT NULL,
provider varchar(128) NOT NULL,
model varchar(128) NOT NULL,
policy_revision varchar(128) NOT NULL,
request_digest varchar(71) NOT NULL,
input_bytes integer NOT NULL,
max_output_tokens integer NOT NULL,
deadline_at_ms bigint NOT NULL,
admitted_at_ms bigint NOT NULL,
mutation_id varchar(128) NOT NULL,
mutation_digest char(64) NOT NULL,
run_event_id varchar(36) NOT NULL,
start_digest char(64) NOT NULL,
record_json jsonb NOT NULL,
FOREIGN KEY (run_id, step_run_id)
REFERENCES "ql3"."step_runs" (run_id, id) ON DELETE RESTRICT,
FOREIGN KEY (mutation_id)
REFERENCES "ql3"."step_run_mutations" (mutation_id) ON DELETE RESTRICT,
FOREIGN KEY (run_event_id)
REFERENCES "ql3"."run_events" (id) ON DELETE RESTRICT,
CONSTRAINT ql3_model_invocation_starts_identity_check CHECK (
invocation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
project_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
run_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
step_run_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
trace_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
provider ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
model ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
policy_revision ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
mutation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
),
CONSTRAINT ql3_model_invocation_starts_value_check CHECK (
request_digest ~ '^sha256:[0-9a-f]{64}$' AND
mutation_digest ~ '^[0-9a-f]{64}$' AND
start_digest ~ '^[0-9a-f]{64}$' AND
input_bytes BETWEEN 1 AND 262144 AND
max_output_tokens BETWEEN 1 AND 32768 AND
admitted_at_ms >= 0 AND deadline_at_ms > admitted_at_ms
),
CONSTRAINT ql3_model_invocation_starts_json_check CHECK (
jsonb_typeof(record_json) = 'object' AND
octet_length(record_json::text) BETWEEN 2 AND 24576 AND
record_json @> jsonb_build_object(
'schema', 'qinglong/model-invocation-start@v1',
'invocationId', invocation_id,
'projectId', project_id,
'runId', run_id,
'stepRunId', step_run_id,
'traceId', trace_id,
'provider', provider,
'model', model,
'policyRevision', policy_revision,
'requestDigest', request_digest,
'inputBytes', input_bytes,
'maxOutputTokens', max_output_tokens,
'deadlineAtMs', deadline_at_ms,
'admittedAtMs', admitted_at_ms,
'stepRunMutationId', mutation_id,
'stepRunMutationDigest', mutation_digest,
'runEventId', run_event_id,
'startDigest', start_digest
)
)
)`;
const POSTGRES_COMPLETION_TABLE_SQL = `
CREATE TABLE "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_completions" (
invocation_id varchar(128) PRIMARY KEY,
project_id varchar(128) NOT NULL,
run_id varchar(36) NOT NULL,
step_run_id varchar(128) NOT NULL,
trace_id varchar(128) NOT NULL,
start_digest char(64) NOT NULL,
outcome varchar(32) NOT NULL,
output_bytes integer NOT NULL,
error_code varchar(64),
completed_at_ms bigint NOT NULL,
mutation_id varchar(128) NOT NULL,
mutation_digest char(64) NOT NULL,
run_event_id varchar(36) NOT NULL,
completion_digest char(64) NOT NULL,
record_json jsonb NOT NULL,
FOREIGN KEY (invocation_id)
REFERENCES "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_starts" (invocation_id)
ON DELETE RESTRICT,
FOREIGN KEY (run_id, step_run_id)
REFERENCES "ql3"."step_runs" (run_id, id) ON DELETE RESTRICT,
FOREIGN KEY (mutation_id)
REFERENCES "ql3"."step_run_mutations" (mutation_id) ON DELETE RESTRICT,
FOREIGN KEY (run_event_id)
REFERENCES "ql3"."run_events" (id) ON DELETE RESTRICT,
CONSTRAINT ql3_model_invocation_completions_value_check CHECK (
outcome IN ('succeeded', 'failed', 'timed_out', 'outcome_unknown') AND
output_bytes BETWEEN 0 AND 1048576 AND completed_at_ms >= 0 AND
((outcome = 'succeeded' AND error_code IS NULL) OR
(outcome <> 'succeeded' AND
error_code ~ '^[A-Z][A-Z0-9_]{0,63}$')) AND
start_digest ~ '^[0-9a-f]{64}$' AND
mutation_digest ~ '^[0-9a-f]{64}$' AND
completion_digest ~ '^[0-9a-f]{64}$'
),
CONSTRAINT ql3_model_invocation_completions_json_check CHECK (
jsonb_typeof(record_json) = 'object' AND
octet_length(record_json::text) BETWEEN 2 AND 24576 AND
record_json @> jsonb_build_object(
'schema', 'qinglong/model-invocation-completion@v1',
'invocationId', invocation_id,
'projectId', project_id,
'runId', run_id,
'stepRunId', step_run_id,
'traceId', trace_id,
'startDigest', start_digest,
'outcome', outcome,
'outputBytes', output_bytes,
'errorCode', error_code,
'completedAtMs', completed_at_ms,
'stepRunMutationId', mutation_id,
'stepRunMutationDigest', mutation_digest,
'runEventId', run_event_id,
'completionDigest', completion_digest
)
)
)`;
const POSTGRES_RESOLUTION_TABLE_SQL = `
CREATE TABLE "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_resolutions" (
resolution_id varchar(128) PRIMARY KEY,
invocation_id varchar(128) NOT NULL UNIQUE,
project_id varchar(128) NOT NULL,
run_id varchar(36) NOT NULL,
step_run_id varchar(128) NOT NULL,
trace_id varchar(128) NOT NULL,
completion_digest char(64) NOT NULL,
decision varchar(16) NOT NULL,
resolved_by_user_id varchar(128) NOT NULL,
resolved_at_ms bigint NOT NULL,
mutation_id varchar(128) NOT NULL UNIQUE,
mutation_digest char(64) NOT NULL,
run_event_id varchar(36) NOT NULL UNIQUE,
resolution_digest char(64) NOT NULL UNIQUE,
record_json jsonb NOT NULL,
FOREIGN KEY (invocation_id)
REFERENCES "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_completions" (invocation_id)
ON DELETE RESTRICT,
FOREIGN KEY (run_id, step_run_id)
REFERENCES "ql3"."step_runs" (run_id, id) ON DELETE RESTRICT,
FOREIGN KEY (mutation_id)
REFERENCES "ql3"."step_run_mutations" (mutation_id) ON DELETE RESTRICT,
FOREIGN KEY (run_event_id)
REFERENCES "ql3"."run_events" (id) ON DELETE RESTRICT,
CONSTRAINT ql3_model_invocation_resolutions_value_check CHECK (
decision IN ('retry', 'fail', 'cancel') AND
resolved_at_ms >= 0 AND
resolution_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
invocation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
project_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
run_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
step_run_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
trace_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
resolved_by_user_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
mutation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
completion_digest ~ '^[0-9a-f]{64}$' AND
mutation_digest ~ '^[0-9a-f]{64}$' AND
resolution_digest ~ '^[0-9a-f]{64}$'
),
CONSTRAINT ql3_model_invocation_resolutions_json_check CHECK (
jsonb_typeof(record_json) = 'object' AND
octet_length(record_json::text) BETWEEN 2 AND 24576 AND
record_json @> jsonb_build_object(
'schema', 'qinglong/model-invocation-resolution@v1',
'resolutionId', resolution_id,
'invocationId', invocation_id,
'projectId', project_id,
'runId', run_id,
'stepRunId', step_run_id,
'traceId', trace_id,
'completionDigest', completion_digest,
'decision', decision,
'resolvedByUserId', resolved_by_user_id,
'resolvedAtMs', resolved_at_ms,
'stepRunMutationId', mutation_id,
'stepRunMutationDigest', mutation_digest,
'runEventId', run_event_id,
'resolutionDigest', resolution_digest
)
)
)`;
const postgresMigration = defineSqlMigration<PostgresQueryable>(
POSTGRES_MODEL_INVOCATION_MIGRATION_ID,
[
POSTGRES_START_TABLE_SQL,
`CREATE INDEX ql3_model_invocation_starts_step_history_idx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_starts"
(run_id, step_run_id, admitted_at_ms, invocation_id)`,
`CREATE UNIQUE INDEX ql3_model_invocation_starts_mutation_uidx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_starts" (mutation_id)`,
`CREATE UNIQUE INDEX ql3_model_invocation_starts_event_uidx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_starts" (run_event_id)`,
`CREATE UNIQUE INDEX ql3_model_invocation_starts_digest_uidx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_starts" (start_digest)`,
POSTGRES_COMPLETION_TABLE_SQL,
`CREATE INDEX ql3_model_invocation_completions_step_history_idx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_completions"
(run_id, step_run_id, completed_at_ms, invocation_id)`,
`CREATE UNIQUE INDEX ql3_model_invocation_completions_mutation_uidx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_completions" (mutation_id)`,
`CREATE UNIQUE INDEX ql3_model_invocation_completions_event_uidx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_completions" (run_event_id)`,
`CREATE UNIQUE INDEX ql3_model_invocation_completions_digest_uidx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_completions" (completion_digest)`,
POSTGRES_RESOLUTION_TABLE_SQL,
`CREATE INDEX ql3_model_invocation_resolutions_step_history_idx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_resolutions"
(run_id, step_run_id, resolved_at_ms, resolution_id)`,
`GRANT USAGE ON SCHEMA "${POSTGRES_MODEL_INVOCATION_SCHEMA}"
TO ql3_runtime`,
`REVOKE ALL ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_starts",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_completions",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_resolutions"
FROM PUBLIC`,
`GRANT SELECT, INSERT ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_starts",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_completions",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_resolutions"
TO ql3_runtime`,
],
(context, statement) => context.query(statement).then(() => undefined),
);
export const postgresCoreMigrations = Object.freeze([
postgresMigration,
]);
@@ -0,0 +1,513 @@
import type { PostgresQueryable } from '@qinglong/runtime-core';
import {
POSTGRES_MODEL_PROVIDER_CREDENTIAL_CATALOG_MIGRATION_ID,
POSTGRES_MODEL_PROVIDER_CREDENTIAL_MANAGEMENT_MIGRATION_ID,
POSTGRES_MODEL_PROVIDER_CREDENTIAL_MANAGEMENT_IDENTITY_MIGRATION_ID,
POSTGRES_MODEL_PROVIDER_CREDENTIAL_TEST_CONNECTION_MIGRATION_ID,
POSTGRES_MODEL_INVOCATION_SCHEMA,
} from '../identities';
import { defineSqlMigration } from '../shared';
const POSTGRES_MODEL_PROVIDER_CREDENTIAL_BINDING_TABLE_SQL = `
CREATE TABLE "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_bindings" (
project_id varchar(128) NOT NULL,
provider varchar(128) NOT NULL,
revision varchar(128) NOT NULL,
secret_ref varchar(512) NOT NULL,
scheme varchar(16) NOT NULL,
binding_digest varchar(71) NOT NULL UNIQUE,
binding_json jsonb NOT NULL,
PRIMARY KEY (project_id, provider, revision),
CONSTRAINT ql3_ai_model_provider_credential_binding_project_fk
FOREIGN KEY (project_id) REFERENCES "ql3"."projects" (id)
ON DELETE RESTRICT,
CONSTRAINT ql3_ai_model_provider_credential_binding_identity_check CHECK (
project_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
provider ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
revision ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
length(secret_ref) BETWEEN 1 AND 512 AND
secret_ref LIKE 'qlsecret:v1:%' AND
scheme = 'bearer' AND
binding_digest ~ '^sha256:[0-9a-f]{64}$'
),
CONSTRAINT ql3_ai_model_provider_credential_binding_json_check CHECK (
jsonb_typeof(binding_json) = 'object' AND
octet_length(binding_json::text) BETWEEN 2 AND 4096 AND
binding_json @> jsonb_build_object(
'schema', 'qinglong/model-provider-credential-binding@v1',
'projectId', project_id, 'provider', provider,
'revision', revision, 'secretRef', secret_ref, 'scheme', scheme
)
)
)`;
const POSTGRES_MODEL_PROVIDER_CREDENTIAL_TRANSITION_TABLE_SQL = `
CREATE TABLE "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_transitions" (
project_id varchar(128) NOT NULL,
provider varchar(128) NOT NULL,
generation integer NOT NULL,
action varchar(16) NOT NULL,
active_binding_revision varchar(128),
active_binding_digest char(64),
previous_transition_digest char(64),
mutation_id varchar(128) NOT NULL UNIQUE,
changed_by_type varchar(32) NOT NULL,
changed_by_id varchar(128) NOT NULL,
changed_at_ms bigint NOT NULL,
command_digest char(64) NOT NULL,
transition_digest char(64) NOT NULL UNIQUE,
transition_json jsonb NOT NULL,
PRIMARY KEY (project_id, provider, generation),
CONSTRAINT ql3_ai_model_provider_credential_transition_project_fk
FOREIGN KEY (project_id) REFERENCES "ql3"."projects" (id)
ON DELETE RESTRICT,
CONSTRAINT ql3_ai_model_provider_credential_transition_binding_fk
FOREIGN KEY (project_id, provider, active_binding_revision)
REFERENCES "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_bindings"
(project_id, provider, revision) ON DELETE RESTRICT,
CONSTRAINT ql3_ai_model_provider_credential_transition_identity_check CHECK (
project_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
provider ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
generation BETWEEN 1 AND 2147483647 AND
action IN ('bind', 'revoke') AND
mutation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
changed_by_type IN ('user', 'api_app', 'mcp_client', 'agent', 'system', 'worker') AND
changed_by_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
changed_at_ms >= 0
),
CONSTRAINT ql3_ai_model_provider_credential_transition_state_check CHECK (
(action = 'bind' AND active_binding_revision IS NOT NULL AND
active_binding_digest ~ '^[0-9a-f]{64}$') OR
(action = 'revoke' AND active_binding_revision IS NULL AND
active_binding_digest IS NULL)
),
CONSTRAINT ql3_ai_model_provider_credential_transition_digest_check CHECK (
(previous_transition_digest IS NULL OR
previous_transition_digest ~ '^[0-9a-f]{64}$') AND
command_digest ~ '^[0-9a-f]{64}$' AND
transition_digest ~ '^[0-9a-f]{64}$'
),
CONSTRAINT ql3_ai_model_provider_credential_transition_json_check CHECK (
jsonb_typeof(transition_json) = 'object' AND
octet_length(transition_json::text) BETWEEN 2 AND 8192 AND
transition_json @> jsonb_build_object(
'schema', 'qinglong/model-provider-credential-transition@v1',
'mutationId', mutation_id, 'projectId', project_id,
'provider', provider, 'generation', generation, 'action', action,
'activeBindingRevision', active_binding_revision,
'activeBindingDigest', active_binding_digest,
'previousTransitionDigest', previous_transition_digest,
'changedAtMs', changed_at_ms, 'commandDigest', command_digest,
'transitionDigest', transition_digest
) AND
transition_json -> 'changedBy' @> jsonb_build_object(
'type', changed_by_type, 'id', changed_by_id
)
)
)`;
const POSTGRES_MODEL_PROVIDER_CREDENTIAL_AUDIT_TABLE_SQL = `
CREATE TABLE "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_audits" (
project_id varchar(128) NOT NULL,
provider varchar(128) NOT NULL,
request_id varchar(128) NOT NULL,
operation varchar(16) NOT NULL,
binding_revision varchar(128) NOT NULL,
binding_digest varchar(71) NOT NULL,
occurred_at_ms bigint NOT NULL,
audit_digest char(64) PRIMARY KEY,
audit_json jsonb NOT NULL,
CONSTRAINT ql3_ai_model_provider_credential_audit_identity_uidx
UNIQUE (project_id, provider, request_id, operation),
CONSTRAINT ql3_ai_model_provider_credential_audit_project_fk
FOREIGN KEY (project_id) REFERENCES "ql3"."projects" (id)
ON DELETE RESTRICT,
CONSTRAINT ql3_ai_model_provider_credential_audit_identity_check CHECK (
project_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
provider ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
request_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
operation IN ('list_models', 'generate', 'stream') AND
binding_revision ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
binding_digest ~ '^sha256:[0-9a-f]{64}$' AND
occurred_at_ms >= 0 AND audit_digest ~ '^[0-9a-f]{64}$'
),
CONSTRAINT ql3_ai_model_provider_credential_audit_json_check CHECK (
jsonb_typeof(audit_json) = 'object' AND
octet_length(audit_json::text) BETWEEN 2 AND 4096 AND
audit_json @> jsonb_build_object(
'schema', 'qinglong/model-provider-credential-audit@v1',
'projectId', project_id, 'provider', provider,
'requestId', request_id, 'operation', operation,
'bindingRevision', binding_revision,
'bindingDigest', binding_digest, 'occurredAtMs', occurred_at_ms
)
)
)`;
const postgresModelProviderCredentialCatalogMigration =
defineSqlMigration<PostgresQueryable>(
POSTGRES_MODEL_PROVIDER_CREDENTIAL_CATALOG_MIGRATION_ID,
[
POSTGRES_MODEL_PROVIDER_CREDENTIAL_BINDING_TABLE_SQL,
POSTGRES_MODEL_PROVIDER_CREDENTIAL_TRANSITION_TABLE_SQL,
POSTGRES_MODEL_PROVIDER_CREDENTIAL_AUDIT_TABLE_SQL,
`CREATE INDEX ql3_ai_model_provider_credential_transition_current_idx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_transitions"
(project_id, provider, generation DESC)`,
`CREATE INDEX ql3_ai_model_provider_credential_audit_project_time_idx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_audits"
(project_id, occurred_at_ms DESC, request_id)`,
`REVOKE ALL ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_bindings",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_transitions",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_audits"
FROM PUBLIC`,
`GRANT USAGE ON SCHEMA "${POSTGRES_MODEL_INVOCATION_SCHEMA}"
TO ql3_runtime, ql3_ai_maintenance`,
`GRANT SELECT ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_bindings",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_transitions"
TO ql3_runtime`,
`GRANT SELECT, INSERT ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_audits"
TO ql3_runtime`,
`GRANT SELECT, INSERT ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_bindings",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_transitions"
TO ql3_ai_maintenance`,
`GRANT SELECT ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_audits"
TO ql3_ai_maintenance`,
],
(context, statement) => context.query(statement).then(() => undefined),
);
const postgresModelProviderCredentialManagementMigration =
defineSqlMigration<PostgresQueryable>(
POSTGRES_MODEL_PROVIDER_CREDENTIAL_MANAGEMENT_MIGRATION_ID,
[
`DO $ql3_ai$
BEGIN
EXECUTE format(
'GRANT CONNECT ON DATABASE %I TO ql3_ai_credential_manager',
current_database()
);
END
$ql3_ai$`,
`REVOKE CREATE ON SCHEMA "ql3", "${POSTGRES_MODEL_INVOCATION_SCHEMA}"
FROM ql3_ai_credential_manager`,
`REVOKE ALL ON ALL TABLES IN SCHEMA "${POSTGRES_MODEL_INVOCATION_SCHEMA}"
FROM ql3_ai_credential_manager`,
`REVOKE ALL ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_bindings",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_transitions",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_audits"
FROM ql3_ai_maintenance`,
`REVOKE ALL ON TABLE
"ql3"."projects",
"ql3"."project_role_bindings",
"ql3"."security_audit_events"
FROM ql3_ai_credential_manager`,
`GRANT USAGE ON SCHEMA "ql3", "${POSTGRES_MODEL_INVOCATION_SCHEMA}"
TO ql3_ai_credential_manager`,
`GRANT SELECT ON TABLE
"ql3"."projects",
"ql3"."project_role_bindings",
"ql3"."security_audit_events"
TO ql3_ai_credential_manager`,
`GRANT INSERT ON TABLE "ql3"."security_audit_events"
TO ql3_ai_credential_manager`,
`GRANT SELECT ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."ai_schema_migrations",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_bindings",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_transitions",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_audits"
TO ql3_ai_credential_manager`,
`GRANT INSERT ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_bindings",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_transitions"
TO ql3_ai_credential_manager`,
],
(context, statement) => context.query(statement).then(() => undefined),
);
const postgresModelProviderCredentialManagementIdentityMigration =
defineSqlMigration<PostgresQueryable>(
POSTGRES_MODEL_PROVIDER_CREDENTIAL_MANAGEMENT_IDENTITY_MIGRATION_ID,
[
`CREATE TABLE "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_management_identity_keyset_ledger" (
authority varchar(64) PRIMARY KEY,
generation bigint NOT NULL,
digest varchar(43) NOT NULL,
issuer varchar(512) NOT NULL,
audience varchar(256) NOT NULL,
active_key_ids jsonb NOT NULL,
revoked_key_ids jsonb NOT NULL,
updated_at_ms bigint NOT NULL,
CONSTRAINT ql3_ai_provider_credential_identity_authority_check
CHECK (authority = 'model-provider-credential-management'),
CONSTRAINT ql3_ai_provider_credential_identity_generation_check
CHECK (generation >= 1 AND updated_at_ms >= 0),
CONSTRAINT ql3_ai_provider_credential_identity_digest_check
CHECK (digest ~ '^[A-Za-z0-9_-]{43}$'),
CONSTRAINT ql3_ai_provider_credential_identity_trust_domain_check
CHECK (
char_length(issuer) BETWEEN 1 AND 512 AND
issuer !~ '[[:cntrl:]]' AND
char_length(audience) BETWEEN 1 AND 256 AND
audience !~ '[[:cntrl:]]'
),
CONSTRAINT ql3_ai_provider_credential_identity_keys_check
CHECK (
jsonb_typeof(active_key_ids) = 'array' AND
jsonb_array_length(active_key_ids) BETWEEN 1 AND 8 AND
octet_length(active_key_ids::text) BETWEEN 3 AND 8192 AND
jsonb_typeof(revoked_key_ids) = 'array' AND
jsonb_array_length(revoked_key_ids) BETWEEN 0 AND 64 AND
octet_length(revoked_key_ids::text) BETWEEN 2 AND 16384
)
)`,
`REVOKE ALL ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_management_identity_keyset_ledger"
FROM PUBLIC, ql3_ai_maintenance`,
`GRANT SELECT, INSERT, UPDATE ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_management_identity_keyset_ledger"
TO ql3_ai_credential_manager`,
],
(context, statement) => context.query(statement).then(() => undefined),
);
const postgresModelProviderCredentialTestConnectionMigration =
defineSqlMigration<PostgresQueryable>(
POSTGRES_MODEL_PROVIDER_CREDENTIAL_TEST_CONNECTION_MIGRATION_ID,
[
`CREATE TABLE "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_test_plans" (
test_id uuid PRIMARY KEY,
request_id varchar(128) NOT NULL,
project_id varchar(128) NOT NULL,
provider varchar(128) NOT NULL,
adapter varchar(32) NOT NULL,
base_url varchar(1024) NOT NULL,
endpoint_revision varchar(128) NOT NULL,
endpoint_config_digest char(64) NOT NULL,
deadline_ms integer NOT NULL,
max_response_bytes integer NOT NULL,
max_models integer NOT NULL,
max_cost_microusd bigint NOT NULL,
retry_limit integer NOT NULL,
requested_by_type varchar(32) NOT NULL,
requested_by_id varchar(128) NOT NULL,
project_version integer NOT NULL,
binding_version integer NOT NULL,
planned_at_ms bigint NOT NULL,
expires_at_ms bigint NOT NULL,
plan_digest char(64) NOT NULL UNIQUE,
plan_json jsonb NOT NULL,
CONSTRAINT ql3_ai_provider_credential_test_plan_project_fk
FOREIGN KEY (project_id) REFERENCES "ql3"."projects" (id)
ON DELETE RESTRICT,
CONSTRAINT ql3_ai_provider_credential_test_plan_identity_uidx
UNIQUE (project_id, request_id),
CONSTRAINT ql3_ai_provider_credential_test_plan_digest_uidx
UNIQUE (test_id, plan_digest),
CONSTRAINT ql3_ai_provider_credential_test_plan_identity_check CHECK (
project_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
provider ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
request_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
endpoint_revision ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
requested_by_type = 'user' AND
char_length(requested_by_id) BETWEEN 1 AND 128 AND
requested_by_id !~ '[[:cntrl:]]'
),
CONSTRAINT ql3_ai_provider_credential_test_plan_budget_check CHECK (
adapter = 'openai-compatible' AND
base_url ~ '^https://[^[:cntrl:]]+/$' AND
endpoint_config_digest ~ '^[0-9a-f]{64}$' AND
deadline_ms BETWEEN 1000 AND 15000 AND
max_response_bytes BETWEEN 1024 AND 262144 AND
max_models BETWEEN 1 AND 256 AND
max_cost_microusd = 0 AND retry_limit = 0
),
CONSTRAINT ql3_ai_provider_credential_test_plan_fence_check CHECK (
project_version BETWEEN 1 AND 2147483647 AND
binding_version BETWEEN 1 AND 2147483647 AND
planned_at_ms >= 0 AND expires_at_ms > planned_at_ms AND
expires_at_ms - planned_at_ms <= 300000 AND
plan_digest ~ '^[0-9a-f]{64}$'
),
CONSTRAINT ql3_ai_provider_credential_test_plan_json_check CHECK (
jsonb_typeof(plan_json) = 'object' AND
octet_length(plan_json::text) BETWEEN 2 AND 16384 AND
plan_json @> jsonb_build_object(
'schema', 'qinglong/model-provider-credential-test-plan@v1',
'testId', test_id::text, 'requestId', request_id,
'projectId', project_id, 'provider', provider,
'plannedAtMs', planned_at_ms, 'expiresAtMs', expires_at_ms,
'planDigest', plan_digest
) AND
plan_json -> 'endpoint' @> jsonb_build_object(
'provider', provider, 'adapter', adapter, 'baseUrl', base_url,
'revision', endpoint_revision,
'configDigest', endpoint_config_digest,
'deadlineMs', deadline_ms,
'maxResponseBytes', max_response_bytes,
'maxModels', max_models, 'maxCostMicrousd', max_cost_microusd,
'retryLimit', retry_limit
) AND
plan_json -> 'requestedBy' @> jsonb_build_object(
'type', requested_by_type, 'id', requested_by_id
) AND
plan_json -> 'fence' @> jsonb_build_object(
'projectVersion', project_version,
'bindingVersion', binding_version
)
)
)`,
`CREATE INDEX ql3_ai_provider_credential_test_plan_expiry_idx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_test_plans"
(expires_at_ms, test_id)`,
`CREATE TABLE "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_test_quota_buckets" (
project_id varchar(128) NOT NULL,
subject_id varchar(128) NOT NULL,
window_started_at_ms bigint NOT NULL,
consumed_count integer NOT NULL,
receipt_ids jsonb NOT NULL,
updated_at_ms bigint NOT NULL,
PRIMARY KEY (project_id, subject_id),
CONSTRAINT ql3_ai_provider_credential_test_quota_project_fk
FOREIGN KEY (project_id) REFERENCES "ql3"."projects" (id)
ON DELETE RESTRICT,
CONSTRAINT ql3_ai_provider_credential_test_quota_identity_check CHECK (
project_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
char_length(subject_id) BETWEEN 1 AND 128 AND
subject_id !~ '[[:cntrl:]]'
),
CONSTRAINT ql3_ai_provider_credential_test_quota_value_check CHECK (
window_started_at_ms >= 0 AND
consumed_count BETWEEN 1 AND 32 AND
updated_at_ms >= window_started_at_ms AND
jsonb_typeof(receipt_ids) = 'array' AND
jsonb_array_length(receipt_ids) BETWEEN 1 AND 32 AND
octet_length(receipt_ids::text) BETWEEN 3 AND 8192
)
)`,
`CREATE TABLE "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_test_executions" (
execution_id uuid PRIMARY KEY,
test_id uuid NOT NULL UNIQUE,
plan_digest char(64) NOT NULL,
started_at_ms bigint NOT NULL,
execution_digest char(64) NOT NULL UNIQUE,
execution_json jsonb NOT NULL,
CONSTRAINT ql3_ai_provider_credential_test_execution_plan_fk
FOREIGN KEY (test_id, plan_digest)
REFERENCES "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_test_plans"
(test_id, plan_digest) ON DELETE RESTRICT,
CONSTRAINT ql3_ai_provider_credential_test_execution_value_check CHECK (
plan_digest ~ '^[0-9a-f]{64}$' AND started_at_ms >= 0 AND
execution_digest ~ '^[0-9a-f]{64}$'
),
CONSTRAINT ql3_ai_provider_credential_test_execution_json_check CHECK (
jsonb_typeof(execution_json) = 'object' AND
octet_length(execution_json::text) BETWEEN 2 AND 4096 AND
execution_json @> jsonb_build_object(
'schema', 'qinglong/model-provider-credential-test-execution@v1',
'executionId', execution_id::text, 'testId', test_id::text,
'planDigest', plan_digest, 'startedAtMs', started_at_ms,
'executionDigest', execution_digest
)
)
)`,
`ALTER TABLE "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_test_executions"
ADD CONSTRAINT ql3_ai_provider_credential_test_execution_identity_uidx
UNIQUE (execution_id, test_id, plan_digest)`,
`CREATE TABLE "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_test_results" (
execution_id uuid PRIMARY KEY,
test_id uuid NOT NULL UNIQUE,
plan_digest char(64) NOT NULL,
outcome varchar(16) NOT NULL,
model_count integer,
duration_ms integer NOT NULL,
completed_at_ms bigint NOT NULL,
result_digest char(64) NOT NULL UNIQUE,
result_json jsonb NOT NULL,
CONSTRAINT ql3_ai_provider_credential_test_result_execution_fk
FOREIGN KEY (execution_id, test_id, plan_digest)
REFERENCES "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_test_executions"
(execution_id, test_id, plan_digest) ON DELETE RESTRICT,
CONSTRAINT ql3_ai_provider_credential_test_result_value_check CHECK (
plan_digest ~ '^[0-9a-f]{64}$' AND
((outcome = 'reachable' AND model_count BETWEEN 0 AND 256) OR
(outcome = 'unreachable' AND model_count IS NULL)) AND
duration_ms BETWEEN 0 AND 15000 AND completed_at_ms >= 0 AND
result_digest ~ '^[0-9a-f]{64}$'
),
CONSTRAINT ql3_ai_provider_credential_test_result_json_check CHECK (
jsonb_typeof(result_json) = 'object' AND
octet_length(result_json::text) BETWEEN 2 AND 4096 AND
result_json @> jsonb_build_object(
'schema', 'qinglong/model-provider-credential-test-result@v1',
'executionId', execution_id::text, 'testId', test_id::text,
'planDigest', plan_digest, 'outcome', outcome,
'modelCount', model_count, 'durationMs', duration_ms,
'completedAtMs', completed_at_ms, 'resultDigest', result_digest
)
)
)`,
`DO $ql3_ai$
BEGIN
EXECUTE format(
'GRANT CONNECT ON DATABASE %I TO ql3_ai_credential_tester',
current_database()
);
END
$ql3_ai$`,
`REVOKE CREATE ON SCHEMA "ql3", "${POSTGRES_MODEL_INVOCATION_SCHEMA}"
FROM ql3_ai_credential_tester`,
`REVOKE ALL ON ALL TABLES IN SCHEMA "ql3", "${POSTGRES_MODEL_INVOCATION_SCHEMA}"
FROM ql3_ai_credential_tester`,
`REVOKE ALL ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_test_plans",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_test_quota_buckets",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_test_executions",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_test_results"
FROM PUBLIC, ql3_runtime, ql3_ai_maintenance, ql3_admin,
ql3_package_manager, ql3_package_executor, ql3_worker_ingress,
ql3_ai_credential_manager, ql3_ai_credential_tester`,
`GRANT USAGE ON SCHEMA "${POSTGRES_MODEL_INVOCATION_SCHEMA}"
TO ql3_ai_credential_tester`,
`GRANT SELECT, INSERT ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_test_plans"
TO ql3_ai_credential_manager`,
`GRANT SELECT, INSERT, UPDATE ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_test_quota_buckets"
TO ql3_ai_credential_manager`,
`GRANT SELECT ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_test_executions",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_test_results"
TO ql3_ai_credential_manager`,
`GRANT SELECT ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."ai_schema_migrations",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_bindings",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_transitions",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_audits",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_test_plans",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_test_executions",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_test_results"
TO ql3_ai_credential_tester`,
`GRANT INSERT ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_audits",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_test_executions",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_test_results"
TO ql3_ai_credential_tester`,
],
(context, statement) => context.query(statement).then(() => undefined),
);
export const postgresCredentialMigrations = Object.freeze([
postgresModelProviderCredentialCatalogMigration,
postgresModelProviderCredentialManagementMigration,
postgresModelProviderCredentialManagementIdentityMigration,
postgresModelProviderCredentialTestConnectionMigration,
]);
@@ -0,0 +1,855 @@
import type { PostgresQueryable } from '@qinglong/runtime-core';
import {
POSTGRES_PLUGIN_PACKAGE_PROMPT_ADMISSION_MIGRATION_ID,
POSTGRES_PLUGIN_PACKAGE_PROMPT_FINALIZATION_MIGRATION_ID,
POSTGRES_PLUGIN_PACKAGE_PROMPT_OUTPUT_ARTIFACT_MIGRATION_ID,
POSTGRES_PLUGIN_PACKAGE_PROMPT_OUTPUT_TOMBSTONE_MIGRATION_ID,
POSTGRES_PLUGIN_PACKAGE_PROMPT_OUTPUT_KEY_RETIREMENT_MIGRATION_ID,
POSTGRES_PLUGIN_PACKAGE_PROMPT_OUTPUT_KEY_ROTATION_MIGRATION_ID,
POSTGRES_PLUGIN_PACKAGE_PROMPT_PRODUCT_AUTHORIZATION_MIGRATION_ID,
POSTGRES_MODEL_INVOCATION_SCHEMA,
POSTGRES_MODEL_INVOCATION_MIGRATION_HISTORY_TABLE,
} from '../identities';
import { defineSqlMigration } from '../shared';
const POSTGRES_PLUGIN_PACKAGE_PROMPT_ADMISSION_TABLE_SQL = `
CREATE TABLE "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_admissions" (
request_id varchar(128) PRIMARY KEY,
invocation_id varchar(128) NOT NULL UNIQUE,
plan_digest char(64) NOT NULL UNIQUE,
run_id varchar(36) NOT NULL UNIQUE,
step_run_id varchar(128) NOT NULL UNIQUE,
project_id varchar(128) NOT NULL,
package_name varchar(63) NOT NULL,
installation_id varchar(128) NOT NULL,
lock_digest char(64) NOT NULL,
generation integer NOT NULL,
generation_digest char(64) NOT NULL,
materialized_revision_digest char(64) NOT NULL,
publication_digest char(64) NOT NULL,
prompt_id varchar(128) NOT NULL,
prompt_definition_digest char(64) NOT NULL,
parameter_digest char(64) NOT NULL,
model_request_digest varchar(71) NOT NULL,
admitted_at_ms bigint NOT NULL,
receipt_digest char(64) NOT NULL UNIQUE,
plan_json jsonb NOT NULL,
receipt_json jsonb NOT NULL,
CONSTRAINT ql3_ai_prompt_admission_run_fk
FOREIGN KEY (run_id) REFERENCES "ql3"."runs" (id) ON DELETE RESTRICT,
CONSTRAINT ql3_ai_prompt_admission_step_fk
FOREIGN KEY (run_id, step_run_id)
REFERENCES "ql3"."step_runs" (run_id, id) ON DELETE RESTRICT,
CONSTRAINT ql3_ai_prompt_admission_publication_fk
FOREIGN KEY (publication_digest)
REFERENCES "ql3"."plugin_package_automation_publications" (
publication_digest
) ON DELETE RESTRICT,
CONSTRAINT ql3_ai_prompt_admission_identity_check CHECK (
request_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
invocation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
run_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,35}$' AND
step_run_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
project_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
package_name ~ '^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$' AND
installation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
prompt_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
generation BETWEEN 1 AND 2147483647 AND admitted_at_ms >= 0
),
CONSTRAINT ql3_ai_prompt_admission_digest_check CHECK (
plan_digest ~ '^[0-9a-f]{64}$' AND
lock_digest ~ '^[0-9a-f]{64}$' AND
generation_digest ~ '^[0-9a-f]{64}$' AND
materialized_revision_digest ~ '^[0-9a-f]{64}$' AND
publication_digest ~ '^[0-9a-f]{64}$' AND
prompt_definition_digest ~ '^[0-9a-f]{64}$' AND
parameter_digest ~ '^[0-9a-f]{64}$' AND
model_request_digest ~ '^sha256:[0-9a-f]{64}$' AND
receipt_digest ~ '^[0-9a-f]{64}$'
),
CONSTRAINT ql3_ai_prompt_admission_json_check CHECK (
jsonb_typeof(plan_json) = 'object' AND
octet_length(plan_json::text) BETWEEN 2 AND 32768 AND
plan_json @> jsonb_build_object(
'schema', 'qinglong/plugin-package-prompt-execution-plan@v1',
'requestId', request_id, 'invocationId', invocation_id,
'planDigest', plan_digest, 'runId', run_id, 'stepRunId', step_run_id,
'parameterDigest', parameter_digest,
'modelRequestDigest', model_request_digest,
'plannedAtMs', admitted_at_ms
) AND
plan_json -> 'target' @> jsonb_build_object(
'projectId', project_id, 'packageName', package_name,
'installationId', installation_id, 'lockDigest', lock_digest,
'generation', generation, 'generationDigest', generation_digest,
'materializedRevisionDigest', materialized_revision_digest,
'publicationDigest', publication_digest, 'promptId', prompt_id,
'promptDefinitionDigest', prompt_definition_digest
) AND
jsonb_typeof(receipt_json) = 'object' AND
octet_length(receipt_json::text) BETWEEN 2 AND 16384 AND
receipt_json @> jsonb_build_object(
'schema', 'qinglong/plugin-package-prompt-admission-receipt@v1',
'requestId', request_id, 'invocationId', invocation_id,
'planDigest', plan_digest, 'runId', run_id, 'stepRunId', step_run_id,
'publicationDigest', publication_digest, 'promptId', prompt_id,
'admittedAtMs', admitted_at_ms, 'receiptDigest', receipt_digest
)
)
)`;
const postgresPluginPackagePromptAdmissionMigration =
defineSqlMigration<PostgresQueryable>(
POSTGRES_PLUGIN_PACKAGE_PROMPT_ADMISSION_MIGRATION_ID,
[
POSTGRES_PLUGIN_PACKAGE_PROMPT_ADMISSION_TABLE_SQL,
`CREATE UNIQUE INDEX ql3_ai_prompt_admission_run_step_uidx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_admissions"
(run_id, step_run_id)`,
`CREATE INDEX ql3_ai_prompt_admission_target_idx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_admissions"
(project_id, package_name, admitted_at_ms, request_id)`,
`REVOKE ALL ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_admissions"
FROM PUBLIC`,
`GRANT SELECT, INSERT ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_admissions"
TO ql3_runtime`,
`CREATE FUNCTION
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."plugin_package_prompt_admission_snapshot"(
p_project_id varchar,
p_package_name varchar,
p_publication_digest char(64),
p_requested_by_subject_type varchar,
p_requested_by_subject_id varchar,
p_project_version integer,
p_binding_version integer
)
RETURNS TABLE(publication_json jsonb, revision_json jsonb)
LANGUAGE plpgsql
VOLATILE
SECURITY DEFINER
SET search_path = pg_catalog, ql3, ql3_ai
AS $ql3_ai$
BEGIN
IF NOT pg_has_role(session_user, 'ql3_runtime', 'member') THEN
RAISE EXCEPTION 'Runtime authority is required'
USING ERRCODE = 'insufficient_privilege';
END IF;
PERFORM 1
FROM "ql3"."projects" AS project
JOIN "ql3"."project_role_bindings" AS binding
ON binding.project_id = project.id
AND binding.subject_type = p_requested_by_subject_type
AND binding.subject_id = p_requested_by_subject_id
AND binding.version = p_binding_version
WHERE project.id = p_project_id
AND project.status = 'active'
AND project.version = p_project_version
AND binding.state = 'active'
AND binding.version = (
SELECT max(candidate.version)
FROM "ql3"."project_role_bindings" AS candidate
WHERE candidate.project_id = p_project_id
AND candidate.subject_type = p_requested_by_subject_type
AND candidate.subject_id = p_requested_by_subject_id
)
FOR SHARE OF project, binding;
IF NOT FOUND THEN
RETURN;
END IF;
IF NOT "ql3"."plugin_package_automation_start_allowed"(
p_project_id, p_package_name, p_publication_digest
) THEN
RETURN;
END IF;
RETURN QUERY
SELECT publication.publication_json, revision.revision_json
FROM "ql3"."plugin_package_automation_publications" AS publication
JOIN "ql3"."plugin_package_materialized_revisions" AS revision
ON revision.generation_digest = publication.generation_digest
AND revision.revision_digest =
publication.materialized_revision_digest
WHERE publication.project_id = p_project_id
AND publication.package_name = p_package_name
AND publication.publication_digest = p_publication_digest
AND publication.state = 'active'
FOR SHARE OF publication, revision;
END
$ql3_ai$`,
`REVOKE ALL ON FUNCTION
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."plugin_package_prompt_admission_snapshot"(
varchar, varchar, char(64), varchar, varchar, integer, integer
)
FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager,
ql3_package_executor, ql3_worker_ingress`,
`GRANT EXECUTE ON FUNCTION
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."plugin_package_prompt_admission_snapshot"(
varchar, varchar, char(64), varchar, varchar, integer, integer
)
TO ql3_runtime`,
],
(context, statement) => context.query(statement).then(() => undefined),
);
const POSTGRES_PLUGIN_PACKAGE_PROMPT_FINALIZATION_TABLE_SQL = `
CREATE TABLE "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_finalizations" (
request_id varchar(128) PRIMARY KEY,
invocation_id varchar(128) NOT NULL UNIQUE,
plan_digest char(64) NOT NULL UNIQUE,
run_id varchar(36) NOT NULL UNIQUE,
step_run_id varchar(128) NOT NULL UNIQUE,
terminal_evidence_kind varchar(16) NOT NULL,
terminal_evidence_digest char(64) NOT NULL UNIQUE,
final_step_run_digest char(64) NOT NULL,
run_status varchar(16) NOT NULL,
event_id varchar(36) NOT NULL UNIQUE,
final_run_version integer NOT NULL,
final_run_event_sequence integer NOT NULL,
finalized_at_ms bigint NOT NULL,
receipt_digest char(64) NOT NULL UNIQUE,
receipt_json jsonb NOT NULL,
CONSTRAINT ql3_ai_prompt_finalization_admission_fk
FOREIGN KEY (request_id)
REFERENCES "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_admissions"
(request_id) ON DELETE RESTRICT,
CONSTRAINT ql3_ai_prompt_finalization_invocation_fk
FOREIGN KEY (invocation_id)
REFERENCES "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_admissions"
(invocation_id) ON DELETE RESTRICT,
CONSTRAINT ql3_ai_prompt_finalization_plan_fk
FOREIGN KEY (plan_digest)
REFERENCES "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_admissions"
(plan_digest) ON DELETE RESTRICT,
CONSTRAINT ql3_ai_prompt_finalization_run_fk
FOREIGN KEY (run_id) REFERENCES "ql3"."runs" (id) ON DELETE RESTRICT,
CONSTRAINT ql3_ai_prompt_finalization_step_fk
FOREIGN KEY (run_id, step_run_id)
REFERENCES "ql3"."step_runs" (run_id, id) ON DELETE RESTRICT,
CONSTRAINT ql3_ai_prompt_finalization_event_fk
FOREIGN KEY (event_id) REFERENCES "ql3"."run_events" (id)
ON DELETE RESTRICT,
CONSTRAINT ql3_ai_prompt_finalization_identity_check CHECK (
request_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
invocation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
run_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,35}$' AND
step_run_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
event_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,35}$' AND
terminal_evidence_kind IN ('completion', 'resolution') AND
run_status IN ('succeeded', 'failed', 'cancelled', 'timed_out') AND
final_run_version BETWEEN 3 AND 2147483647 AND
final_run_event_sequence = final_run_version AND finalized_at_ms >= 0
),
CONSTRAINT ql3_ai_prompt_finalization_digest_check CHECK (
plan_digest ~ '^[0-9a-f]{64}$' AND
terminal_evidence_digest ~ '^[0-9a-f]{64}$' AND
final_step_run_digest ~ '^[0-9a-f]{64}$' AND
receipt_digest ~ '^[0-9a-f]{64}$'
),
CONSTRAINT ql3_ai_prompt_finalization_json_check CHECK (
jsonb_typeof(receipt_json) = 'object' AND
octet_length(receipt_json::text) BETWEEN 2 AND 16384 AND
receipt_json @> jsonb_build_object(
'schema', 'qinglong/plugin-package-prompt-finalization-receipt@v1',
'requestId', request_id, 'invocationId', invocation_id,
'planDigest', plan_digest, 'runId', run_id, 'stepRunId', step_run_id,
'terminalEvidenceKind', terminal_evidence_kind,
'terminalEvidenceDigest', terminal_evidence_digest,
'finalStepRunDigest', final_step_run_digest, 'runStatus', run_status,
'eventId', event_id, 'finalRunVersion', final_run_version,
'finalRunEventSequence', final_run_event_sequence,
'finalizedAtMs', finalized_at_ms, 'receiptDigest', receipt_digest
)
)
)`;
const postgresPluginPackagePromptFinalizationMigration =
defineSqlMigration<PostgresQueryable>(
POSTGRES_PLUGIN_PACKAGE_PROMPT_FINALIZATION_MIGRATION_ID,
[
POSTGRES_PLUGIN_PACKAGE_PROMPT_FINALIZATION_TABLE_SQL,
`CREATE UNIQUE INDEX ql3_ai_prompt_finalization_run_step_uidx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_finalizations"
(run_id, step_run_id)`,
`CREATE INDEX ql3_ai_prompt_finalization_status_idx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_finalizations"
(run_status, finalized_at_ms, request_id)`,
`REVOKE ALL ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_finalizations"
FROM PUBLIC`,
`GRANT SELECT, INSERT ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_finalizations"
TO ql3_runtime`,
`GRANT SELECT ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."${POSTGRES_MODEL_INVOCATION_MIGRATION_HISTORY_TABLE}"
TO ql3_runtime`,
],
(context, statement) => context.query(statement).then(() => undefined),
);
const POSTGRES_PLUGIN_PACKAGE_PROMPT_OUTPUT_ARTIFACT_TABLE_SQL = `
CREATE TABLE "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_output_artifacts" (
artifact_id varchar(128) PRIMARY KEY,
project_id varchar(128) NOT NULL,
run_id varchar(36) NOT NULL,
step_run_id varchar(128) NOT NULL,
invocation_id varchar(128) NOT NULL UNIQUE,
requested_by_type varchar(32) NOT NULL,
requested_by_id varchar(128) NOT NULL,
provider varchar(256) NOT NULL,
model varchar(256) NOT NULL,
content_digest char(64) NOT NULL,
output_bytes integer NOT NULL,
retention_policy_revision varchar(128) NOT NULL,
retention_ms bigint NOT NULL,
retention_policy_digest char(64) NOT NULL,
retention_eligible_at_ms bigint NOT NULL,
key_id varchar(128) NOT NULL,
algorithm varchar(16) NOT NULL,
plaintext_bytes integer NOT NULL,
sealed_at_ms bigint NOT NULL,
artifact_digest char(64) NOT NULL UNIQUE,
artifact_json jsonb NOT NULL,
CONSTRAINT ql3_ai_prompt_output_artifact_admission_fk
FOREIGN KEY (invocation_id)
REFERENCES "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_admissions"
(invocation_id) ON DELETE RESTRICT,
CONSTRAINT ql3_ai_prompt_output_artifact_start_fk
FOREIGN KEY (invocation_id)
REFERENCES "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_starts"
(invocation_id) ON DELETE RESTRICT,
CONSTRAINT ql3_ai_prompt_output_artifact_step_fk
FOREIGN KEY (run_id, step_run_id)
REFERENCES "ql3"."step_runs" (run_id, id) ON DELETE RESTRICT,
CONSTRAINT ql3_ai_prompt_output_artifact_identity_check CHECK (
artifact_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
project_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
run_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,35}$' AND
step_run_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
invocation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
length(requested_by_type) BETWEEN 1 AND 32 AND
requested_by_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
length(provider) BETWEEN 1 AND 256 AND
length(model) BETWEEN 1 AND 256 AND
retention_policy_revision ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
key_id ~ '^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$' AND
algorithm = 'aes-256-gcm'
),
CONSTRAINT ql3_ai_prompt_output_artifact_value_check CHECK (
output_bytes BETWEEN 0 AND 1048576 AND
plaintext_bytes BETWEEN 1 AND 1052672 AND
retention_ms BETWEEN 3600000 AND 31536000000 AND
sealed_at_ms >= 0 AND
retention_eligible_at_ms = sealed_at_ms + retention_ms AND
content_digest ~ '^[0-9a-f]{64}$' AND
retention_policy_digest ~ '^[0-9a-f]{64}$' AND
artifact_digest ~ '^[0-9a-f]{64}$'
),
CONSTRAINT ql3_ai_prompt_output_artifact_json_check CHECK (
jsonb_typeof(artifact_json) = 'object' AND
octet_length(artifact_json::text) BETWEEN 2 AND 1572864 AND
artifact_json @> jsonb_build_object(
'schema', 'qinglong/plugin-package-prompt-output-artifact@v1',
'artifactId', artifact_id, 'projectId', project_id,
'runId', run_id, 'stepRunId', step_run_id,
'invocationId', invocation_id, 'provider', provider, 'model', model,
'contentDigest', content_digest, 'outputBytes', output_bytes,
'retentionPolicyDigest', retention_policy_digest,
'retentionEligibleAtMs', retention_eligible_at_ms,
'keyId', key_id, 'algorithm', algorithm,
'plaintextBytes', plaintext_bytes, 'sealedAtMs', sealed_at_ms,
'artifactDigest', artifact_digest
) AND
artifact_json -> 'requestedBy' @> jsonb_build_object(
'type', requested_by_type, 'id', requested_by_id
) AND
artifact_json -> 'retentionPolicy' @> jsonb_build_object(
'revision', retention_policy_revision, 'retentionMs', retention_ms
)
)
)`;
const postgresPluginPackagePromptOutputArtifactMigration =
defineSqlMigration<PostgresQueryable>(
POSTGRES_PLUGIN_PACKAGE_PROMPT_OUTPUT_ARTIFACT_MIGRATION_ID,
[
POSTGRES_PLUGIN_PACKAGE_PROMPT_OUTPUT_ARTIFACT_TABLE_SQL,
`CREATE INDEX ql3_ai_prompt_output_artifact_retention_idx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_output_artifacts"
(retention_eligible_at_ms, artifact_id)`,
`CREATE INDEX ql3_ai_prompt_output_artifact_project_run_idx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_output_artifacts"
(project_id, run_id, artifact_id)`,
`REVOKE ALL ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_output_artifacts"
FROM PUBLIC`,
`GRANT SELECT, INSERT ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_output_artifacts"
TO ql3_runtime`,
],
(context, statement) => context.query(statement).then(() => undefined),
);
const POSTGRES_PLUGIN_PACKAGE_PROMPT_OUTPUT_TOMBSTONE_TABLE_SQL = `
CREATE TABLE "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_output_artifact_tombstones" (
artifact_id varchar(128) PRIMARY KEY,
project_id varchar(128) NOT NULL,
run_id varchar(36) NOT NULL,
step_run_id varchar(128) NOT NULL,
invocation_id varchar(128) NOT NULL UNIQUE,
artifact_digest char(64) NOT NULL UNIQUE,
retention_policy_digest char(64) NOT NULL,
retention_eligible_at_ms bigint NOT NULL,
key_id varchar(128) NOT NULL,
tombstoned_at_ms bigint NOT NULL,
tombstone_digest char(64) NOT NULL UNIQUE,
tombstone_json jsonb NOT NULL,
CONSTRAINT ql3_ai_prompt_output_tombstone_admission_fk
FOREIGN KEY (invocation_id)
REFERENCES "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_admissions"
(invocation_id) ON DELETE RESTRICT,
CONSTRAINT ql3_ai_prompt_output_tombstone_start_fk
FOREIGN KEY (invocation_id)
REFERENCES "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_starts"
(invocation_id) ON DELETE RESTRICT,
CONSTRAINT ql3_ai_prompt_output_tombstone_step_fk
FOREIGN KEY (run_id, step_run_id)
REFERENCES "ql3"."step_runs" (run_id, id) ON DELETE RESTRICT,
CONSTRAINT ql3_ai_prompt_output_tombstone_identity_check CHECK (
artifact_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
project_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
run_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,35}$' AND
step_run_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
invocation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
key_id ~ '^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$'
),
CONSTRAINT ql3_ai_prompt_output_tombstone_value_check CHECK (
retention_eligible_at_ms >= 0 AND
tombstoned_at_ms >= retention_eligible_at_ms AND
artifact_digest ~ '^[0-9a-f]{64}$' AND
retention_policy_digest ~ '^[0-9a-f]{64}$' AND
tombstone_digest ~ '^[0-9a-f]{64}$'
),
CONSTRAINT ql3_ai_prompt_output_tombstone_json_check CHECK (
jsonb_typeof(tombstone_json) = 'object' AND
octet_length(tombstone_json::text) BETWEEN 2 AND 8192 AND
tombstone_json @> jsonb_build_object(
'schema', 'qinglong/plugin-package-prompt-output-artifact-tombstone@v1',
'tombstonedAtMs', tombstoned_at_ms,
'tombstoneDigest', tombstone_digest
) AND
tombstone_json -> 'reference' @> jsonb_build_object(
'artifactId', artifact_id, 'projectId', project_id,
'runId', run_id, 'stepRunId', step_run_id,
'invocationId', invocation_id, 'artifactDigest', artifact_digest,
'retentionPolicyDigest', retention_policy_digest,
'retentionEligibleAtMs', retention_eligible_at_ms, 'keyId', key_id
)
)
)`;
const postgresPluginPackagePromptOutputTombstoneMigration =
defineSqlMigration<PostgresQueryable>(
POSTGRES_PLUGIN_PACKAGE_PROMPT_OUTPUT_TOMBSTONE_MIGRATION_ID,
[
POSTGRES_PLUGIN_PACKAGE_PROMPT_OUTPUT_TOMBSTONE_TABLE_SQL,
`CREATE INDEX ql3_ai_prompt_output_tombstone_time_idx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_output_artifact_tombstones"
(tombstoned_at_ms, artifact_id)`,
`CREATE INDEX ql3_ai_prompt_output_tombstone_project_run_idx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_output_artifact_tombstones"
(project_id, run_id, artifact_id)`,
`REVOKE ALL ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_output_artifact_tombstones"
FROM PUBLIC`,
`DO $$
BEGIN
EXECUTE format(
'GRANT CONNECT ON DATABASE %I TO ql3_ai_maintenance',
current_database()
);
END
$$`,
`GRANT SELECT ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_output_artifact_tombstones"
TO ql3_runtime`,
`GRANT USAGE ON SCHEMA "${POSTGRES_MODEL_INVOCATION_SCHEMA}"
TO ql3_ai_maintenance`,
`GRANT SELECT ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."${POSTGRES_MODEL_INVOCATION_MIGRATION_HISTORY_TABLE}",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_completions",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_admissions",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_finalizations"
TO ql3_ai_maintenance`,
`GRANT SELECT, DELETE ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_output_artifacts"
TO ql3_ai_maintenance`,
`GRANT SELECT, INSERT ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_output_artifact_tombstones"
TO ql3_ai_maintenance`,
`GRANT USAGE ON SCHEMA "ql3" TO ql3_ai_maintenance`,
`GRANT SELECT ON TABLE "ql3"."runs", "ql3"."step_runs"
TO ql3_ai_maintenance`,
],
(context, statement) => context.query(statement).then(() => undefined),
);
const POSTGRES_PLUGIN_PACKAGE_PROMPT_OUTPUT_KEY_RETIREMENT_PREPARATION_TABLE_SQL = `
CREATE TABLE "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_output_key_retirement_preparations" (
key_id varchar(128) PRIMARY KEY,
retirement_id varchar(128) NOT NULL UNIQUE,
request_id varchar(128) NOT NULL UNIQUE,
mutation_id varchar(128) NOT NULL UNIQUE,
catalog_digest char(64) NOT NULL,
material_proof char(64) NOT NULL,
prepared_at_ms bigint NOT NULL,
preparation_digest char(64) NOT NULL UNIQUE,
preparation_json jsonb NOT NULL,
CONSTRAINT ql3_ai_prompt_output_key_retirement_preparation_fk_uidx
UNIQUE (key_id, preparation_digest),
CONSTRAINT ql3_ai_prompt_output_key_retirement_preparation_identity_check CHECK (
key_id ~ '^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$' AND
retirement_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
request_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
mutation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$'
),
CONSTRAINT ql3_ai_prompt_output_key_retirement_preparation_value_check CHECK (
prepared_at_ms >= 0 AND
catalog_digest ~ '^[0-9a-f]{64}$' AND
material_proof ~ '^[0-9a-f]{64}$' AND
preparation_digest ~ '^[0-9a-f]{64}$'
),
CONSTRAINT ql3_ai_prompt_output_key_retirement_preparation_json_check CHECK (
jsonb_typeof(preparation_json) = 'object' AND
octet_length(preparation_json::text) BETWEEN 2 AND 8192 AND
preparation_json @> jsonb_build_object(
'schema', 'qinglong/plugin-package-prompt-output-key-retirement-preparation@v1',
'keyId', key_id, 'retirementId', retirement_id,
'requestId', request_id, 'mutationId', mutation_id,
'catalogDigest', catalog_digest, 'materialProof', material_proof,
'preparedAtMs', prepared_at_ms,
'preparationDigest', preparation_digest
)
)
)`;
const POSTGRES_PLUGIN_PACKAGE_PROMPT_OUTPUT_KEY_RETIREMENT_COMPLETION_TABLE_SQL = `
CREATE TABLE "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_output_key_retirement_completions" (
key_id varchar(128) PRIMARY KEY,
retirement_id varchar(128) NOT NULL UNIQUE,
request_id varchar(128) NOT NULL UNIQUE,
mutation_id varchar(128) NOT NULL UNIQUE,
preparation_digest char(64) NOT NULL UNIQUE,
retired_catalog_digest char(64) NOT NULL,
absence_proof char(64) NOT NULL,
completed_at_ms bigint NOT NULL,
completion_digest char(64) NOT NULL UNIQUE,
completion_json jsonb NOT NULL,
CONSTRAINT ql3_ai_prompt_output_key_retirement_completion_preparation_fk
FOREIGN KEY (key_id, preparation_digest)
REFERENCES "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_output_key_retirement_preparations"
(key_id, preparation_digest) ON DELETE RESTRICT,
CONSTRAINT ql3_ai_prompt_output_key_retirement_completion_identity_check CHECK (
key_id ~ '^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$' AND
retirement_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
request_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
mutation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$'
),
CONSTRAINT ql3_ai_prompt_output_key_retirement_completion_value_check CHECK (
completed_at_ms >= 0 AND
preparation_digest ~ '^[0-9a-f]{64}$' AND
retired_catalog_digest ~ '^[0-9a-f]{64}$' AND
absence_proof ~ '^[0-9a-f]{64}$' AND
completion_digest ~ '^[0-9a-f]{64}$'
),
CONSTRAINT ql3_ai_prompt_output_key_retirement_completion_json_check CHECK (
jsonb_typeof(completion_json) = 'object' AND
octet_length(completion_json::text) BETWEEN 2 AND 8192 AND
completion_json @> jsonb_build_object(
'schema', 'qinglong/plugin-package-prompt-output-key-retirement-completion@v1',
'keyId', key_id, 'retirementId', retirement_id,
'requestId', request_id, 'mutationId', mutation_id,
'preparationDigest', preparation_digest,
'retiredCatalogDigest', retired_catalog_digest,
'absenceProof', absence_proof, 'completedAtMs', completed_at_ms,
'completionDigest', completion_digest
)
)
)`;
const postgresPluginPackagePromptOutputKeyRetirementMigration =
defineSqlMigration<PostgresQueryable>(
POSTGRES_PLUGIN_PACKAGE_PROMPT_OUTPUT_KEY_RETIREMENT_MIGRATION_ID,
[
POSTGRES_PLUGIN_PACKAGE_PROMPT_OUTPUT_KEY_RETIREMENT_PREPARATION_TABLE_SQL,
POSTGRES_PLUGIN_PACKAGE_PROMPT_OUTPUT_KEY_RETIREMENT_COMPLETION_TABLE_SQL,
`CREATE INDEX ql3_ai_prompt_output_key_retirement_completion_time_idx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_output_key_retirement_completions"
(completed_at_ms, key_id)`,
`REVOKE ALL ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_output_key_retirement_preparations",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_output_key_retirement_completions"
FROM PUBLIC`,
`GRANT SELECT ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_output_key_retirement_preparations"
TO ql3_runtime`,
`GRANT SELECT, INSERT ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_output_key_retirement_preparations",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_output_key_retirement_completions"
TO ql3_ai_maintenance`,
],
(context, statement) => context.query(statement).then(() => undefined),
);
const POSTGRES_PLUGIN_PACKAGE_PROMPT_OUTPUT_KEY_ROTATION_PREPARATION_TABLE_SQL = `
CREATE TABLE "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_output_key_rotation_preparations" (
rotation_id varchar(128) PRIMARY KEY,
request_id varchar(128) NOT NULL UNIQUE,
mutation_id varchar(128) NOT NULL UNIQUE,
expected_secret_uid varchar(128) NOT NULL,
expected_active_key_id varchar(128) NOT NULL,
expected_catalog_digest char(64) NOT NULL,
new_key_id varchar(128) NOT NULL UNIQUE,
material_proof char(64) NOT NULL,
prepared_at_ms bigint NOT NULL,
preparation_digest char(64) NOT NULL UNIQUE,
preparation_json jsonb NOT NULL,
CONSTRAINT ql3_ai_prompt_key_rotation_preparation_fk_uidx
UNIQUE (rotation_id, preparation_digest),
CONSTRAINT ql3_ai_prompt_key_rotation_source_uidx
UNIQUE (expected_secret_uid, expected_catalog_digest),
CONSTRAINT ql3_ai_prompt_key_rotation_preparation_identity_check CHECK (
rotation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
request_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
mutation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
expected_secret_uid ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
expected_active_key_id ~ '^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$' AND
new_key_id ~ '^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$' AND
expected_active_key_id <> new_key_id
),
CONSTRAINT ql3_ai_prompt_key_rotation_preparation_value_check CHECK (
prepared_at_ms >= 0 AND
expected_catalog_digest ~ '^[0-9a-f]{64}$' AND
material_proof ~ '^[0-9a-f]{64}$' AND
preparation_digest ~ '^[0-9a-f]{64}$'
),
CONSTRAINT ql3_ai_prompt_key_rotation_preparation_json_check CHECK (
jsonb_typeof(preparation_json) = 'object' AND
octet_length(preparation_json::text) BETWEEN 2 AND 8192 AND
preparation_json @> jsonb_build_object(
'schema', 'qinglong/plugin-package-prompt-output-key-rotation-preparation@v1',
'rotationId', rotation_id, 'requestId', request_id,
'mutationId', mutation_id, 'expectedSecretUid', expected_secret_uid,
'expectedActiveKeyId', expected_active_key_id,
'expectedCatalogDigest', expected_catalog_digest,
'newKeyId', new_key_id, 'materialProof', material_proof,
'preparedAtMs', prepared_at_ms, 'preparationDigest', preparation_digest
)
)
)`;
const POSTGRES_PLUGIN_PACKAGE_PROMPT_OUTPUT_KEY_ROTATION_COMPLETION_TABLE_SQL = `
CREATE TABLE "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_output_key_rotation_completions" (
rotation_id varchar(128) PRIMARY KEY,
request_id varchar(128) NOT NULL UNIQUE,
mutation_id varchar(128) NOT NULL UNIQUE,
preparation_digest char(64) NOT NULL UNIQUE,
generation bigint NOT NULL,
previous_active_key_id varchar(128) NOT NULL,
active_key_id varchar(128) NOT NULL,
catalog_digest char(64) NOT NULL,
material_proof char(64) NOT NULL,
completed_at_ms bigint NOT NULL,
completion_digest char(64) NOT NULL UNIQUE,
completion_json jsonb NOT NULL,
CONSTRAINT ql3_ai_prompt_key_rotation_completion_preparation_fk
FOREIGN KEY (rotation_id, preparation_digest)
REFERENCES "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_output_key_rotation_preparations"
(rotation_id, preparation_digest) ON DELETE RESTRICT,
CONSTRAINT ql3_ai_prompt_key_rotation_completion_identity_check CHECK (
rotation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
request_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
mutation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
previous_active_key_id ~ '^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$' AND
active_key_id ~ '^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$' AND
previous_active_key_id <> active_key_id
),
CONSTRAINT ql3_ai_prompt_key_rotation_completion_value_check CHECK (
generation BETWEEN 2 AND 9007199254740991 AND completed_at_ms >= 0 AND
preparation_digest ~ '^[0-9a-f]{64}$' AND
catalog_digest ~ '^[0-9a-f]{64}$' AND
material_proof ~ '^[0-9a-f]{64}$' AND
completion_digest ~ '^[0-9a-f]{64}$'
),
CONSTRAINT ql3_ai_prompt_key_rotation_completion_json_check CHECK (
jsonb_typeof(completion_json) = 'object' AND
octet_length(completion_json::text) BETWEEN 2 AND 8192 AND
completion_json @> jsonb_build_object(
'schema', 'qinglong/plugin-package-prompt-output-key-rotation-completion@v1',
'rotationId', rotation_id, 'requestId', request_id,
'mutationId', mutation_id, 'preparationDigest', preparation_digest,
'generation', generation, 'previousActiveKeyId', previous_active_key_id,
'activeKeyId', active_key_id, 'catalogDigest', catalog_digest,
'materialProof', material_proof, 'completedAtMs', completed_at_ms,
'completionDigest', completion_digest
)
)
)`;
const postgresPluginPackagePromptOutputKeyRotationMigration =
defineSqlMigration<PostgresQueryable>(
POSTGRES_PLUGIN_PACKAGE_PROMPT_OUTPUT_KEY_ROTATION_MIGRATION_ID,
[
POSTGRES_PLUGIN_PACKAGE_PROMPT_OUTPUT_KEY_ROTATION_PREPARATION_TABLE_SQL,
POSTGRES_PLUGIN_PACKAGE_PROMPT_OUTPUT_KEY_ROTATION_COMPLETION_TABLE_SQL,
`CREATE INDEX ql3_ai_prompt_key_rotation_completion_time_idx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_output_key_rotation_completions"
(completed_at_ms, rotation_id)`,
`REVOKE ALL ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_output_key_rotation_preparations",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_output_key_rotation_completions"
FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager,
ql3_package_executor, ql3_automation_manager, ql3_worker_ingress,
ql3_worker_credential_manager, ql3_worker_credential_executor,
ql3_ai_credential_manager, ql3_ai_credential_tester`,
`GRANT SELECT, INSERT ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_output_key_rotation_preparations",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_prompt_output_key_rotation_completions"
TO ql3_ai_maintenance`,
],
(context, statement) => context.query(statement).then(() => undefined),
);
const postgresPluginPackagePromptProductAuthorizationMigration =
defineSqlMigration<PostgresQueryable>(
POSTGRES_PLUGIN_PACKAGE_PROMPT_PRODUCT_AUTHORIZATION_MIGRATION_ID,
[
`CREATE FUNCTION
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."plugin_package_prompt_authorize_admission"(
p_credential_id varchar,
p_credential_version integer,
p_project_id varchar,
p_subject_type varchar,
p_subject_id varchar,
p_project_version integer,
p_binding_version integer,
p_audit_event_id uuid,
p_request_id varchar,
p_planned_at_ms bigint,
p_replay boolean
)
RETURNS boolean
LANGUAGE plpgsql
VOLATILE
SECURITY DEFINER
SET search_path = pg_catalog, ql3, ql3_ai
AS $ql3_ai$
BEGIN
IF NOT pg_has_role(session_user, 'ql3_runtime', 'member') THEN
RAISE EXCEPTION 'Runtime authority is required'
USING ERRCODE = 'insufficient_privilege';
END IF;
PERFORM pg_advisory_xact_lock(
hashtextextended('ql3-api-credential:' || p_credential_id, 0)
);
PERFORM pg_advisory_xact_lock(
hashtextextended(
'ql3-identity:' || p_subject_type || ':' || p_subject_id,
0
)
);
PERFORM 1
FROM "ql3"."api_credentials" AS credential
JOIN "ql3"."identity_subjects" AS identity
ON identity.subject_type = credential.subject_type
AND identity.subject_id = credential.subject_id
WHERE credential.credential_id = p_credential_id
AND credential.version = p_credential_version
AND credential.version = (
SELECT max(candidate.version)
FROM "ql3"."api_credentials" AS candidate
WHERE candidate.credential_id = p_credential_id
)
AND credential.state = 'active'
AND credential.subject_type = p_subject_type
AND credential.subject_id = p_subject_id
AND credential.not_before_at_ms <=
floor(extract(epoch FROM clock_timestamp()) * 1000)::bigint
AND credential.expires_at_ms >
floor(extract(epoch FROM clock_timestamp()) * 1000)::bigint
AND identity.status = 'active'
FOR SHARE OF credential, identity;
IF NOT FOUND THEN
RETURN false;
END IF;
PERFORM 1
FROM "ql3"."projects" AS project
JOIN "ql3"."project_role_bindings" AS binding
ON binding.project_id = project.id
AND binding.subject_type = p_subject_type
AND binding.subject_id = p_subject_id
AND binding.version = p_binding_version
WHERE project.id = p_project_id
AND project.status = 'active'
AND project.version = p_project_version
AND binding.state = 'active'
AND binding.role IN ('owner', 'admin', 'operator')
AND p_subject_type <> 'agent'
AND binding.version = (
SELECT max(candidate.version)
FROM "ql3"."project_role_bindings" AS candidate
WHERE candidate.project_id = p_project_id
AND candidate.subject_type = p_subject_type
AND candidate.subject_id = p_subject_id
)
FOR SHARE OF project, binding;
IF NOT FOUND THEN
RETURN false;
END IF;
IF NOT p_replay THEN
INSERT INTO "ql3"."security_audit_events" (
event_id, request_id, operation_id, project_id,
subject_type, subject_id, authentication_id, outcome, reasons,
project_version, binding_version, occurred_at_ms
) VALUES (
p_audit_event_id, p_request_id, 'prompt.execute', p_project_id,
p_subject_type, p_subject_id,
'api_credential:' || p_credential_id || ':' || p_credential_version,
'allowed', '["project_policy_allowed"]'::jsonb,
p_project_version, p_binding_version, p_planned_at_ms
);
END IF;
RETURN true;
END
$ql3_ai$`,
`REVOKE ALL ON FUNCTION
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."plugin_package_prompt_authorize_admission"(
varchar, integer, varchar, varchar, varchar, integer, integer,
uuid, varchar, bigint, boolean
)
FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager,
ql3_package_executor, ql3_worker_ingress`,
`GRANT EXECUTE ON FUNCTION
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."plugin_package_prompt_authorize_admission"(
varchar, integer, varchar, varchar, varchar, integer, integer,
uuid, varchar, bigint, boolean
)
TO ql3_runtime`,
],
(context, statement) => context.query(statement).then(() => undefined),
);
export const postgresPromptBaseMigrations = Object.freeze([
postgresPluginPackagePromptAdmissionMigration,
postgresPluginPackagePromptFinalizationMigration,
postgresPluginPackagePromptOutputArtifactMigration,
postgresPluginPackagePromptOutputTombstoneMigration,
postgresPluginPackagePromptOutputKeyRetirementMigration,
]);
export const postgresPromptExtensionMigrations = Object.freeze([
postgresPluginPackagePromptOutputKeyRotationMigration,
postgresPluginPackagePromptProductAuthorizationMigration,
]);
@@ -0,0 +1,377 @@
import type { PostgresQueryable } from '@qinglong/runtime-core';
import {
POSTGRES_MODEL_INVOCATION_USAGE_MIGRATION_ID,
POSTGRES_MODEL_INVOCATION_QUOTA_MIGRATION_ID,
POSTGRES_MODEL_INVOCATION_PRICING_MIGRATION_ID,
POSTGRES_MODEL_INVOCATION_SCHEMA,
} from '../identities';
import { defineSqlMigration } from '../shared';
const POSTGRES_USAGE_LEDGER_TABLE_SQL = `
CREATE TABLE "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_usage_ledger" (
invocation_id varchar(128) PRIMARY KEY,
project_id varchar(128) NOT NULL,
run_id varchar(36) NOT NULL,
step_run_id varchar(128) NOT NULL,
trace_id varchar(128) NOT NULL,
provider varchar(128) NOT NULL,
model varchar(128) NOT NULL,
policy_revision varchar(128) NOT NULL,
completion_digest char(64) NOT NULL,
outcome varchar(32) NOT NULL,
settled_at_ms bigint NOT NULL,
input_bytes integer NOT NULL,
output_bytes integer NOT NULL,
input_tokens bigint NOT NULL,
output_tokens bigint NOT NULL,
total_tokens bigint NOT NULL,
cost_micros bigint,
ledger_digest char(64) NOT NULL,
record_json jsonb NOT NULL,
FOREIGN KEY (invocation_id, completion_digest)
REFERENCES "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_completions"
(invocation_id, completion_digest) ON DELETE RESTRICT,
CONSTRAINT ql3_model_invocation_usage_identity_check CHECK (
invocation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
project_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
run_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
step_run_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
trace_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
provider ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
model ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
policy_revision ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$'
),
CONSTRAINT ql3_model_invocation_usage_value_check CHECK (
outcome IN ('succeeded', 'failed', 'timed_out', 'outcome_unknown') AND
settled_at_ms >= 0 AND
input_bytes BETWEEN 1 AND 262144 AND
output_bytes BETWEEN 0 AND 1048576 AND
input_tokens >= 0 AND output_tokens >= 0 AND
total_tokens = input_tokens + output_tokens AND
(cost_micros IS NULL OR cost_micros >= 0) AND
completion_digest ~ '^[0-9a-f]{64}$' AND
ledger_digest ~ '^[0-9a-f]{64}$'
),
CONSTRAINT ql3_model_invocation_usage_json_check CHECK (
jsonb_typeof(record_json) = 'object' AND
octet_length(record_json::text) BETWEEN 2 AND 24576 AND
record_json @> jsonb_build_object(
'schema', 'qinglong/model-invocation-usage-ledger@v1',
'invocationId', invocation_id,
'projectId', project_id,
'runId', run_id,
'stepRunId', step_run_id,
'traceId', trace_id,
'provider', provider,
'model', model,
'policyRevision', policy_revision,
'completionDigest', completion_digest,
'outcome', outcome,
'settledAtMs', settled_at_ms,
'inputBytes', input_bytes,
'outputBytes', output_bytes,
'inputTokens', input_tokens,
'outputTokens', output_tokens,
'totalTokens', total_tokens,
'costMicros', cost_micros,
'ledgerDigest', ledger_digest
)
)
)`;
const postgresUsageMigration = defineSqlMigration<PostgresQueryable>(
POSTGRES_MODEL_INVOCATION_USAGE_MIGRATION_ID,
[
`CREATE UNIQUE INDEX ql3_model_invocation_completions_identity_digest_uidx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_completions"
(invocation_id, completion_digest)`,
POSTGRES_USAGE_LEDGER_TABLE_SQL,
`CREATE INDEX ql3_model_invocation_usage_project_time_idx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_usage_ledger"
(project_id, settled_at_ms, invocation_id)`,
`CREATE UNIQUE INDEX ql3_model_invocation_usage_digest_uidx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_usage_ledger"
(ledger_digest)`,
`REVOKE ALL ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_usage_ledger"
FROM PUBLIC`,
`GRANT SELECT, INSERT ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_usage_ledger"
TO ql3_runtime`,
],
(context, statement) => context.query(statement).then(() => undefined),
);
const POSTGRES_QUOTA_RESERVATION_TABLE_SQL = `
CREATE TABLE "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_quota_reservations" (
invocation_id varchar(128) PRIMARY KEY,
project_id varchar(128) NOT NULL,
model_policy_revision varchar(128) NOT NULL,
quota_policy_revision varchar(128) NOT NULL,
window_ms bigint NOT NULL,
window_start_ms bigint NOT NULL,
window_end_ms bigint NOT NULL,
max_invocations integer NOT NULL,
max_tokens bigint NOT NULL,
max_cost_micros bigint,
reserved_tokens bigint NOT NULL,
reserved_cost_micros bigint,
reserved_at_ms bigint NOT NULL,
admission_digest char(64) NOT NULL,
reservation_digest char(64) NOT NULL,
record_json jsonb NOT NULL,
FOREIGN KEY (invocation_id)
REFERENCES "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_starts"
(invocation_id) ON DELETE RESTRICT,
CONSTRAINT ql3_model_invocation_quota_reservation_value_check CHECK (
window_ms IN (60000, 3600000, 86400000) AND
window_start_ms >= 0 AND window_end_ms = window_start_ms + window_ms AND
reserved_at_ms >= window_start_ms AND reserved_at_ms < window_end_ms AND
max_invocations BETWEEN 1 AND 100000 AND
max_tokens BETWEEN 1 AND 1000000000000 AND
(max_cost_micros IS NULL OR
max_cost_micros BETWEEN 0 AND 1000000000000000) AND
reserved_tokens BETWEEN 1 AND 1000000000000 AND
(reserved_cost_micros IS NULL OR
reserved_cost_micros BETWEEN 0 AND 1000000000000000) AND
(max_cost_micros IS NULL OR reserved_cost_micros IS NOT NULL) AND
admission_digest ~ '^[0-9a-f]{64}$' AND
reservation_digest ~ '^[0-9a-f]{64}$'
),
CONSTRAINT ql3_model_invocation_quota_reservation_json_check CHECK (
jsonb_typeof(record_json) = 'object' AND
octet_length(record_json::text) BETWEEN 2 AND 24576 AND
record_json @> jsonb_build_object(
'schema', 'qinglong/model-invocation-quota-reservation@v1',
'invocationId', invocation_id,
'projectId', project_id,
'modelPolicyRevision', model_policy_revision,
'quotaPolicyRevision', quota_policy_revision,
'windowMs', window_ms,
'windowStartMs', window_start_ms,
'windowEndMs', window_end_ms,
'maxInvocations', max_invocations,
'maxTokens', max_tokens,
'maxCostMicros', max_cost_micros,
'reservedTokens', reserved_tokens,
'reservedCostMicros', reserved_cost_micros,
'reservedAtMs', reserved_at_ms,
'admissionDigest', admission_digest,
'reservationDigest', reservation_digest
)
)
)`;
const POSTGRES_QUOTA_SETTLEMENT_TABLE_SQL = `
CREATE TABLE "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_quota_settlements" (
invocation_id varchar(128) PRIMARY KEY,
project_id varchar(128) NOT NULL,
reservation_digest char(64) NOT NULL,
completion_digest char(64) NOT NULL,
effective_tokens bigint NOT NULL,
effective_cost_micros bigint,
retained_token_reservation boolean NOT NULL,
retained_cost_reservation boolean NOT NULL,
settled_at_ms bigint NOT NULL,
settlement_digest char(64) NOT NULL,
record_json jsonb NOT NULL,
FOREIGN KEY (invocation_id)
REFERENCES "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_quota_reservations"
(invocation_id) ON DELETE RESTRICT,
FOREIGN KEY (invocation_id, completion_digest)
REFERENCES "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_completions"
(invocation_id, completion_digest) ON DELETE RESTRICT,
CONSTRAINT ql3_model_invocation_quota_settlement_value_check CHECK (
effective_tokens BETWEEN 0 AND 1000000000000 AND
(effective_cost_micros IS NULL OR
effective_cost_micros BETWEEN 0 AND 1000000000000000) AND
settled_at_ms >= 0 AND
reservation_digest ~ '^[0-9a-f]{64}$' AND
completion_digest ~ '^[0-9a-f]{64}$' AND
settlement_digest ~ '^[0-9a-f]{64}$'
),
CONSTRAINT ql3_model_invocation_quota_settlement_json_check CHECK (
jsonb_typeof(record_json) = 'object' AND
octet_length(record_json::text) BETWEEN 2 AND 24576 AND
record_json @> jsonb_build_object(
'schema', 'qinglong/model-invocation-quota-settlement@v1',
'invocationId', invocation_id,
'projectId', project_id,
'reservationDigest', reservation_digest,
'completionDigest', completion_digest,
'effectiveTokens', effective_tokens,
'effectiveCostMicros', effective_cost_micros,
'retainedTokenReservation', retained_token_reservation,
'retainedCostReservation', retained_cost_reservation,
'settledAtMs', settled_at_ms,
'settlementDigest', settlement_digest
)
)
)`;
const postgresQuotaMigration = defineSqlMigration<PostgresQueryable>(
POSTGRES_MODEL_INVOCATION_QUOTA_MIGRATION_ID,
[
POSTGRES_QUOTA_RESERVATION_TABLE_SQL,
`CREATE INDEX ql3_model_invocation_quota_reservation_window_idx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_quota_reservations"
(project_id, window_start_ms, invocation_id)`,
`CREATE UNIQUE INDEX ql3_model_invocation_quota_reservation_digest_uidx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_quota_reservations"
(reservation_digest)`,
POSTGRES_QUOTA_SETTLEMENT_TABLE_SQL,
`CREATE UNIQUE INDEX ql3_model_invocation_quota_settlement_digest_uidx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_quota_settlements"
(settlement_digest)`,
`REVOKE ALL ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_quota_reservations",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_quota_settlements"
FROM PUBLIC`,
`GRANT SELECT, INSERT ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_quota_reservations",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_quota_settlements"
TO ql3_runtime`,
],
(context, statement) => context.query(statement).then(() => undefined),
);
const POSTGRES_PRICE_QUOTE_TABLE_SQL = `
CREATE TABLE "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_price_quotes" (
invocation_id varchar(128) PRIMARY KEY,
project_id varchar(128) NOT NULL,
model_policy_revision varchar(128) NOT NULL,
provider varchar(128) NOT NULL,
model varchar(128) NOT NULL,
price_revision varchar(128) NOT NULL,
currency varchar(3) NOT NULL,
input_micros_per_million_tokens bigint NOT NULL,
output_micros_per_million_tokens bigint NOT NULL,
max_total_tokens bigint NOT NULL,
max_output_tokens bigint NOT NULL,
reserved_cost_micros bigint NOT NULL,
catalog_digest char(64) NOT NULL,
quote_digest char(64) NOT NULL,
record_json jsonb NOT NULL,
FOREIGN KEY (invocation_id)
REFERENCES "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_starts"
(invocation_id) ON DELETE RESTRICT,
CONSTRAINT ql3_model_invocation_price_quote_identity_check CHECK (
project_id ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
model_policy_revision ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
provider ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
model ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$' AND
price_revision ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$'
),
CONSTRAINT ql3_model_invocation_price_quote_value_check CHECK (
currency = 'USD' AND
input_micros_per_million_tokens BETWEEN 0 AND 1000000000000 AND
output_micros_per_million_tokens BETWEEN 0 AND 1000000000000 AND
max_total_tokens BETWEEN 1 AND 1000000000000 AND
max_output_tokens BETWEEN 1 AND max_total_tokens AND
reserved_cost_micros BETWEEN 0 AND 1000000000000000 AND
catalog_digest ~ '^[0-9a-f]{64}$' AND
quote_digest ~ '^[0-9a-f]{64}$'
),
CONSTRAINT ql3_model_invocation_price_quote_json_check CHECK (
jsonb_typeof(record_json) = 'object' AND
octet_length(record_json::text) BETWEEN 2 AND 24576 AND
record_json @> jsonb_build_object(
'schema', 'qinglong/model-invocation-price-quote@v1',
'invocationId', invocation_id,
'projectId', project_id,
'modelPolicyRevision', model_policy_revision,
'provider', provider,
'model', model,
'priceRevision', price_revision,
'currency', currency,
'inputMicrosPerMillionTokens', input_micros_per_million_tokens,
'outputMicrosPerMillionTokens', output_micros_per_million_tokens,
'maxTotalTokens', max_total_tokens,
'maxOutputTokens', max_output_tokens,
'reservedCostMicros', reserved_cost_micros,
'catalogDigest', catalog_digest,
'quoteDigest', quote_digest
)
)
)`;
const POSTGRES_PRICE_SETTLEMENT_TABLE_SQL = `
CREATE TABLE "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_price_settlements" (
invocation_id varchar(128) PRIMARY KEY,
project_id varchar(128) NOT NULL,
quote_digest char(64) NOT NULL,
completion_digest char(64) NOT NULL,
currency varchar(3) NOT NULL,
input_tokens bigint NOT NULL,
output_tokens bigint NOT NULL,
cost_micros bigint NOT NULL,
settled_at_ms bigint NOT NULL,
settlement_digest char(64) NOT NULL,
record_json jsonb NOT NULL,
FOREIGN KEY (invocation_id, quote_digest)
REFERENCES "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_price_quotes"
(invocation_id, quote_digest) ON DELETE RESTRICT,
FOREIGN KEY (invocation_id, completion_digest)
REFERENCES "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_completions"
(invocation_id, completion_digest) ON DELETE RESTRICT,
CONSTRAINT ql3_model_invocation_price_settlement_value_check CHECK (
currency = 'USD' AND
input_tokens >= 0 AND output_tokens >= 0 AND
cost_micros BETWEEN 0 AND 1000000000000000 AND
settled_at_ms >= 0 AND
quote_digest ~ '^[0-9a-f]{64}$' AND
completion_digest ~ '^[0-9a-f]{64}$' AND
settlement_digest ~ '^[0-9a-f]{64}$'
),
CONSTRAINT ql3_model_invocation_price_settlement_json_check CHECK (
jsonb_typeof(record_json) = 'object' AND
octet_length(record_json::text) BETWEEN 2 AND 24576 AND
record_json @> jsonb_build_object(
'schema', 'qinglong/model-invocation-price-settlement@v1',
'invocationId', invocation_id,
'projectId', project_id,
'quoteDigest', quote_digest,
'completionDigest', completion_digest,
'currency', currency,
'inputTokens', input_tokens,
'outputTokens', output_tokens,
'costMicros', cost_micros,
'settledAtMs', settled_at_ms,
'settlementDigest', settlement_digest
)
)
)`;
const postgresPricingMigration = defineSqlMigration<PostgresQueryable>(
POSTGRES_MODEL_INVOCATION_PRICING_MIGRATION_ID,
[
POSTGRES_PRICE_QUOTE_TABLE_SQL,
`CREATE UNIQUE INDEX ql3_model_invocation_price_quote_identity_digest_uidx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_price_quotes"
(invocation_id, quote_digest)`,
`CREATE UNIQUE INDEX ql3_model_invocation_price_quote_digest_uidx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_price_quotes"
(quote_digest)`,
POSTGRES_PRICE_SETTLEMENT_TABLE_SQL,
`CREATE UNIQUE INDEX ql3_model_invocation_price_settlement_digest_uidx
ON "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_price_settlements"
(settlement_digest)`,
`REVOKE ALL ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_price_quotes",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_price_settlements"
FROM PUBLIC`,
`GRANT SELECT, INSERT ON TABLE
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_price_quotes",
"${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_price_settlements"
TO ql3_runtime`,
],
(context, statement) => context.query(statement).then(() => undefined),
);
export const postgresUsagePricingMigrations = Object.freeze([
postgresUsageMigration,
postgresQuotaMigration,
postgresPricingMigration,
]);
@@ -0,0 +1,94 @@
import { createHash } from 'node:crypto';
import {
MigrationStreamHistoryCorruptionError,
type MigrationStreamRecord,
type MigrationStreamStep,
} from '@qinglong/runtime-core/migration-stream';
export interface HistoryRow extends Record<string, unknown> {
readonly migrationId: unknown;
readonly streamId: unknown;
readonly dialect: unknown;
readonly checksum: unknown;
readonly appliedAtMs: unknown;
}
function checksum(statements: readonly string[]): string {
return createHash('sha256')
.update(
JSON.stringify({
format: 1,
statements,
}),
)
.digest('hex');
}
export function defineSqlMigration<TContext>(
id: string,
statementsValue: readonly string[],
execute: (context: TContext, statement: string) => void | Promise<void>,
): MigrationStreamStep<TContext> {
const statements = Object.freeze(
statementsValue.map((statement) => statement.trim()),
);
if (
statements.length < 1 ||
statements.length > 32 ||
statements.some(
(statement) => statement.length < 1 || statement.length > 64 * 1024,
)
) {
throw new TypeError('ModelInvocation migration statements are invalid');
}
return Object.freeze({
id,
checksum: checksum(statements),
async up(context: TContext): Promise<void> {
for (const statement of statements) await execute(context, statement);
},
});
}
function appliedAtMs(value: unknown, migrationId: string): number {
if (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0) {
return value;
}
if (typeof value === 'string' && /^(0|[1-9]\d*)$/.test(value)) {
const parsed = Number(value);
if (Number.isSafeInteger(parsed)) return parsed;
}
throw new MigrationStreamHistoryCorruptionError(migrationId);
}
export function historyRecord(
row: HistoryRow,
expected: Readonly<{
migrationIds: readonly string[];
streamId: string;
dialect: 'sqlite' | 'postgresql';
}>,
): MigrationStreamRecord {
const corruptionId =
typeof row.migrationId === 'string'
? row.migrationId
: expected.migrationIds[0] ?? expected.streamId;
if (
typeof row.migrationId !== 'string' ||
!expected.migrationIds.includes(row.migrationId) ||
row.streamId !== expected.streamId ||
row.dialect !== expected.dialect ||
typeof row.checksum !== 'string' ||
!/^[0-9a-f]{64}$/.test(row.checksum)
) {
throw new MigrationStreamHistoryCorruptionError(corruptionId);
}
return Object.freeze({
migrationId: row.migrationId,
streamId: row.streamId,
dialect: expected.dialect,
checksum: row.checksum,
appliedAtMs: appliedAtMs(row.appliedAtMs, row.migrationId),
});
}
@@ -0,0 +1,320 @@
import { Buffer } from 'node:buffer';
import { createHash } from 'node:crypto';
import type { DatabaseSync } from 'node:sqlite';
import {
MigrationStreamHistoryCorruptionError,
auditMigrationStreamHistory,
runMigrationStream,
type MigrationStreamDefinition,
type MigrationStreamRecord,
type MigrationStreamStore,
type MigrationStreamTransaction,
} from '@qinglong/runtime-core/migration-stream';
import {
LOCAL_MODEL_INVOCATION_MIGRATION_STREAM_ID,
LOCAL_MODEL_INVOCATION_MIGRATION_ID,
LOCAL_MODEL_INVOCATION_USAGE_MIGRATION_ID,
LOCAL_MODEL_INVOCATION_QUOTA_MIGRATION_ID,
LOCAL_MODEL_INVOCATION_PRICING_MIGRATION_ID,
LOCAL_MODEL_PRICE_CATALOG_MIGRATION_ID,
LOCAL_MODEL_PRICE_CATALOG_AUTHORIZATION_MIGRATION_ID,
LOCAL_MODEL_INVOCATION_FEATURE_ACTIVATION_MIGRATION_ID,
LOCAL_PLUGIN_PACKAGE_PROMPT_ADMISSION_MIGRATION_ID,
LOCAL_PLUGIN_PACKAGE_PROMPT_FINALIZATION_MIGRATION_ID,
LOCAL_PLUGIN_PACKAGE_PROMPT_OUTPUT_ARTIFACT_MIGRATION_ID,
LOCAL_PLUGIN_PACKAGE_PROMPT_OUTPUT_TOMBSTONE_MIGRATION_ID,
LOCAL_PLUGIN_PACKAGE_PROMPT_OUTPUT_KEY_RETIREMENT_MIGRATION_ID,
LOCAL_MODEL_PROVIDER_CREDENTIAL_CATALOG_MIGRATION_ID,
LOCAL_MODEL_INVOCATION_MIGRATION_HISTORY_TABLE,
} from './identities';
import { historyRecord, type HistoryRow } from './shared';
import { sqliteCatalogMigrations } from './sqlite/catalog';
import type { LocalMigrationContext } from './sqlite/context';
import { sqliteCoreMigrations } from './sqlite/core';
import { sqliteCredentialMigrations } from './sqlite/credential';
import { sqlitePromptMigrations } from './sqlite/prompt';
import { sqliteUsagePricingMigrations } from './sqlite/usagePricing';
const LOCAL_HISTORY_IDENTITY = Object.freeze({
migrationIds: Object.freeze([
LOCAL_MODEL_INVOCATION_MIGRATION_ID,
LOCAL_MODEL_INVOCATION_USAGE_MIGRATION_ID,
LOCAL_MODEL_INVOCATION_QUOTA_MIGRATION_ID,
LOCAL_MODEL_INVOCATION_PRICING_MIGRATION_ID,
LOCAL_MODEL_PRICE_CATALOG_MIGRATION_ID,
LOCAL_MODEL_PRICE_CATALOG_AUTHORIZATION_MIGRATION_ID,
LOCAL_MODEL_INVOCATION_FEATURE_ACTIVATION_MIGRATION_ID,
LOCAL_PLUGIN_PACKAGE_PROMPT_ADMISSION_MIGRATION_ID,
LOCAL_PLUGIN_PACKAGE_PROMPT_FINALIZATION_MIGRATION_ID,
LOCAL_PLUGIN_PACKAGE_PROMPT_OUTPUT_ARTIFACT_MIGRATION_ID,
LOCAL_PLUGIN_PACKAGE_PROMPT_OUTPUT_TOMBSTONE_MIGRATION_ID,
LOCAL_PLUGIN_PACKAGE_PROMPT_OUTPUT_KEY_RETIREMENT_MIGRATION_ID,
LOCAL_MODEL_PROVIDER_CREDENTIAL_CATALOG_MIGRATION_ID,
]),
streamId: LOCAL_MODEL_INVOCATION_MIGRATION_STREAM_ID,
dialect: 'sqlite' as const,
});
function readLocalHistory(
client: DatabaseSync,
migrationId?: string,
): readonly MigrationStreamRecord[] {
const rows = client
.prepare(
`SELECT
migration_id AS "migrationId",
stream_id AS "streamId",
dialect,
checksum,
applied_at_ms AS "appliedAtMs"
FROM "${LOCAL_MODEL_INVOCATION_MIGRATION_HISTORY_TABLE}"
WHERE stream_id = ?
${migrationId === undefined ? '' : 'AND migration_id = ?'}
ORDER BY migration_id`,
)
.all(
LOCAL_MODEL_INVOCATION_MIGRATION_STREAM_ID,
...(migrationId === undefined ? [] : [migrationId]),
) as unknown as HistoryRow[];
return rows.map((row) => historyRecord(row, LOCAL_HISTORY_IDENTITY));
}
class LocalModelInvocationMigrationStore
implements MigrationStreamStore<LocalMigrationContext>
{
constructor(private readonly client: DatabaseSync) {}
async ensureHistory(): Promise<void> {
const required = [
'QingLong3SchemaMigrations',
'Runs',
'RunEvents',
'StepRuns',
'StepRunMutations',
];
const rows = this.client
.prepare(
`SELECT name FROM sqlite_schema
WHERE type = 'table' AND name IN (${required
.map(() => '?')
.join(', ')})`,
)
.all(...required) as unknown as { name?: unknown }[];
const names = new Set(
rows
.map((row) => row.name)
.filter((name): name is string => typeof name === 'string'),
);
if (required.some((name) => !names.has(name))) {
throw new TypeError(
'Local ModelInvocation feature requires the main SQLite migration stream',
);
}
this.client.exec(`
CREATE TABLE IF NOT EXISTS "${LOCAL_MODEL_INVOCATION_MIGRATION_HISTORY_TABLE}" (
migration_id TEXT PRIMARY KEY,
stream_id TEXT NOT NULL
CONSTRAINT ql3_ai_migrations_stream_check
CHECK (stream_id = '${LOCAL_MODEL_INVOCATION_MIGRATION_STREAM_ID}'),
dialect TEXT NOT NULL
CONSTRAINT ql3_ai_migrations_dialect_check CHECK (dialect = 'sqlite'),
checksum TEXT NOT NULL
CONSTRAINT ql3_ai_migrations_checksum_check
CHECK (length(checksum) = 64 AND checksum NOT GLOB '*[^0-9a-f]*'),
applied_at_ms INTEGER NOT NULL
CONSTRAINT ql3_ai_migrations_applied_at_check CHECK (applied_at_ms >= 0)
)
`);
}
async listAll(): Promise<readonly MigrationStreamRecord[]> {
return readLocalHistory(this.client);
}
async findById(migrationId: string): Promise<MigrationStreamRecord | null> {
const rows = readLocalHistory(this.client, migrationId);
if (rows.length > 1) {
throw new MigrationStreamHistoryCorruptionError(migrationId);
}
return rows[0] ?? null;
}
async transaction<T>(
work: (
transaction: MigrationStreamTransaction<LocalMigrationContext>,
) => Promise<T>,
): Promise<T> {
this.client.exec('BEGIN IMMEDIATE');
try {
const result = await work({
context: { client: this.client },
findById: (migrationId) => this.findById(migrationId),
insert: async (record) => {
if (
record.streamId !== LOCAL_MODEL_INVOCATION_MIGRATION_STREAM_ID ||
!LOCAL_HISTORY_IDENTITY.migrationIds.includes(record.migrationId) ||
record.dialect !== 'sqlite'
) {
throw new TypeError(
'Local ModelInvocation migration identity is invalid',
);
}
this.client
.prepare(
`INSERT INTO "${LOCAL_MODEL_INVOCATION_MIGRATION_HISTORY_TABLE}"
(migration_id, stream_id, dialect, checksum, applied_at_ms)
VALUES (?, ?, ?, ?, ?)`,
)
.run(
record.migrationId,
record.streamId,
record.dialect,
record.checksum,
record.appliedAtMs,
);
},
});
this.client.exec('COMMIT');
return result;
} catch (error) {
if (this.client.isTransaction) this.client.exec('ROLLBACK');
throw error;
}
}
}
export const localModelInvocationMigrationDefinition: MigrationStreamDefinition<LocalMigrationContext> =
Object.freeze({
id: LOCAL_MODEL_INVOCATION_MIGRATION_STREAM_ID,
dialect: 'sqlite',
migrationIdScheme: 'sqlite-numbered',
checksumScheme: 'sha256',
migrations: Object.freeze([
...sqliteCoreMigrations,
...sqliteUsagePricingMigrations,
...sqliteCatalogMigrations,
...sqlitePromptMigrations,
...sqliteCredentialMigrations,
]),
});
export const LOCAL_MODEL_INVOCATION_MIGRATION_PLAN_DIGEST = createHash('sha256')
.update(
Buffer.from('qinglong/local-model-invocation-migration-plan@v1\0', 'utf8'),
)
.update(
JSON.stringify(
localModelInvocationMigrationDefinition.migrations.map(
({ id, checksum: migrationChecksum }) => ({
id,
checksum: migrationChecksum,
}),
),
),
'utf8',
)
.digest('hex');
export class LocalModelInvocationFeatureNotReadyError extends Error {
readonly code = 'LOCAL_MODEL_INVOCATION_FEATURE_NOT_READY';
constructor(options?: ErrorOptions) {
super('The local ModelInvocation feature schema is not ready', options);
this.name = 'LocalModelInvocationFeatureNotReadyError';
}
}
const LOCAL_MODEL_INVOCATION_FEATURE_TABLES = Object.freeze([
'ModelInvocationCompletions',
'ModelInvocationFeatureHead',
'ModelInvocationFeatureTransitions',
'ModelInvocationPriceQuotes',
'ModelInvocationPriceSettlements',
'ModelInvocationPromptAdmissions',
'ModelInvocationPromptFinalizations',
'ModelInvocationPromptOutputArtifactTombstones',
'ModelInvocationPromptOutputArtifacts',
'ModelInvocationPromptOutputKeyRetirementCompletions',
'ModelInvocationPromptOutputKeyRetirementPreparations',
'ModelInvocationProviderCredentialAudits',
'ModelInvocationProviderCredentialBindings',
'ModelInvocationProviderCredentialTransitions',
'ModelInvocationQuotaReservations',
'ModelInvocationQuotaSettlements',
'ModelInvocationResolutions',
'ModelInvocationStarts',
'ModelInvocationUsageLedger',
'ModelPriceCatalogAuthorizations',
'ModelPriceCatalogHeads',
'ModelPriceCatalogPublications',
LOCAL_MODEL_INVOCATION_MIGRATION_HISTORY_TABLE,
]);
export function assertLocalModelInvocationFeatureReady(
client: DatabaseSync,
): void {
try {
if (
!client ||
typeof client !== 'object' ||
!client.isOpen ||
typeof client.prepare !== 'function'
) {
throw new LocalModelInvocationFeatureNotReadyError();
}
const tables = (
client
.prepare(
`SELECT name
FROM sqlite_schema
WHERE type = 'table'
AND (
name = ?
OR name LIKE 'ModelInvocation%'
OR name LIKE 'ModelPriceCatalog%'
)
ORDER BY name`,
)
.all(LOCAL_MODEL_INVOCATION_MIGRATION_HISTORY_TABLE) as {
readonly name?: unknown;
}[]
).map(({ name }) => name);
if (
tables.length !== LOCAL_MODEL_INVOCATION_FEATURE_TABLES.length ||
tables.some(
(table, index) =>
table !== LOCAL_MODEL_INVOCATION_FEATURE_TABLES[index],
)
) {
throw new LocalModelInvocationFeatureNotReadyError();
}
const applied = auditMigrationStreamHistory(
readLocalHistory(client),
localModelInvocationMigrationDefinition,
);
if (
applied.size !== localModelInvocationMigrationDefinition.migrations.length
) {
throw new LocalModelInvocationFeatureNotReadyError();
}
} catch (error) {
if (error instanceof LocalModelInvocationFeatureNotReadyError) {
throw error;
}
throw new LocalModelInvocationFeatureNotReadyError({
cause: error instanceof Error ? error : undefined,
});
}
}
export async function migrateLocalModelInvocationFeature(
client: DatabaseSync,
): Promise<void> {
await runMigrationStream({
stream: localModelInvocationMigrationDefinition,
store: new LocalModelInvocationMigrationStore(client),
});
}
@@ -0,0 +1,486 @@
import type { LocalMigrationContext } from './context';
import {
LOCAL_MODEL_PRICE_CATALOG_MIGRATION_ID,
LOCAL_MODEL_PRICE_CATALOG_AUTHORIZATION_MIGRATION_ID,
LOCAL_MODEL_INVOCATION_FEATURE_ACTIVATION_MIGRATION_ID,
} from '../identities';
import { defineSqlMigration } from '../shared';
const LOCAL_MODEL_PRICE_CATALOG_PUBLICATION_TABLE_SQL = `
CREATE TABLE "ModelPriceCatalogPublications" (
provider TEXT NOT NULL,
model TEXT NOT NULL,
price_revision TEXT NOT NULL,
catalog_digest TEXT NOT NULL,
mutation_id TEXT NOT NULL UNIQUE,
command_digest TEXT NOT NULL,
publication_digest TEXT NOT NULL,
published_at_ms INTEGER NOT NULL,
published_by_user_id TEXT NOT NULL,
publication_json TEXT NOT NULL,
PRIMARY KEY (provider, model, price_revision),
CONSTRAINT ql3_model_price_catalog_publication_identity_check CHECK (
length(provider) BETWEEN 1 AND 128 AND
length(model) BETWEEN 1 AND 128 AND
length(price_revision) BETWEEN 1 AND 128 AND
length(mutation_id) BETWEEN 1 AND 128 AND
length(published_by_user_id) BETWEEN 1 AND 128
),
CONSTRAINT ql3_model_price_catalog_publication_value_check CHECK (
published_at_ms >= 0 AND
length(catalog_digest) = 64 AND
catalog_digest NOT GLOB '*[^0-9a-f]*' AND
length(command_digest) = 64 AND
command_digest NOT GLOB '*[^0-9a-f]*' AND
length(publication_digest) = 64 AND
publication_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_model_price_catalog_publication_json_check CHECK (
length(CAST(publication_json AS BLOB)) BETWEEN 2 AND 24576 AND
json_valid(publication_json) AND
json_type(publication_json) = 'object' AND
json_extract(publication_json, '$.schema') =
'qinglong/model-price-catalog-publication@v1' AND
json_extract(publication_json, '$.entry.schema') =
'qinglong/model-price-catalog-entry@v1' AND
json_extract(publication_json, '$.entry.provider') = provider AND
json_extract(publication_json, '$.entry.model') = model AND
json_extract(publication_json, '$.entry.priceRevision') =
price_revision AND
json_extract(publication_json, '$.entry.catalogDigest') =
catalog_digest AND
json_extract(publication_json, '$.entry.publishedAtMs') =
published_at_ms AND
json_extract(publication_json, '$.mutationId') = mutation_id AND
json_extract(publication_json, '$.publishedByUserId') =
published_by_user_id AND
json_extract(publication_json, '$.commandDigest') = command_digest AND
json_extract(publication_json, '$.publicationDigest') =
publication_digest
)
)`;
const LOCAL_MODEL_PRICE_CATALOG_HEAD_TABLE_SQL = `
CREATE TABLE "ModelPriceCatalogHeads" (
provider TEXT NOT NULL,
model TEXT NOT NULL,
generation INTEGER NOT NULL,
previous_head_digest TEXT,
active_price_revision TEXT,
active_catalog_digest TEXT,
revoked_price_revision TEXT,
revoked_catalog_digest TEXT,
action TEXT NOT NULL,
mutation_id TEXT NOT NULL UNIQUE,
changed_by_user_id TEXT NOT NULL,
changed_at_ms INTEGER NOT NULL,
command_digest TEXT NOT NULL,
head_digest TEXT NOT NULL UNIQUE,
head_json TEXT NOT NULL,
PRIMARY KEY (provider, model, generation),
FOREIGN KEY (previous_head_digest)
REFERENCES "ModelPriceCatalogHeads" (head_digest) ON DELETE RESTRICT,
FOREIGN KEY (
provider, model, active_price_revision, active_catalog_digest
) REFERENCES "ModelPriceCatalogPublications" (
provider, model, price_revision, catalog_digest
) ON DELETE RESTRICT,
FOREIGN KEY (
provider, model, revoked_price_revision, revoked_catalog_digest
) REFERENCES "ModelPriceCatalogPublications" (
provider, model, price_revision, catalog_digest
) ON DELETE RESTRICT,
CONSTRAINT ql3_model_price_catalog_head_identity_check CHECK (
length(provider) BETWEEN 1 AND 128 AND
length(model) BETWEEN 1 AND 128 AND
length(mutation_id) BETWEEN 1 AND 128 AND
length(changed_by_user_id) BETWEEN 1 AND 128
),
CONSTRAINT ql3_model_price_catalog_head_value_check CHECK (
generation BETWEEN 1 AND 2147483647 AND
((generation = 1 AND previous_head_digest IS NULL) OR
(generation > 1 AND previous_head_digest IS NOT NULL)) AND
((active_price_revision IS NULL AND active_catalog_digest IS NULL) OR
(active_price_revision IS NOT NULL AND
active_catalog_digest IS NOT NULL)) AND
((revoked_price_revision IS NULL AND revoked_catalog_digest IS NULL) OR
(revoked_price_revision IS NOT NULL AND
revoked_catalog_digest IS NOT NULL)) AND
(revoked_price_revision IS NULL OR
revoked_price_revision <> active_price_revision) AND
action IN ('activate', 'deactivate', 'revoke') AND
((action = 'revoke') = (revoked_price_revision IS NOT NULL)) AND
changed_at_ms >= 0 AND
(previous_head_digest IS NULL OR (
length(previous_head_digest) = 64 AND
previous_head_digest NOT GLOB '*[^0-9a-f]*'
)) AND
(active_catalog_digest IS NULL OR (
length(active_catalog_digest) = 64 AND
active_catalog_digest NOT GLOB '*[^0-9a-f]*'
)) AND
(revoked_catalog_digest IS NULL OR (
length(revoked_catalog_digest) = 64 AND
revoked_catalog_digest NOT GLOB '*[^0-9a-f]*'
)) AND
length(command_digest) = 64 AND
command_digest NOT GLOB '*[^0-9a-f]*' AND
length(head_digest) = 64 AND head_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_model_price_catalog_head_json_check CHECK (
length(CAST(head_json AS BLOB)) BETWEEN 2 AND 24576 AND
json_valid(head_json) AND json_type(head_json) = 'object' AND
json_extract(head_json, '$.schema') =
'qinglong/model-price-catalog-head@v1' AND
json_extract(head_json, '$.provider') = provider AND
json_extract(head_json, '$.model') = model AND
json_extract(head_json, '$.generation') = generation AND
((previous_head_digest IS NULL AND
json_type(head_json, '$.previousHeadDigest') = 'null') OR
json_extract(head_json, '$.previousHeadDigest') =
previous_head_digest) AND
((active_price_revision IS NULL AND
json_type(head_json, '$.activePriceRevision') = 'null') OR
json_extract(head_json, '$.activePriceRevision') =
active_price_revision) AND
((active_catalog_digest IS NULL AND
json_type(head_json, '$.activeCatalogDigest') = 'null') OR
json_extract(head_json, '$.activeCatalogDigest') =
active_catalog_digest) AND
((revoked_price_revision IS NULL AND
json_type(head_json, '$.revokedPriceRevision') = 'null') OR
json_extract(head_json, '$.revokedPriceRevision') =
revoked_price_revision) AND
((revoked_catalog_digest IS NULL AND
json_type(head_json, '$.revokedCatalogDigest') = 'null') OR
json_extract(head_json, '$.revokedCatalogDigest') =
revoked_catalog_digest) AND
json_extract(head_json, '$.action') = action AND
json_extract(head_json, '$.mutationId') = mutation_id AND
json_extract(head_json, '$.changedByUserId') =
changed_by_user_id AND
json_extract(head_json, '$.changedAtMs') = changed_at_ms AND
json_extract(head_json, '$.commandDigest') = command_digest AND
json_extract(head_json, '$.headDigest') = head_digest
)
)`;
const localModelPriceCatalogMigration =
defineSqlMigration<LocalMigrationContext>(
LOCAL_MODEL_PRICE_CATALOG_MIGRATION_ID,
[
LOCAL_MODEL_PRICE_CATALOG_PUBLICATION_TABLE_SQL,
`CREATE UNIQUE INDEX ql3_model_price_catalog_publication_identity_digest_uidx
ON "ModelPriceCatalogPublications"
(provider, model, price_revision, catalog_digest)`,
`CREATE UNIQUE INDEX ql3_model_price_catalog_publication_digest_uidx
ON "ModelPriceCatalogPublications" (publication_digest)`,
LOCAL_MODEL_PRICE_CATALOG_HEAD_TABLE_SQL,
`CREATE INDEX ql3_model_price_catalog_head_current_idx
ON "ModelPriceCatalogHeads" (provider, model, generation DESC)`,
`CREATE UNIQUE INDEX ql3_model_price_catalog_revoked_revision_uidx
ON "ModelPriceCatalogHeads"
(provider, model, revoked_price_revision)
WHERE revoked_price_revision IS NOT NULL`,
],
(context, statement) => context.client.exec(statement),
);
const LOCAL_MODEL_PRICE_CATALOG_AUTHORIZATION_TABLE_SQL = `
CREATE TABLE "ModelPriceCatalogAuthorizations" (
authorization_id TEXT PRIMARY KEY,
request_id TEXT NOT NULL,
operation TEXT NOT NULL,
provider TEXT NOT NULL,
model TEXT NOT NULL,
price_revision TEXT,
catalog_command_digest TEXT NOT NULL UNIQUE,
publication_digest TEXT UNIQUE,
head_digest TEXT UNIQUE,
result_digest TEXT NOT NULL UNIQUE,
user_id TEXT NOT NULL,
authentication_id TEXT NOT NULL,
assurance TEXT NOT NULL,
authenticated_at_ms INTEGER NOT NULL,
expires_at_ms INTEGER NOT NULL,
policy_revision TEXT NOT NULL,
policy_decision_digest TEXT NOT NULL,
decision_mode TEXT NOT NULL,
command_digest TEXT NOT NULL UNIQUE,
committed_at_ms INTEGER NOT NULL,
authorization_digest TEXT NOT NULL UNIQUE,
reasons_json TEXT NOT NULL,
authorization_json TEXT NOT NULL,
FOREIGN KEY (publication_digest)
REFERENCES "ModelPriceCatalogPublications" (publication_digest)
ON DELETE RESTRICT,
FOREIGN KEY (head_digest)
REFERENCES "ModelPriceCatalogHeads" (head_digest)
ON DELETE RESTRICT,
CONSTRAINT ql3_model_price_catalog_authorization_identity_check CHECK (
length(authorization_id) BETWEEN 1 AND 128 AND
length(request_id) BETWEEN 1 AND 128 AND
length(provider) BETWEEN 1 AND 128 AND
length(model) BETWEEN 1 AND 128 AND
(price_revision IS NULL OR length(price_revision) BETWEEN 1 AND 128) AND
length(user_id) BETWEEN 1 AND 128 AND
length(authentication_id) BETWEEN 1 AND 128 AND
length(policy_revision) BETWEEN 1 AND 128
),
CONSTRAINT ql3_model_price_catalog_authorization_value_check CHECK (
operation IN ('publish', 'activate', 'deactivate', 'revoke') AND
((operation = 'deactivate' AND price_revision IS NULL) OR
(operation <> 'deactivate' AND price_revision IS NOT NULL)) AND
((operation = 'publish' AND publication_digest IS NOT NULL AND
head_digest IS NULL AND result_digest = publication_digest) OR
(operation <> 'publish' AND publication_digest IS NULL AND
head_digest IS NOT NULL AND result_digest = head_digest)) AND
assurance IN ('multi_factor', 'hardware', 'local_console') AND
decision_mode IN ('human_confirmation', 'separation_of_duty') AND
authenticated_at_ms >= 0 AND
expires_at_ms > authenticated_at_ms AND
committed_at_ms >= authenticated_at_ms AND
committed_at_ms < expires_at_ms AND
committed_at_ms - authenticated_at_ms <= 300000 AND
length(catalog_command_digest) = 64 AND
catalog_command_digest NOT GLOB '*[^0-9a-f]*' AND
length(result_digest) = 64 AND
result_digest NOT GLOB '*[^0-9a-f]*' AND
length(policy_decision_digest) = 64 AND
policy_decision_digest NOT GLOB '*[^0-9a-f]*' AND
length(command_digest) = 64 AND
command_digest NOT GLOB '*[^0-9a-f]*' AND
length(authorization_digest) = 64 AND
authorization_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_model_price_catalog_authorization_reasons_check CHECK (
length(CAST(reasons_json AS BLOB)) BETWEEN 5 AND 2048 AND
json_valid(reasons_json) AND json_type(reasons_json) = 'array' AND
json_array_length(reasons_json) BETWEEN 1 AND 8
),
CONSTRAINT ql3_model_price_catalog_authorization_json_check CHECK (
length(CAST(authorization_json AS BLOB)) BETWEEN 2 AND 32768 AND
json_valid(authorization_json) AND
json_type(authorization_json) = 'object' AND
json_extract(authorization_json, '$.schema') =
'qinglong/model-price-catalog-authorization@v1' AND
json_extract(authorization_json, '$.authorizationId') =
authorization_id AND
json_extract(authorization_json, '$.requestId') = request_id AND
json_extract(authorization_json, '$.operation') = operation AND
json_extract(authorization_json, '$.provider') = provider AND
json_extract(authorization_json, '$.model') = model AND
((price_revision IS NULL AND
json_type(authorization_json, '$.priceRevision') = 'null') OR
json_extract(authorization_json, '$.priceRevision') =
price_revision) AND
json_extract(authorization_json, '$.catalogCommandDigest') =
catalog_command_digest AND
json_extract(authorization_json, '$.resultDigest') = result_digest AND
json_extract(authorization_json, '$.principal.subject.type') = 'user' AND
json_extract(authorization_json, '$.principal.subject.id') = user_id AND
json_extract(authorization_json, '$.principal.authenticationId') =
authentication_id AND
json_extract(authorization_json, '$.principal.assurance') = assurance AND
json_extract(authorization_json, '$.principal.authenticatedAtMs') =
authenticated_at_ms AND
json_extract(authorization_json, '$.principal.expiresAtMs') =
expires_at_ms AND
json_extract(authorization_json, '$.policy.revision') =
policy_revision AND
json_extract(authorization_json, '$.policy.decisionDigest') =
policy_decision_digest AND
json_extract(authorization_json, '$.decisionMode') = decision_mode AND
json_extract(authorization_json, '$.commandDigest') = command_digest AND
json_extract(authorization_json, '$.committedAtMs') = committed_at_ms AND
json_extract(authorization_json, '$.authorizationDigest') =
authorization_digest AND
json_extract(authorization_json, '$.policy.reasons') =
json(reasons_json)
)
)`;
const localModelPriceCatalogAuthorizationMigration =
defineSqlMigration<LocalMigrationContext>(
LOCAL_MODEL_PRICE_CATALOG_AUTHORIZATION_MIGRATION_ID,
[
LOCAL_MODEL_PRICE_CATALOG_AUTHORIZATION_TABLE_SQL,
`CREATE INDEX ql3_model_price_catalog_authorization_target_idx
ON "ModelPriceCatalogAuthorizations"
(provider, model, operation, committed_at_ms DESC)`,
],
(context, statement) => context.client.exec(statement),
);
const LOCAL_MODEL_INVOCATION_FEATURE_TRANSITION_TABLE_SQL = `
CREATE TABLE "ModelInvocationFeatureTransitions" (
feature_id TEXT NOT NULL
CONSTRAINT ql3_ai_feature_transition_feature_check
CHECK (feature_id = 'model-invocation'),
generation INTEGER NOT NULL
CONSTRAINT ql3_ai_feature_transition_generation_check
CHECK (generation BETWEEN 1 AND 2147483647),
previous_generation INTEGER,
state TEXT NOT NULL
CONSTRAINT ql3_ai_feature_transition_state_check
CHECK (state IN ('active', 'inactive')),
mutation_id TEXT NOT NULL UNIQUE,
request_id TEXT NOT NULL,
expected_migration_digest TEXT NOT NULL,
safety_mode TEXT NOT NULL
CONSTRAINT ql3_ai_feature_transition_safety_check
CHECK (safety_mode IN (
'fresh_database',
'backup_verified',
'preserve_existing'
)),
backup_evidence_digest TEXT,
changed_by_user_id TEXT NOT NULL,
authentication_id TEXT NOT NULL,
assurance TEXT NOT NULL
CONSTRAINT ql3_ai_feature_transition_assurance_check
CHECK (assurance = 'local_console'),
command_digest TEXT NOT NULL,
transition_digest TEXT NOT NULL UNIQUE,
committed_at_ms INTEGER NOT NULL
CONSTRAINT ql3_ai_feature_transition_time_check
CHECK (committed_at_ms >= 0),
transition_json TEXT NOT NULL,
PRIMARY KEY (feature_id, generation),
UNIQUE (feature_id, generation, transition_digest),
CONSTRAINT ql3_ai_feature_transition_previous_fk
FOREIGN KEY (feature_id, previous_generation)
REFERENCES "ModelInvocationFeatureTransitions" (feature_id, generation)
ON DELETE RESTRICT,
CONSTRAINT ql3_ai_feature_transition_previous_check CHECK (
(
generation = 1 AND
previous_generation IS NULL AND
state = 'active'
) OR (
generation > 1 AND
previous_generation = generation - 1
)
),
CONSTRAINT ql3_ai_feature_transition_identity_check CHECK (
length(mutation_id) BETWEEN 1 AND 128 AND
length(request_id) BETWEEN 1 AND 128 AND
length(changed_by_user_id) BETWEEN 1 AND 255 AND
length(authentication_id) BETWEEN 1 AND 128
),
CONSTRAINT ql3_ai_feature_transition_digest_check CHECK (
length(expected_migration_digest) = 64 AND
expected_migration_digest NOT GLOB '*[^0-9a-f]*' AND
(
backup_evidence_digest IS NULL OR
length(backup_evidence_digest) = 64 AND
backup_evidence_digest NOT GLOB '*[^0-9a-f]*'
) AND
length(command_digest) = 64 AND
command_digest NOT GLOB '*[^0-9a-f]*' AND
length(transition_digest) = 64 AND
transition_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_ai_feature_transition_safety_shape_check CHECK (
(
state = 'active' AND
(
(
safety_mode = 'fresh_database' AND
backup_evidence_digest IS NULL
) OR (
safety_mode = 'backup_verified' AND
backup_evidence_digest IS NOT NULL
)
)
) OR (
state = 'inactive' AND
safety_mode = 'preserve_existing' AND
backup_evidence_digest IS NULL
)
),
CONSTRAINT ql3_ai_feature_transition_json_check CHECK (
json_valid(transition_json) AND
json_type(transition_json) = 'object' AND
json_extract(transition_json, '$.schema') =
'qinglong/model-invocation-feature-transition@v1' AND
json_extract(transition_json, '$.featureId') = feature_id AND
json_extract(transition_json, '$.generation') = generation AND
(
(
previous_generation IS NULL AND
json_type(transition_json, '$.previousGeneration') = 'null'
) OR
json_extract(transition_json, '$.previousGeneration') =
previous_generation
) AND
json_extract(transition_json, '$.state') = state AND
json_extract(transition_json, '$.mutationId') = mutation_id AND
json_extract(transition_json, '$.requestId') = request_id AND
json_extract(transition_json, '$.expectedMigrationDigest') =
expected_migration_digest AND
json_extract(transition_json, '$.safety.mode') = safety_mode AND
(
(
backup_evidence_digest IS NULL AND
json_type(transition_json, '$.safety.backupEvidenceDigest') = 'null'
) OR
json_extract(transition_json, '$.safety.backupEvidenceDigest') =
backup_evidence_digest
) AND
json_extract(transition_json, '$.changedByUserId') =
changed_by_user_id AND
json_extract(transition_json, '$.authenticationId') =
authentication_id AND
json_extract(transition_json, '$.assurance') = assurance AND
json_extract(transition_json, '$.commandDigest') = command_digest AND
json_extract(transition_json, '$.committedAtMs') = committed_at_ms AND
json_extract(transition_json, '$.transitionDigest') =
transition_digest
)
)`;
const LOCAL_MODEL_INVOCATION_FEATURE_HEAD_TABLE_SQL = `
CREATE TABLE "ModelInvocationFeatureHead" (
feature_id TEXT PRIMARY KEY NOT NULL
CONSTRAINT ql3_ai_feature_head_feature_check
CHECK (feature_id = 'model-invocation'),
generation INTEGER NOT NULL
CONSTRAINT ql3_ai_feature_head_generation_check
CHECK (generation BETWEEN 1 AND 2147483647),
state TEXT NOT NULL
CONSTRAINT ql3_ai_feature_head_state_check
CHECK (state IN ('active', 'inactive')),
transition_digest TEXT NOT NULL UNIQUE,
updated_at_ms INTEGER NOT NULL
CONSTRAINT ql3_ai_feature_head_time_check
CHECK (updated_at_ms >= 0),
CONSTRAINT ql3_ai_feature_head_transition_fk
FOREIGN KEY (feature_id, generation, transition_digest)
REFERENCES "ModelInvocationFeatureTransitions" (
feature_id,
generation,
transition_digest
) ON DELETE RESTRICT
)`;
const localModelInvocationFeatureActivationMigration =
defineSqlMigration<LocalMigrationContext>(
LOCAL_MODEL_INVOCATION_FEATURE_ACTIVATION_MIGRATION_ID,
[
LOCAL_MODEL_INVOCATION_FEATURE_TRANSITION_TABLE_SQL,
`CREATE INDEX ql3_ai_feature_transition_state_idx
ON "ModelInvocationFeatureTransitions"
(feature_id, state, generation DESC)`,
LOCAL_MODEL_INVOCATION_FEATURE_HEAD_TABLE_SQL,
],
(context, statement) => context.client.exec(statement),
);
export const sqliteCatalogMigrations = Object.freeze([
localModelPriceCatalogMigration,
localModelPriceCatalogAuthorizationMigration,
localModelInvocationFeatureActivationMigration,
]);
@@ -0,0 +1,5 @@
import type { DatabaseSync } from 'node:sqlite';
export interface LocalMigrationContext {
readonly client: DatabaseSync;
}
@@ -0,0 +1,249 @@
import type { LocalMigrationContext } from './context';
import {
LOCAL_MODEL_INVOCATION_MIGRATION_ID,
} from '../identities';
import { defineSqlMigration } from '../shared';
const LOCAL_START_TABLE_SQL = `
CREATE TABLE "ModelInvocationStarts" (
invocation_id TEXT PRIMARY KEY NOT NULL,
project_id TEXT NOT NULL,
run_id TEXT NOT NULL,
step_run_id TEXT NOT NULL,
trace_id TEXT NOT NULL,
provider TEXT NOT NULL,
model TEXT NOT NULL,
policy_revision TEXT NOT NULL,
request_digest TEXT NOT NULL,
input_bytes INTEGER NOT NULL,
max_output_tokens INTEGER NOT NULL,
deadline_at_ms INTEGER NOT NULL,
admitted_at_ms INTEGER NOT NULL,
mutation_id TEXT NOT NULL,
mutation_digest TEXT NOT NULL,
run_event_id TEXT NOT NULL,
start_digest TEXT NOT NULL,
record_json TEXT NOT NULL,
FOREIGN KEY (run_id, step_run_id)
REFERENCES "StepRuns" (run_id, id) ON DELETE RESTRICT,
FOREIGN KEY (mutation_id)
REFERENCES "StepRunMutations" (mutation_id) ON DELETE RESTRICT,
FOREIGN KEY (run_event_id)
REFERENCES "RunEvents" (id) ON DELETE RESTRICT,
CONSTRAINT ql3_model_invocation_starts_identity_check CHECK (
length(invocation_id) BETWEEN 1 AND 128 AND
length(project_id) BETWEEN 1 AND 128 AND
length(run_id) BETWEEN 1 AND 128 AND
length(step_run_id) BETWEEN 1 AND 128 AND
length(trace_id) BETWEEN 1 AND 128 AND
length(provider) BETWEEN 1 AND 128 AND
length(model) BETWEEN 1 AND 128 AND
length(policy_revision) BETWEEN 1 AND 128 AND
length(mutation_id) BETWEEN 1 AND 128 AND
length(run_event_id) BETWEEN 1 AND 36
),
CONSTRAINT ql3_model_invocation_starts_digest_check CHECK (
length(request_digest) = 71 AND
substr(request_digest, 1, 7) = 'sha256:' AND
substr(request_digest, 8) NOT GLOB '*[^0-9a-f]*' AND
length(mutation_digest) = 64 AND
mutation_digest NOT GLOB '*[^0-9a-f]*' AND
length(start_digest) = 64 AND
start_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_model_invocation_starts_budget_check CHECK (
input_bytes BETWEEN 1 AND 262144 AND
max_output_tokens BETWEEN 1 AND 32768 AND
admitted_at_ms >= 0 AND deadline_at_ms > admitted_at_ms
),
CONSTRAINT ql3_model_invocation_starts_json_check CHECK (
length(CAST(record_json AS BLOB)) BETWEEN 2 AND 24576 AND
json_valid(record_json) AND json_type(record_json) = 'object' AND
json_extract(record_json, '$.schema') =
'qinglong/model-invocation-start@v1' AND
json_extract(record_json, '$.invocationId') = invocation_id AND
json_extract(record_json, '$.projectId') = project_id AND
json_extract(record_json, '$.runId') = run_id AND
json_extract(record_json, '$.stepRunId') = step_run_id AND
json_extract(record_json, '$.traceId') = trace_id AND
json_extract(record_json, '$.provider') = provider AND
json_extract(record_json, '$.model') = model AND
json_extract(record_json, '$.policyRevision') = policy_revision AND
json_extract(record_json, '$.requestDigest') = request_digest AND
json_extract(record_json, '$.inputBytes') = input_bytes AND
json_extract(record_json, '$.maxOutputTokens') = max_output_tokens AND
json_extract(record_json, '$.deadlineAtMs') = deadline_at_ms AND
json_extract(record_json, '$.admittedAtMs') = admitted_at_ms AND
json_extract(record_json, '$.stepRunMutationId') = mutation_id AND
json_extract(record_json, '$.stepRunMutationDigest') = mutation_digest AND
json_extract(record_json, '$.runEventId') = run_event_id AND
json_extract(record_json, '$.startDigest') = start_digest
)
)`;
const LOCAL_COMPLETION_TABLE_SQL = `
CREATE TABLE "ModelInvocationCompletions" (
invocation_id TEXT PRIMARY KEY NOT NULL,
project_id TEXT NOT NULL,
run_id TEXT NOT NULL,
step_run_id TEXT NOT NULL,
trace_id TEXT NOT NULL,
start_digest TEXT NOT NULL,
outcome TEXT NOT NULL,
output_bytes INTEGER NOT NULL,
error_code TEXT,
completed_at_ms INTEGER NOT NULL,
mutation_id TEXT NOT NULL,
mutation_digest TEXT NOT NULL,
run_event_id TEXT NOT NULL,
completion_digest TEXT NOT NULL,
record_json TEXT NOT NULL,
FOREIGN KEY (invocation_id)
REFERENCES "ModelInvocationStarts" (invocation_id) ON DELETE RESTRICT,
FOREIGN KEY (run_id, step_run_id)
REFERENCES "StepRuns" (run_id, id) ON DELETE RESTRICT,
FOREIGN KEY (mutation_id)
REFERENCES "StepRunMutations" (mutation_id) ON DELETE RESTRICT,
FOREIGN KEY (run_event_id)
REFERENCES "RunEvents" (id) ON DELETE RESTRICT,
CONSTRAINT ql3_model_invocation_completions_outcome_check CHECK (
outcome IN ('succeeded', 'failed', 'timed_out', 'outcome_unknown')
),
CONSTRAINT ql3_model_invocation_completions_value_check CHECK (
output_bytes BETWEEN 0 AND 1048576 AND completed_at_ms >= 0 AND
((outcome = 'succeeded' AND error_code IS NULL) OR
(outcome <> 'succeeded' AND
length(error_code) BETWEEN 1 AND 64 AND
substr(error_code, 1, 1) GLOB '[A-Z]' AND
error_code NOT GLOB '*[^A-Z0-9_]*')) AND
length(start_digest) = 64 AND
start_digest NOT GLOB '*[^0-9a-f]*' AND
length(mutation_digest) = 64 AND
mutation_digest NOT GLOB '*[^0-9a-f]*' AND
length(completion_digest) = 64 AND
completion_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_model_invocation_completions_json_check CHECK (
length(CAST(record_json AS BLOB)) BETWEEN 2 AND 24576 AND
json_valid(record_json) AND json_type(record_json) = 'object' AND
json_extract(record_json, '$.schema') =
'qinglong/model-invocation-completion@v1' AND
json_extract(record_json, '$.invocationId') = invocation_id AND
json_extract(record_json, '$.projectId') = project_id AND
json_extract(record_json, '$.runId') = run_id AND
json_extract(record_json, '$.stepRunId') = step_run_id AND
json_extract(record_json, '$.traceId') = trace_id AND
json_extract(record_json, '$.startDigest') = start_digest AND
json_extract(record_json, '$.outcome') = outcome AND
json_extract(record_json, '$.outputBytes') = output_bytes AND
json_extract(record_json, '$.errorCode') IS error_code AND
json_extract(record_json, '$.completedAtMs') = completed_at_ms AND
json_extract(record_json, '$.stepRunMutationId') = mutation_id AND
json_extract(record_json, '$.stepRunMutationDigest') = mutation_digest AND
json_extract(record_json, '$.runEventId') = run_event_id AND
json_extract(record_json, '$.completionDigest') = completion_digest
)
)`;
const LOCAL_RESOLUTION_TABLE_SQL = `
CREATE TABLE "ModelInvocationResolutions" (
resolution_id TEXT PRIMARY KEY NOT NULL,
invocation_id TEXT NOT NULL UNIQUE,
project_id TEXT NOT NULL,
run_id TEXT NOT NULL,
step_run_id TEXT NOT NULL,
trace_id TEXT NOT NULL,
completion_digest TEXT NOT NULL,
decision TEXT NOT NULL,
resolved_by_user_id TEXT NOT NULL,
resolved_at_ms INTEGER NOT NULL,
mutation_id TEXT NOT NULL UNIQUE,
mutation_digest TEXT NOT NULL,
run_event_id TEXT NOT NULL UNIQUE,
resolution_digest TEXT NOT NULL UNIQUE,
record_json TEXT NOT NULL,
FOREIGN KEY (invocation_id)
REFERENCES "ModelInvocationCompletions" (invocation_id) ON DELETE RESTRICT,
FOREIGN KEY (run_id, step_run_id)
REFERENCES "StepRuns" (run_id, id) ON DELETE RESTRICT,
FOREIGN KEY (mutation_id)
REFERENCES "StepRunMutations" (mutation_id) ON DELETE RESTRICT,
FOREIGN KEY (run_event_id)
REFERENCES "RunEvents" (id) ON DELETE RESTRICT,
CONSTRAINT ql3_model_invocation_resolutions_value_check CHECK (
decision IN ('retry', 'fail', 'cancel') AND
resolved_at_ms >= 0 AND
length(resolution_id) BETWEEN 1 AND 128 AND
length(invocation_id) BETWEEN 1 AND 128 AND
length(project_id) BETWEEN 1 AND 128 AND
length(run_id) BETWEEN 1 AND 128 AND
length(step_run_id) BETWEEN 1 AND 128 AND
length(trace_id) BETWEEN 1 AND 128 AND
length(resolved_by_user_id) BETWEEN 1 AND 128 AND
length(mutation_id) BETWEEN 1 AND 128 AND
length(run_event_id) BETWEEN 1 AND 36 AND
length(completion_digest) = 64 AND
completion_digest NOT GLOB '*[^0-9a-f]*' AND
length(mutation_digest) = 64 AND
mutation_digest NOT GLOB '*[^0-9a-f]*' AND
length(resolution_digest) = 64 AND
resolution_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_model_invocation_resolutions_json_check CHECK (
length(CAST(record_json AS BLOB)) BETWEEN 2 AND 24576 AND
json_valid(record_json) AND json_type(record_json) = 'object' AND
json_extract(record_json, '$.schema') =
'qinglong/model-invocation-resolution@v1' AND
json_extract(record_json, '$.resolutionId') = resolution_id AND
json_extract(record_json, '$.invocationId') = invocation_id AND
json_extract(record_json, '$.projectId') = project_id AND
json_extract(record_json, '$.runId') = run_id AND
json_extract(record_json, '$.stepRunId') = step_run_id AND
json_extract(record_json, '$.traceId') = trace_id AND
json_extract(record_json, '$.completionDigest') = completion_digest AND
json_extract(record_json, '$.decision') = decision AND
json_extract(record_json, '$.resolvedByUserId') = resolved_by_user_id AND
json_extract(record_json, '$.resolvedAtMs') = resolved_at_ms AND
json_extract(record_json, '$.stepRunMutationId') = mutation_id AND
json_extract(record_json, '$.stepRunMutationDigest') = mutation_digest AND
json_extract(record_json, '$.runEventId') = run_event_id AND
json_extract(record_json, '$.resolutionDigest') = resolution_digest
)
)`;
const localMigration = defineSqlMigration<LocalMigrationContext>(
LOCAL_MODEL_INVOCATION_MIGRATION_ID,
[
LOCAL_START_TABLE_SQL,
`CREATE INDEX ql3_model_invocation_starts_step_history_idx
ON "ModelInvocationStarts"
(run_id, step_run_id, admitted_at_ms, invocation_id)`,
`CREATE UNIQUE INDEX ql3_model_invocation_starts_mutation_uidx
ON "ModelInvocationStarts" (mutation_id)`,
`CREATE UNIQUE INDEX ql3_model_invocation_starts_event_uidx
ON "ModelInvocationStarts" (run_event_id)`,
`CREATE UNIQUE INDEX ql3_model_invocation_starts_digest_uidx
ON "ModelInvocationStarts" (start_digest)`,
LOCAL_COMPLETION_TABLE_SQL,
`CREATE INDEX ql3_model_invocation_completions_step_history_idx
ON "ModelInvocationCompletions"
(run_id, step_run_id, completed_at_ms, invocation_id)`,
`CREATE UNIQUE INDEX ql3_model_invocation_completions_mutation_uidx
ON "ModelInvocationCompletions" (mutation_id)`,
`CREATE UNIQUE INDEX ql3_model_invocation_completions_event_uidx
ON "ModelInvocationCompletions" (run_event_id)`,
`CREATE UNIQUE INDEX ql3_model_invocation_completions_digest_uidx
ON "ModelInvocationCompletions" (completion_digest)`,
LOCAL_RESOLUTION_TABLE_SQL,
`CREATE INDEX ql3_model_invocation_resolutions_step_history_idx
ON "ModelInvocationResolutions"
(run_id, step_run_id, resolved_at_ms, resolution_id)`,
],
(context, statement) => context.client.exec(statement),
);
export const sqliteCoreMigrations = Object.freeze([
localMigration,
]);
@@ -0,0 +1,171 @@
import type { LocalMigrationContext } from './context';
import {
LOCAL_MODEL_PROVIDER_CREDENTIAL_CATALOG_MIGRATION_ID,
} from '../identities';
import { defineSqlMigration } from '../shared';
const LOCAL_MODEL_PROVIDER_CREDENTIAL_BINDING_TABLE_SQL = `
CREATE TABLE "ModelInvocationProviderCredentialBindings" (
project_id TEXT NOT NULL,
provider TEXT NOT NULL,
revision TEXT NOT NULL,
binding_digest TEXT NOT NULL,
binding_json TEXT NOT NULL,
PRIMARY KEY (project_id, provider, revision),
CONSTRAINT ql3_ai_local_provider_binding_identity_check CHECK (
length(project_id) BETWEEN 1 AND 128 AND
length(provider) BETWEEN 1 AND 128 AND
length(revision) BETWEEN 1 AND 128 AND
project_id NOT GLOB '*[^A-Za-z0-9._:/-]*' AND
provider NOT GLOB '*[^A-Za-z0-9._:/-]*' AND
revision NOT GLOB '*[^A-Za-z0-9._:/-]*'
),
CONSTRAINT ql3_ai_local_provider_binding_digest_check CHECK (
length(binding_digest) = 64 AND
binding_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_ai_local_provider_binding_json_check CHECK (
length(CAST(binding_json AS BLOB)) BETWEEN 2 AND 4096 AND
json_valid(binding_json) AND json_type(binding_json) = 'object' AND
json_extract(binding_json, '$.schema') =
'qinglong/model-provider-credential-binding@v1' AND
json_extract(binding_json, '$.projectId') = project_id AND
json_extract(binding_json, '$.provider') = provider AND
json_extract(binding_json, '$.revision') = revision AND
json_extract(binding_json, '$.scheme') = 'bearer'
)
)`;
const LOCAL_MODEL_PROVIDER_CREDENTIAL_TRANSITION_TABLE_SQL = `
CREATE TABLE "ModelInvocationProviderCredentialTransitions" (
mutation_id TEXT PRIMARY KEY NOT NULL,
project_id TEXT NOT NULL,
provider TEXT NOT NULL,
generation INTEGER NOT NULL,
action TEXT NOT NULL,
active_binding_revision TEXT,
active_binding_digest TEXT,
previous_transition_digest TEXT,
changed_by_type TEXT NOT NULL,
changed_by_id TEXT NOT NULL,
changed_at_ms INTEGER NOT NULL,
command_digest TEXT NOT NULL,
transition_digest TEXT NOT NULL,
command_json TEXT NOT NULL,
transition_json TEXT NOT NULL,
UNIQUE (project_id, provider, generation),
UNIQUE (transition_digest),
FOREIGN KEY (project_id, provider, active_binding_revision)
REFERENCES "ModelInvocationProviderCredentialBindings"
(project_id, provider, revision) ON DELETE RESTRICT,
CONSTRAINT ql3_ai_local_provider_transition_identity_check CHECK (
length(mutation_id) BETWEEN 1 AND 128 AND
length(project_id) BETWEEN 1 AND 128 AND
length(provider) BETWEEN 1 AND 128 AND
length(changed_by_id) BETWEEN 1 AND 128 AND
project_id NOT GLOB '*[^A-Za-z0-9._:/-]*' AND
provider NOT GLOB '*[^A-Za-z0-9._:/-]*'
),
CONSTRAINT ql3_ai_local_provider_transition_value_check CHECK (
generation BETWEEN 1 AND 2147483647 AND
action IN ('bind', 'revoke') AND
changed_by_type = 'user' AND
changed_at_ms >= 0 AND
((action = 'bind' AND active_binding_revision IS NOT NULL AND
active_binding_digest IS NOT NULL) OR
(action = 'revoke' AND active_binding_revision IS NULL AND
active_binding_digest IS NULL))
),
CONSTRAINT ql3_ai_local_provider_transition_digest_check CHECK (
length(command_digest) = 64 AND command_digest NOT GLOB '*[^0-9a-f]*' AND
length(transition_digest) = 64 AND
transition_digest NOT GLOB '*[^0-9a-f]*' AND
(previous_transition_digest IS NULL OR
(length(previous_transition_digest) = 64 AND
previous_transition_digest NOT GLOB '*[^0-9a-f]*')) AND
(active_binding_digest IS NULL OR
(length(active_binding_digest) = 64 AND
active_binding_digest NOT GLOB '*[^0-9a-f]*'))
),
CONSTRAINT ql3_ai_local_provider_transition_json_check CHECK (
length(CAST(command_json AS BLOB)) BETWEEN 2 AND 8192 AND
json_valid(command_json) AND json_type(command_json) = 'object' AND
json_extract(command_json, '$.mutationId') = mutation_id AND
json_extract(command_json, '$.projectId') = project_id AND
json_extract(command_json, '$.provider') = provider AND
json_extract(command_json, '$.action') = action AND
length(CAST(transition_json AS BLOB)) BETWEEN 2 AND 8192 AND
json_valid(transition_json) AND json_type(transition_json) = 'object' AND
json_extract(transition_json, '$.schema') =
'qinglong/model-provider-credential-transition@v1' AND
json_extract(transition_json, '$.mutationId') = mutation_id AND
json_extract(transition_json, '$.projectId') = project_id AND
json_extract(transition_json, '$.provider') = provider AND
json_extract(transition_json, '$.generation') = generation AND
json_extract(transition_json, '$.action') = action AND
json_extract(transition_json, '$.transitionDigest') = transition_digest
)
)`;
const LOCAL_MODEL_PROVIDER_CREDENTIAL_AUDIT_TABLE_SQL = `
CREATE TABLE "ModelInvocationProviderCredentialAudits" (
operation TEXT NOT NULL,
project_id TEXT NOT NULL,
provider TEXT NOT NULL,
request_id TEXT NOT NULL,
binding_revision TEXT NOT NULL,
binding_digest TEXT NOT NULL,
occurred_at_ms INTEGER NOT NULL,
audit_json TEXT NOT NULL,
PRIMARY KEY (operation, project_id, provider, request_id),
CONSTRAINT ql3_ai_local_provider_audit_identity_check CHECK (
operation IN ('list_models', 'generate', 'stream') AND
length(project_id) BETWEEN 1 AND 128 AND
length(provider) BETWEEN 1 AND 128 AND
length(request_id) BETWEEN 1 AND 128 AND
length(binding_revision) BETWEEN 1 AND 128
),
CONSTRAINT ql3_ai_local_provider_audit_digest_check CHECK (
length(binding_digest) = 71 AND
binding_digest GLOB 'sha256:[0-9a-f]*' AND
occurred_at_ms >= 0
),
CONSTRAINT ql3_ai_local_provider_audit_json_check CHECK (
length(CAST(audit_json AS BLOB)) BETWEEN 2 AND 4096 AND
json_valid(audit_json) AND json_type(audit_json) = 'object' AND
json_extract(audit_json, '$.schema') =
'qinglong/model-provider-credential-audit@v1' AND
json_extract(audit_json, '$.operation') = operation AND
json_extract(audit_json, '$.projectId') = project_id AND
json_extract(audit_json, '$.provider') = provider AND
json_extract(audit_json, '$.requestId') = request_id AND
json_extract(audit_json, '$.bindingRevision') = binding_revision AND
json_extract(audit_json, '$.bindingDigest') = binding_digest AND
json_extract(audit_json, '$.occurredAtMs') = occurred_at_ms
)
)`;
const localModelProviderCredentialCatalogMigration =
defineSqlMigration<LocalMigrationContext>(
LOCAL_MODEL_PROVIDER_CREDENTIAL_CATALOG_MIGRATION_ID,
[
LOCAL_MODEL_PROVIDER_CREDENTIAL_BINDING_TABLE_SQL,
`CREATE UNIQUE INDEX ql3_ai_local_provider_binding_digest_uidx
ON "ModelInvocationProviderCredentialBindings" (binding_digest)`,
LOCAL_MODEL_PROVIDER_CREDENTIAL_TRANSITION_TABLE_SQL,
`CREATE INDEX ql3_ai_local_provider_transition_current_idx
ON "ModelInvocationProviderCredentialTransitions"
(project_id, provider, generation DESC)`,
LOCAL_MODEL_PROVIDER_CREDENTIAL_AUDIT_TABLE_SQL,
`CREATE INDEX ql3_ai_local_provider_audit_time_idx
ON "ModelInvocationProviderCredentialAudits"
(project_id, occurred_at_ms DESC, provider, request_id)`,
],
(context, statement) => context.client.exec(statement),
);
export const sqliteCredentialMigrations = Object.freeze([
localModelProviderCredentialCatalogMigration,
]);
@@ -0,0 +1,539 @@
import type { LocalMigrationContext } from './context';
import {
LOCAL_PLUGIN_PACKAGE_PROMPT_ADMISSION_MIGRATION_ID,
LOCAL_PLUGIN_PACKAGE_PROMPT_FINALIZATION_MIGRATION_ID,
LOCAL_PLUGIN_PACKAGE_PROMPT_OUTPUT_ARTIFACT_MIGRATION_ID,
LOCAL_PLUGIN_PACKAGE_PROMPT_OUTPUT_TOMBSTONE_MIGRATION_ID,
LOCAL_PLUGIN_PACKAGE_PROMPT_OUTPUT_KEY_RETIREMENT_MIGRATION_ID,
} from '../identities';
import { defineSqlMigration } from '../shared';
const LOCAL_PLUGIN_PACKAGE_PROMPT_ADMISSION_TABLE_SQL = `
CREATE TABLE "ModelInvocationPromptAdmissions" (
request_id TEXT PRIMARY KEY NOT NULL,
invocation_id TEXT NOT NULL UNIQUE,
plan_digest TEXT NOT NULL UNIQUE,
run_id TEXT NOT NULL UNIQUE,
step_run_id TEXT NOT NULL UNIQUE,
project_id TEXT NOT NULL,
package_name TEXT NOT NULL,
installation_id TEXT NOT NULL,
lock_digest TEXT NOT NULL,
generation INTEGER NOT NULL,
generation_digest TEXT NOT NULL,
materialized_revision_digest TEXT NOT NULL,
publication_digest TEXT NOT NULL,
prompt_id TEXT NOT NULL,
prompt_definition_digest TEXT NOT NULL,
parameter_digest TEXT NOT NULL,
model_request_digest TEXT NOT NULL,
admitted_at_ms INTEGER NOT NULL,
receipt_digest TEXT NOT NULL UNIQUE,
plan_json TEXT NOT NULL,
receipt_json TEXT NOT NULL,
CONSTRAINT ql3_ai_prompt_admission_run_fk
FOREIGN KEY (run_id) REFERENCES "Runs" (id)
ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT ql3_ai_prompt_admission_step_fk
FOREIGN KEY (run_id, step_run_id) REFERENCES "StepRuns" (run_id, id)
ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT ql3_ai_prompt_admission_publication_fk
FOREIGN KEY (publication_digest)
REFERENCES "QingLong3PluginPackageAutomationPublications" (
publication_digest
) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT ql3_ai_prompt_admission_identity_check CHECK (
length(request_id) BETWEEN 1 AND 128 AND
length(invocation_id) BETWEEN 1 AND 128 AND
length(run_id) BETWEEN 1 AND 128 AND
length(step_run_id) BETWEEN 1 AND 128 AND
length(project_id) BETWEEN 1 AND 128 AND
length(package_name) BETWEEN 1 AND 63 AND
length(installation_id) BETWEEN 1 AND 128 AND
length(prompt_id) BETWEEN 1 AND 128 AND
generation BETWEEN 1 AND 2147483647 AND
admitted_at_ms >= 0
),
CONSTRAINT ql3_ai_prompt_admission_digest_check CHECK (
length(plan_digest) = 64 AND
plan_digest NOT GLOB '*[^0-9a-f]*' AND
length(lock_digest) = 64 AND
lock_digest NOT GLOB '*[^0-9a-f]*' AND
length(generation_digest) = 64 AND
generation_digest NOT GLOB '*[^0-9a-f]*' AND
length(materialized_revision_digest) = 64 AND
materialized_revision_digest NOT GLOB '*[^0-9a-f]*' AND
length(publication_digest) = 64 AND
publication_digest NOT GLOB '*[^0-9a-f]*' AND
length(prompt_definition_digest) = 64 AND
prompt_definition_digest NOT GLOB '*[^0-9a-f]*' AND
length(parameter_digest) = 64 AND
parameter_digest NOT GLOB '*[^0-9a-f]*' AND
model_request_digest GLOB 'sha256:[0-9a-f]*' AND
length(model_request_digest) = 71 AND
substr(model_request_digest, 8) NOT GLOB '*[^0-9a-f]*' AND
length(receipt_digest) = 64 AND
receipt_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_ai_prompt_admission_plan_json_check CHECK (
length(CAST(plan_json AS BLOB)) BETWEEN 2 AND 32768 AND
json_valid(plan_json) AND json_type(plan_json) = 'object' AND
json_extract(plan_json, '$.schema') =
'qinglong/plugin-package-prompt-execution-plan@v1' AND
json_extract(plan_json, '$.requestId') = request_id AND
json_extract(plan_json, '$.invocationId') = invocation_id AND
json_extract(plan_json, '$.planDigest') = plan_digest AND
json_extract(plan_json, '$.runId') = run_id AND
json_extract(plan_json, '$.stepRunId') = step_run_id AND
json_extract(plan_json, '$.target.projectId') = project_id AND
json_extract(plan_json, '$.target.packageName') = package_name AND
json_extract(plan_json, '$.target.installationId') = installation_id AND
json_extract(plan_json, '$.target.lockDigest') = lock_digest AND
json_extract(plan_json, '$.target.generation') = generation AND
json_extract(plan_json, '$.target.generationDigest') =
generation_digest AND
json_extract(plan_json, '$.target.materializedRevisionDigest') =
materialized_revision_digest AND
json_extract(plan_json, '$.target.publicationDigest') =
publication_digest AND
json_extract(plan_json, '$.target.promptId') = prompt_id AND
json_extract(plan_json, '$.target.promptDefinitionDigest') =
prompt_definition_digest AND
json_extract(plan_json, '$.parameterDigest') = parameter_digest AND
json_extract(plan_json, '$.modelRequestDigest') = model_request_digest AND
json_extract(plan_json, '$.plannedAtMs') = admitted_at_ms
),
CONSTRAINT ql3_ai_prompt_admission_receipt_json_check CHECK (
length(CAST(receipt_json AS BLOB)) BETWEEN 2 AND 16384 AND
json_valid(receipt_json) AND json_type(receipt_json) = 'object' AND
json_extract(receipt_json, '$.schema') =
'qinglong/plugin-package-prompt-admission-receipt@v1' AND
json_extract(receipt_json, '$.requestId') = request_id AND
json_extract(receipt_json, '$.invocationId') = invocation_id AND
json_extract(receipt_json, '$.planDigest') = plan_digest AND
json_extract(receipt_json, '$.runId') = run_id AND
json_extract(receipt_json, '$.stepRunId') = step_run_id AND
json_extract(receipt_json, '$.publicationDigest') =
publication_digest AND
json_extract(receipt_json, '$.promptId') = prompt_id AND
json_extract(receipt_json, '$.admittedAtMs') = admitted_at_ms AND
json_extract(receipt_json, '$.receiptDigest') = receipt_digest
)
)`;
const localPluginPackagePromptAdmissionMigration =
defineSqlMigration<LocalMigrationContext>(
LOCAL_PLUGIN_PACKAGE_PROMPT_ADMISSION_MIGRATION_ID,
[
LOCAL_PLUGIN_PACKAGE_PROMPT_ADMISSION_TABLE_SQL,
`CREATE UNIQUE INDEX ql3_ai_prompt_admission_run_step_uidx
ON "ModelInvocationPromptAdmissions" (run_id, step_run_id)`,
`CREATE INDEX ql3_ai_prompt_admission_target_idx
ON "ModelInvocationPromptAdmissions"
(project_id, package_name, admitted_at_ms, request_id)`,
],
(context, statement) => context.client.exec(statement),
);
const LOCAL_PLUGIN_PACKAGE_PROMPT_FINALIZATION_TABLE_SQL = `
CREATE TABLE "ModelInvocationPromptFinalizations" (
request_id TEXT PRIMARY KEY NOT NULL,
invocation_id TEXT NOT NULL UNIQUE,
plan_digest TEXT NOT NULL UNIQUE,
run_id TEXT NOT NULL UNIQUE,
step_run_id TEXT NOT NULL UNIQUE,
terminal_evidence_kind TEXT NOT NULL,
terminal_evidence_digest TEXT NOT NULL UNIQUE,
final_step_run_digest TEXT NOT NULL,
run_status TEXT NOT NULL,
event_id TEXT NOT NULL UNIQUE,
final_run_version INTEGER NOT NULL,
final_run_event_sequence INTEGER NOT NULL,
finalized_at_ms INTEGER NOT NULL,
receipt_digest TEXT NOT NULL UNIQUE,
receipt_json TEXT NOT NULL,
CONSTRAINT ql3_ai_prompt_finalization_admission_fk
FOREIGN KEY (request_id) REFERENCES "ModelInvocationPromptAdmissions" (
request_id
) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT ql3_ai_prompt_finalization_invocation_fk
FOREIGN KEY (invocation_id) REFERENCES "ModelInvocationPromptAdmissions" (
invocation_id
) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT ql3_ai_prompt_finalization_plan_fk
FOREIGN KEY (plan_digest) REFERENCES "ModelInvocationPromptAdmissions" (
plan_digest
) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT ql3_ai_prompt_finalization_run_fk
FOREIGN KEY (run_id) REFERENCES "Runs" (id)
ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT ql3_ai_prompt_finalization_step_fk
FOREIGN KEY (run_id, step_run_id) REFERENCES "StepRuns" (run_id, id)
ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT ql3_ai_prompt_finalization_event_fk
FOREIGN KEY (event_id) REFERENCES "RunEvents" (id)
ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT ql3_ai_prompt_finalization_identity_check CHECK (
length(request_id) BETWEEN 1 AND 128 AND
length(invocation_id) BETWEEN 1 AND 128 AND
length(run_id) BETWEEN 1 AND 128 AND
length(step_run_id) BETWEEN 1 AND 128 AND
length(event_id) BETWEEN 1 AND 128 AND
terminal_evidence_kind IN ('completion', 'resolution') AND
run_status IN ('succeeded', 'failed', 'cancelled', 'timed_out') AND
final_run_version BETWEEN 3 AND 2147483647 AND
final_run_event_sequence = final_run_version AND
finalized_at_ms >= 0
),
CONSTRAINT ql3_ai_prompt_finalization_digest_check CHECK (
length(plan_digest) = 64 AND
plan_digest NOT GLOB '*[^0-9a-f]*' AND
length(terminal_evidence_digest) = 64 AND
terminal_evidence_digest NOT GLOB '*[^0-9a-f]*' AND
length(final_step_run_digest) = 64 AND
final_step_run_digest NOT GLOB '*[^0-9a-f]*' AND
length(receipt_digest) = 64 AND
receipt_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_ai_prompt_finalization_receipt_json_check CHECK (
length(CAST(receipt_json AS BLOB)) BETWEEN 2 AND 16384 AND
json_valid(receipt_json) AND json_type(receipt_json) = 'object' AND
json_extract(receipt_json, '$.schema') =
'qinglong/plugin-package-prompt-finalization-receipt@v1' AND
json_extract(receipt_json, '$.requestId') = request_id AND
json_extract(receipt_json, '$.invocationId') = invocation_id AND
json_extract(receipt_json, '$.planDigest') = plan_digest AND
json_extract(receipt_json, '$.runId') = run_id AND
json_extract(receipt_json, '$.stepRunId') = step_run_id AND
json_extract(receipt_json, '$.terminalEvidenceKind') =
terminal_evidence_kind AND
json_extract(receipt_json, '$.terminalEvidenceDigest') =
terminal_evidence_digest AND
json_extract(receipt_json, '$.finalStepRunDigest') =
final_step_run_digest AND
json_extract(receipt_json, '$.runStatus') = run_status AND
json_extract(receipt_json, '$.eventId') = event_id AND
json_extract(receipt_json, '$.finalRunVersion') = final_run_version AND
json_extract(receipt_json, '$.finalRunEventSequence') =
final_run_event_sequence AND
json_extract(receipt_json, '$.finalizedAtMs') = finalized_at_ms AND
json_extract(receipt_json, '$.receiptDigest') = receipt_digest
)
)`;
const localPluginPackagePromptFinalizationMigration =
defineSqlMigration<LocalMigrationContext>(
LOCAL_PLUGIN_PACKAGE_PROMPT_FINALIZATION_MIGRATION_ID,
[
LOCAL_PLUGIN_PACKAGE_PROMPT_FINALIZATION_TABLE_SQL,
`CREATE UNIQUE INDEX ql3_ai_prompt_finalization_run_step_uidx
ON "ModelInvocationPromptFinalizations" (run_id, step_run_id)`,
`CREATE INDEX ql3_ai_prompt_finalization_status_idx
ON "ModelInvocationPromptFinalizations"
(run_status, finalized_at_ms, request_id)`,
],
(context, statement) => context.client.exec(statement),
);
const LOCAL_PLUGIN_PACKAGE_PROMPT_OUTPUT_ARTIFACT_TABLE_SQL = `
CREATE TABLE "ModelInvocationPromptOutputArtifacts" (
artifact_id TEXT PRIMARY KEY NOT NULL,
project_id TEXT NOT NULL,
run_id TEXT NOT NULL,
step_run_id TEXT NOT NULL,
invocation_id TEXT NOT NULL UNIQUE,
requested_by_type TEXT NOT NULL,
requested_by_id TEXT NOT NULL,
provider TEXT NOT NULL,
model TEXT NOT NULL,
content_digest TEXT NOT NULL,
output_bytes INTEGER NOT NULL,
retention_policy_revision TEXT NOT NULL,
retention_ms INTEGER NOT NULL,
retention_policy_digest TEXT NOT NULL,
retention_eligible_at_ms INTEGER NOT NULL,
key_id TEXT NOT NULL,
algorithm TEXT NOT NULL,
plaintext_bytes INTEGER NOT NULL,
sealed_at_ms INTEGER NOT NULL,
artifact_digest TEXT NOT NULL UNIQUE,
artifact_json TEXT NOT NULL,
CONSTRAINT ql3_ai_prompt_output_artifact_admission_fk
FOREIGN KEY (invocation_id)
REFERENCES "ModelInvocationPromptAdmissions" (invocation_id)
ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT ql3_ai_prompt_output_artifact_start_fk
FOREIGN KEY (invocation_id)
REFERENCES "ModelInvocationStarts" (invocation_id)
ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT ql3_ai_prompt_output_artifact_step_fk
FOREIGN KEY (run_id, step_run_id) REFERENCES "StepRuns" (run_id, id)
ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT ql3_ai_prompt_output_artifact_identity_check CHECK (
length(artifact_id) BETWEEN 1 AND 128 AND
length(project_id) BETWEEN 1 AND 128 AND
length(run_id) BETWEEN 1 AND 128 AND
length(step_run_id) BETWEEN 1 AND 128 AND
length(invocation_id) BETWEEN 1 AND 128 AND
length(requested_by_type) BETWEEN 1 AND 32 AND
length(requested_by_id) BETWEEN 1 AND 128 AND
length(provider) BETWEEN 1 AND 256 AND
length(model) BETWEEN 1 AND 256 AND
length(retention_policy_revision) BETWEEN 1 AND 128 AND
length(key_id) BETWEEN 1 AND 128 AND
algorithm = 'aes-256-gcm'
),
CONSTRAINT ql3_ai_prompt_output_artifact_value_check CHECK (
output_bytes BETWEEN 0 AND 1048576 AND
plaintext_bytes BETWEEN 1 AND 1052672 AND
retention_ms BETWEEN 3600000 AND 31536000000 AND
sealed_at_ms >= 0 AND
retention_eligible_at_ms = sealed_at_ms + retention_ms AND
length(content_digest) = 64 AND
content_digest NOT GLOB '*[^0-9a-f]*' AND
length(retention_policy_digest) = 64 AND
retention_policy_digest NOT GLOB '*[^0-9a-f]*' AND
length(artifact_digest) = 64 AND
artifact_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_ai_prompt_output_artifact_json_check CHECK (
length(CAST(artifact_json AS BLOB)) BETWEEN 2 AND 1572864 AND
json_valid(artifact_json) AND json_type(artifact_json) = 'object' AND
json_extract(artifact_json, '$.schema') =
'qinglong/plugin-package-prompt-output-artifact@v1' AND
json_extract(artifact_json, '$.artifactId') = artifact_id AND
json_extract(artifact_json, '$.projectId') = project_id AND
json_extract(artifact_json, '$.runId') = run_id AND
json_extract(artifact_json, '$.stepRunId') = step_run_id AND
json_extract(artifact_json, '$.invocationId') = invocation_id AND
json_extract(artifact_json, '$.requestedBy.type') = requested_by_type AND
json_extract(artifact_json, '$.requestedBy.id') = requested_by_id AND
json_extract(artifact_json, '$.provider') = provider AND
json_extract(artifact_json, '$.model') = model AND
json_extract(artifact_json, '$.contentDigest') = content_digest AND
json_extract(artifact_json, '$.outputBytes') = output_bytes AND
json_extract(artifact_json, '$.retentionPolicy.revision') =
retention_policy_revision AND
json_extract(artifact_json, '$.retentionPolicy.retentionMs') =
retention_ms AND
json_extract(artifact_json, '$.retentionPolicyDigest') =
retention_policy_digest AND
json_extract(artifact_json, '$.retentionEligibleAtMs') =
retention_eligible_at_ms AND
json_extract(artifact_json, '$.keyId') = key_id AND
json_extract(artifact_json, '$.algorithm') = algorithm AND
json_extract(artifact_json, '$.plaintextBytes') = plaintext_bytes AND
json_extract(artifact_json, '$.sealedAtMs') = sealed_at_ms AND
json_extract(artifact_json, '$.artifactDigest') = artifact_digest
)
)`;
const localPluginPackagePromptOutputArtifactMigration =
defineSqlMigration<LocalMigrationContext>(
LOCAL_PLUGIN_PACKAGE_PROMPT_OUTPUT_ARTIFACT_MIGRATION_ID,
[
LOCAL_PLUGIN_PACKAGE_PROMPT_OUTPUT_ARTIFACT_TABLE_SQL,
`CREATE INDEX ql3_ai_prompt_output_artifact_retention_idx
ON "ModelInvocationPromptOutputArtifacts"
(retention_eligible_at_ms, artifact_id)`,
`CREATE INDEX ql3_ai_prompt_output_artifact_project_run_idx
ON "ModelInvocationPromptOutputArtifacts"
(project_id, run_id, artifact_id)`,
],
(context, statement) => context.client.exec(statement),
);
const LOCAL_PLUGIN_PACKAGE_PROMPT_OUTPUT_TOMBSTONE_TABLE_SQL = `
CREATE TABLE "ModelInvocationPromptOutputArtifactTombstones" (
artifact_id TEXT PRIMARY KEY NOT NULL,
project_id TEXT NOT NULL,
run_id TEXT NOT NULL,
step_run_id TEXT NOT NULL,
invocation_id TEXT NOT NULL UNIQUE,
artifact_digest TEXT NOT NULL UNIQUE,
retention_policy_digest TEXT NOT NULL,
retention_eligible_at_ms INTEGER NOT NULL,
key_id TEXT NOT NULL,
tombstoned_at_ms INTEGER NOT NULL,
tombstone_digest TEXT NOT NULL UNIQUE,
tombstone_json TEXT NOT NULL,
CONSTRAINT ql3_ai_prompt_output_tombstone_admission_fk
FOREIGN KEY (invocation_id)
REFERENCES "ModelInvocationPromptAdmissions" (invocation_id)
ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT ql3_ai_prompt_output_tombstone_start_fk
FOREIGN KEY (invocation_id)
REFERENCES "ModelInvocationStarts" (invocation_id)
ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT ql3_ai_prompt_output_tombstone_step_fk
FOREIGN KEY (run_id, step_run_id) REFERENCES "StepRuns" (run_id, id)
ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT ql3_ai_prompt_output_tombstone_identity_check CHECK (
length(artifact_id) BETWEEN 1 AND 128 AND
length(project_id) BETWEEN 1 AND 128 AND
length(run_id) BETWEEN 1 AND 128 AND
length(step_run_id) BETWEEN 1 AND 128 AND
length(invocation_id) BETWEEN 1 AND 128 AND
length(key_id) BETWEEN 1 AND 128
),
CONSTRAINT ql3_ai_prompt_output_tombstone_value_check CHECK (
retention_eligible_at_ms >= 0 AND
tombstoned_at_ms >= retention_eligible_at_ms AND
length(artifact_digest) = 64 AND
artifact_digest NOT GLOB '*[^0-9a-f]*' AND
length(retention_policy_digest) = 64 AND
retention_policy_digest NOT GLOB '*[^0-9a-f]*' AND
length(tombstone_digest) = 64 AND
tombstone_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_ai_prompt_output_tombstone_json_check CHECK (
length(CAST(tombstone_json AS BLOB)) BETWEEN 2 AND 8192 AND
json_valid(tombstone_json) AND json_type(tombstone_json) = 'object' AND
json_extract(tombstone_json, '$.schema') =
'qinglong/plugin-package-prompt-output-artifact-tombstone@v1' AND
json_extract(tombstone_json, '$.reference.artifactId') = artifact_id AND
json_extract(tombstone_json, '$.reference.projectId') = project_id AND
json_extract(tombstone_json, '$.reference.runId') = run_id AND
json_extract(tombstone_json, '$.reference.stepRunId') = step_run_id AND
json_extract(tombstone_json, '$.reference.invocationId') = invocation_id AND
json_extract(tombstone_json, '$.reference.artifactDigest') = artifact_digest AND
json_extract(tombstone_json, '$.reference.retentionPolicyDigest') =
retention_policy_digest AND
json_extract(tombstone_json, '$.reference.retentionEligibleAtMs') =
retention_eligible_at_ms AND
json_extract(tombstone_json, '$.reference.keyId') = key_id AND
json_extract(tombstone_json, '$.tombstonedAtMs') = tombstoned_at_ms AND
json_extract(tombstone_json, '$.tombstoneDigest') = tombstone_digest
)
)`;
const localPluginPackagePromptOutputTombstoneMigration =
defineSqlMigration<LocalMigrationContext>(
LOCAL_PLUGIN_PACKAGE_PROMPT_OUTPUT_TOMBSTONE_MIGRATION_ID,
[
LOCAL_PLUGIN_PACKAGE_PROMPT_OUTPUT_TOMBSTONE_TABLE_SQL,
`CREATE INDEX ql3_ai_prompt_output_tombstone_time_idx
ON "ModelInvocationPromptOutputArtifactTombstones"
(tombstoned_at_ms, artifact_id)`,
`CREATE INDEX ql3_ai_prompt_output_tombstone_project_run_idx
ON "ModelInvocationPromptOutputArtifactTombstones"
(project_id, run_id, artifact_id)`,
],
(context, statement) => context.client.exec(statement),
);
const LOCAL_PLUGIN_PACKAGE_PROMPT_OUTPUT_KEY_RETIREMENT_PREPARATION_TABLE_SQL = `
CREATE TABLE "ModelInvocationPromptOutputKeyRetirementPreparations" (
key_id TEXT PRIMARY KEY NOT NULL,
retirement_id TEXT NOT NULL UNIQUE,
request_id TEXT NOT NULL UNIQUE,
mutation_id TEXT NOT NULL UNIQUE,
catalog_digest TEXT NOT NULL,
material_proof TEXT NOT NULL,
prepared_at_ms INTEGER NOT NULL,
preparation_digest TEXT NOT NULL UNIQUE,
preparation_json TEXT NOT NULL,
CONSTRAINT ql3_ai_prompt_output_key_retirement_preparation_identity_check CHECK (
length(key_id) BETWEEN 1 AND 128 AND
length(retirement_id) BETWEEN 1 AND 128 AND
length(request_id) BETWEEN 1 AND 128 AND
length(mutation_id) BETWEEN 1 AND 128
),
CONSTRAINT ql3_ai_prompt_output_key_retirement_preparation_value_check CHECK (
prepared_at_ms >= 0 AND
length(catalog_digest) = 64 AND catalog_digest NOT GLOB '*[^0-9a-f]*' AND
length(material_proof) = 64 AND material_proof NOT GLOB '*[^0-9a-f]*' AND
length(preparation_digest) = 64 AND
preparation_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_ai_prompt_output_key_retirement_preparation_json_check CHECK (
length(CAST(preparation_json AS BLOB)) BETWEEN 2 AND 8192 AND
json_valid(preparation_json) AND json_type(preparation_json) = 'object' AND
json_extract(preparation_json, '$.schema') =
'qinglong/plugin-package-prompt-output-key-retirement-preparation@v1' AND
json_extract(preparation_json, '$.keyId') = key_id AND
json_extract(preparation_json, '$.retirementId') = retirement_id AND
json_extract(preparation_json, '$.requestId') = request_id AND
json_extract(preparation_json, '$.mutationId') = mutation_id AND
json_extract(preparation_json, '$.catalogDigest') = catalog_digest AND
json_extract(preparation_json, '$.materialProof') = material_proof AND
json_extract(preparation_json, '$.preparedAtMs') = prepared_at_ms AND
json_extract(preparation_json, '$.preparationDigest') = preparation_digest
)
)`;
const LOCAL_PLUGIN_PACKAGE_PROMPT_OUTPUT_KEY_RETIREMENT_COMPLETION_TABLE_SQL = `
CREATE TABLE "ModelInvocationPromptOutputKeyRetirementCompletions" (
key_id TEXT PRIMARY KEY NOT NULL,
retirement_id TEXT NOT NULL UNIQUE,
request_id TEXT NOT NULL UNIQUE,
mutation_id TEXT NOT NULL UNIQUE,
preparation_digest TEXT NOT NULL UNIQUE,
retired_catalog_digest TEXT NOT NULL,
absence_proof TEXT NOT NULL,
completed_at_ms INTEGER NOT NULL,
completion_digest TEXT NOT NULL UNIQUE,
completion_json TEXT NOT NULL,
CONSTRAINT ql3_ai_prompt_output_key_retirement_completion_preparation_fk
FOREIGN KEY (key_id, preparation_digest)
REFERENCES "ModelInvocationPromptOutputKeyRetirementPreparations"
(key_id, preparation_digest)
ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT ql3_ai_prompt_output_key_retirement_completion_identity_check CHECK (
length(key_id) BETWEEN 1 AND 128 AND
length(retirement_id) BETWEEN 1 AND 128 AND
length(request_id) BETWEEN 1 AND 128 AND
length(mutation_id) BETWEEN 1 AND 128
),
CONSTRAINT ql3_ai_prompt_output_key_retirement_completion_value_check CHECK (
completed_at_ms >= 0 AND
length(preparation_digest) = 64 AND
preparation_digest NOT GLOB '*[^0-9a-f]*' AND
length(retired_catalog_digest) = 64 AND
retired_catalog_digest NOT GLOB '*[^0-9a-f]*' AND
length(absence_proof) = 64 AND absence_proof NOT GLOB '*[^0-9a-f]*' AND
length(completion_digest) = 64 AND completion_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_ai_prompt_output_key_retirement_completion_json_check CHECK (
length(CAST(completion_json AS BLOB)) BETWEEN 2 AND 8192 AND
json_valid(completion_json) AND json_type(completion_json) = 'object' AND
json_extract(completion_json, '$.schema') =
'qinglong/plugin-package-prompt-output-key-retirement-completion@v1' AND
json_extract(completion_json, '$.keyId') = key_id AND
json_extract(completion_json, '$.retirementId') = retirement_id AND
json_extract(completion_json, '$.requestId') = request_id AND
json_extract(completion_json, '$.mutationId') = mutation_id AND
json_extract(completion_json, '$.preparationDigest') = preparation_digest AND
json_extract(completion_json, '$.retiredCatalogDigest') =
retired_catalog_digest AND
json_extract(completion_json, '$.absenceProof') = absence_proof AND
json_extract(completion_json, '$.completedAtMs') = completed_at_ms AND
json_extract(completion_json, '$.completionDigest') = completion_digest
)
)`;
const localPluginPackagePromptOutputKeyRetirementMigration =
defineSqlMigration<LocalMigrationContext>(
LOCAL_PLUGIN_PACKAGE_PROMPT_OUTPUT_KEY_RETIREMENT_MIGRATION_ID,
[
LOCAL_PLUGIN_PACKAGE_PROMPT_OUTPUT_KEY_RETIREMENT_PREPARATION_TABLE_SQL,
`CREATE UNIQUE INDEX ql3_ai_prompt_output_key_retirement_preparation_fk_uidx
ON "ModelInvocationPromptOutputKeyRetirementPreparations"
(key_id, preparation_digest)`,
LOCAL_PLUGIN_PACKAGE_PROMPT_OUTPUT_KEY_RETIREMENT_COMPLETION_TABLE_SQL,
`CREATE INDEX ql3_ai_prompt_output_key_retirement_completion_time_idx
ON "ModelInvocationPromptOutputKeyRetirementCompletions"
(completed_at_ms, key_id)`,
],
(context, statement) => context.client.exec(statement),
);
export const sqlitePromptMigrations = Object.freeze([
localPluginPackagePromptAdmissionMigration,
localPluginPackagePromptFinalizationMigration,
localPluginPackagePromptOutputArtifactMigration,
localPluginPackagePromptOutputTombstoneMigration,
localPluginPackagePromptOutputKeyRetirementMigration,
]);
@@ -0,0 +1,374 @@
import type { LocalMigrationContext } from './context';
import {
LOCAL_MODEL_INVOCATION_USAGE_MIGRATION_ID,
LOCAL_MODEL_INVOCATION_QUOTA_MIGRATION_ID,
LOCAL_MODEL_INVOCATION_PRICING_MIGRATION_ID,
} from '../identities';
import { defineSqlMigration } from '../shared';
const LOCAL_USAGE_LEDGER_TABLE_SQL = `
CREATE TABLE "ModelInvocationUsageLedger" (
invocation_id TEXT PRIMARY KEY NOT NULL,
project_id TEXT NOT NULL,
run_id TEXT NOT NULL,
step_run_id TEXT NOT NULL,
trace_id TEXT NOT NULL,
provider TEXT NOT NULL,
model TEXT NOT NULL,
policy_revision TEXT NOT NULL,
completion_digest TEXT NOT NULL,
outcome TEXT NOT NULL,
settled_at_ms INTEGER NOT NULL,
input_bytes INTEGER NOT NULL,
output_bytes INTEGER NOT NULL,
input_tokens INTEGER NOT NULL,
output_tokens INTEGER NOT NULL,
total_tokens INTEGER NOT NULL,
cost_micros INTEGER,
ledger_digest TEXT NOT NULL,
record_json TEXT NOT NULL,
FOREIGN KEY (invocation_id, completion_digest)
REFERENCES "ModelInvocationCompletions"
(invocation_id, completion_digest) ON DELETE RESTRICT,
CONSTRAINT ql3_model_invocation_usage_identity_check CHECK (
length(invocation_id) BETWEEN 1 AND 128 AND
length(project_id) BETWEEN 1 AND 128 AND
length(run_id) BETWEEN 1 AND 128 AND
length(step_run_id) BETWEEN 1 AND 128 AND
length(trace_id) BETWEEN 1 AND 128 AND
length(provider) BETWEEN 1 AND 128 AND
length(model) BETWEEN 1 AND 128 AND
length(policy_revision) BETWEEN 1 AND 128
),
CONSTRAINT ql3_model_invocation_usage_value_check CHECK (
outcome IN ('succeeded', 'failed', 'timed_out', 'outcome_unknown') AND
settled_at_ms >= 0 AND
input_bytes BETWEEN 1 AND 262144 AND
output_bytes BETWEEN 0 AND 1048576 AND
input_tokens >= 0 AND output_tokens >= 0 AND
total_tokens = input_tokens + output_tokens AND
(cost_micros IS NULL OR cost_micros >= 0) AND
length(completion_digest) = 64 AND
completion_digest NOT GLOB '*[^0-9a-f]*' AND
length(ledger_digest) = 64 AND
ledger_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_model_invocation_usage_json_check CHECK (
length(CAST(record_json AS BLOB)) BETWEEN 2 AND 24576 AND
json_valid(record_json) AND json_type(record_json) = 'object' AND
json_extract(record_json, '$.schema') =
'qinglong/model-invocation-usage-ledger@v1' AND
json_extract(record_json, '$.invocationId') = invocation_id AND
json_extract(record_json, '$.projectId') = project_id AND
json_extract(record_json, '$.runId') = run_id AND
json_extract(record_json, '$.stepRunId') = step_run_id AND
json_extract(record_json, '$.traceId') = trace_id AND
json_extract(record_json, '$.provider') = provider AND
json_extract(record_json, '$.model') = model AND
json_extract(record_json, '$.policyRevision') = policy_revision AND
json_extract(record_json, '$.completionDigest') = completion_digest AND
json_extract(record_json, '$.outcome') = outcome AND
json_extract(record_json, '$.settledAtMs') = settled_at_ms AND
json_extract(record_json, '$.inputBytes') = input_bytes AND
json_extract(record_json, '$.outputBytes') = output_bytes AND
json_extract(record_json, '$.inputTokens') = input_tokens AND
json_extract(record_json, '$.outputTokens') = output_tokens AND
json_extract(record_json, '$.totalTokens') = total_tokens AND
((cost_micros IS NULL AND
json_type(record_json, '$.costMicros') = 'null') OR
json_extract(record_json, '$.costMicros') = cost_micros) AND
json_extract(record_json, '$.ledgerDigest') = ledger_digest
)
)`;
const localUsageMigration = defineSqlMigration<LocalMigrationContext>(
LOCAL_MODEL_INVOCATION_USAGE_MIGRATION_ID,
[
`CREATE UNIQUE INDEX ql3_model_invocation_completions_identity_digest_uidx
ON "ModelInvocationCompletions" (invocation_id, completion_digest)`,
LOCAL_USAGE_LEDGER_TABLE_SQL,
`CREATE INDEX ql3_model_invocation_usage_project_time_idx
ON "ModelInvocationUsageLedger"
(project_id, settled_at_ms, invocation_id)`,
`CREATE UNIQUE INDEX ql3_model_invocation_usage_digest_uidx
ON "ModelInvocationUsageLedger" (ledger_digest)`,
],
(context, statement) => context.client.exec(statement),
);
const LOCAL_QUOTA_RESERVATION_TABLE_SQL = `
CREATE TABLE "ModelInvocationQuotaReservations" (
invocation_id TEXT PRIMARY KEY NOT NULL,
project_id TEXT NOT NULL,
model_policy_revision TEXT NOT NULL,
quota_policy_revision TEXT NOT NULL,
window_ms INTEGER NOT NULL,
window_start_ms INTEGER NOT NULL,
window_end_ms INTEGER NOT NULL,
max_invocations INTEGER NOT NULL,
max_tokens INTEGER NOT NULL,
max_cost_micros INTEGER,
reserved_tokens INTEGER NOT NULL,
reserved_cost_micros INTEGER,
reserved_at_ms INTEGER NOT NULL,
admission_digest TEXT NOT NULL,
reservation_digest TEXT NOT NULL,
record_json TEXT NOT NULL,
FOREIGN KEY (invocation_id)
REFERENCES "ModelInvocationStarts" (invocation_id) ON DELETE RESTRICT,
CONSTRAINT ql3_model_invocation_quota_reservation_value_check CHECK (
window_ms IN (60000, 3600000, 86400000) AND
window_start_ms >= 0 AND window_end_ms = window_start_ms + window_ms AND
reserved_at_ms >= window_start_ms AND reserved_at_ms < window_end_ms AND
max_invocations BETWEEN 1 AND 100000 AND
max_tokens BETWEEN 1 AND 1000000000000 AND
(max_cost_micros IS NULL OR
max_cost_micros BETWEEN 0 AND 1000000000000000) AND
reserved_tokens BETWEEN 1 AND 1000000000000 AND
(reserved_cost_micros IS NULL OR
reserved_cost_micros BETWEEN 0 AND 1000000000000000) AND
(max_cost_micros IS NULL OR reserved_cost_micros IS NOT NULL) AND
length(admission_digest) = 64 AND
admission_digest NOT GLOB '*[^0-9a-f]*' AND
length(reservation_digest) = 64 AND
reservation_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_model_invocation_quota_reservation_json_check CHECK (
length(CAST(record_json AS BLOB)) BETWEEN 2 AND 24576 AND
json_valid(record_json) AND json_type(record_json) = 'object' AND
json_extract(record_json, '$.schema') =
'qinglong/model-invocation-quota-reservation@v1' AND
json_extract(record_json, '$.invocationId') = invocation_id AND
json_extract(record_json, '$.projectId') = project_id AND
json_extract(record_json, '$.modelPolicyRevision') =
model_policy_revision AND
json_extract(record_json, '$.quotaPolicyRevision') =
quota_policy_revision AND
json_extract(record_json, '$.windowMs') = window_ms AND
json_extract(record_json, '$.windowStartMs') = window_start_ms AND
json_extract(record_json, '$.windowEndMs') = window_end_ms AND
json_extract(record_json, '$.maxInvocations') = max_invocations AND
json_extract(record_json, '$.maxTokens') = max_tokens AND
((max_cost_micros IS NULL AND
json_type(record_json, '$.maxCostMicros') = 'null') OR
json_extract(record_json, '$.maxCostMicros') = max_cost_micros) AND
json_extract(record_json, '$.reservedTokens') = reserved_tokens AND
((reserved_cost_micros IS NULL AND
json_type(record_json, '$.reservedCostMicros') = 'null') OR
json_extract(record_json, '$.reservedCostMicros') =
reserved_cost_micros) AND
json_extract(record_json, '$.reservedAtMs') = reserved_at_ms AND
json_extract(record_json, '$.admissionDigest') = admission_digest AND
json_extract(record_json, '$.reservationDigest') = reservation_digest
)
)`;
const LOCAL_QUOTA_SETTLEMENT_TABLE_SQL = `
CREATE TABLE "ModelInvocationQuotaSettlements" (
invocation_id TEXT PRIMARY KEY NOT NULL,
project_id TEXT NOT NULL,
reservation_digest TEXT NOT NULL,
completion_digest TEXT NOT NULL,
effective_tokens INTEGER NOT NULL,
effective_cost_micros INTEGER,
retained_token_reservation INTEGER NOT NULL,
retained_cost_reservation INTEGER NOT NULL,
settled_at_ms INTEGER NOT NULL,
settlement_digest TEXT NOT NULL,
record_json TEXT NOT NULL,
FOREIGN KEY (invocation_id)
REFERENCES "ModelInvocationQuotaReservations" (invocation_id)
ON DELETE RESTRICT,
FOREIGN KEY (invocation_id, completion_digest)
REFERENCES "ModelInvocationCompletions"
(invocation_id, completion_digest) ON DELETE RESTRICT,
CONSTRAINT ql3_model_invocation_quota_settlement_value_check CHECK (
effective_tokens BETWEEN 0 AND 1000000000000 AND
(effective_cost_micros IS NULL OR
effective_cost_micros BETWEEN 0 AND 1000000000000000) AND
retained_token_reservation IN (0, 1) AND
retained_cost_reservation IN (0, 1) AND
settled_at_ms >= 0 AND
length(reservation_digest) = 64 AND
reservation_digest NOT GLOB '*[^0-9a-f]*' AND
length(completion_digest) = 64 AND
completion_digest NOT GLOB '*[^0-9a-f]*' AND
length(settlement_digest) = 64 AND
settlement_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_model_invocation_quota_settlement_json_check CHECK (
length(CAST(record_json AS BLOB)) BETWEEN 2 AND 24576 AND
json_valid(record_json) AND json_type(record_json) = 'object' AND
json_extract(record_json, '$.schema') =
'qinglong/model-invocation-quota-settlement@v1' AND
json_extract(record_json, '$.invocationId') = invocation_id AND
json_extract(record_json, '$.projectId') = project_id AND
json_extract(record_json, '$.reservationDigest') =
reservation_digest AND
json_extract(record_json, '$.completionDigest') = completion_digest AND
json_extract(record_json, '$.effectiveTokens') = effective_tokens AND
((effective_cost_micros IS NULL AND
json_type(record_json, '$.effectiveCostMicros') = 'null') OR
json_extract(record_json, '$.effectiveCostMicros') =
effective_cost_micros) AND
json_extract(record_json, '$.retainedTokenReservation') =
retained_token_reservation AND
json_extract(record_json, '$.retainedCostReservation') =
retained_cost_reservation AND
json_extract(record_json, '$.settledAtMs') = settled_at_ms AND
json_extract(record_json, '$.settlementDigest') = settlement_digest
)
)`;
const localQuotaMigration = defineSqlMigration<LocalMigrationContext>(
LOCAL_MODEL_INVOCATION_QUOTA_MIGRATION_ID,
[
LOCAL_QUOTA_RESERVATION_TABLE_SQL,
`CREATE INDEX ql3_model_invocation_quota_reservation_window_idx
ON "ModelInvocationQuotaReservations"
(project_id, window_start_ms, invocation_id)`,
`CREATE UNIQUE INDEX ql3_model_invocation_quota_reservation_digest_uidx
ON "ModelInvocationQuotaReservations" (reservation_digest)`,
LOCAL_QUOTA_SETTLEMENT_TABLE_SQL,
`CREATE UNIQUE INDEX ql3_model_invocation_quota_settlement_digest_uidx
ON "ModelInvocationQuotaSettlements" (settlement_digest)`,
],
(context, statement) => context.client.exec(statement),
);
const LOCAL_PRICE_QUOTE_TABLE_SQL = `
CREATE TABLE "ModelInvocationPriceQuotes" (
invocation_id TEXT PRIMARY KEY NOT NULL,
project_id TEXT NOT NULL,
model_policy_revision TEXT NOT NULL,
provider TEXT NOT NULL,
model TEXT NOT NULL,
price_revision TEXT NOT NULL,
currency TEXT NOT NULL,
input_micros_per_million_tokens INTEGER NOT NULL,
output_micros_per_million_tokens INTEGER NOT NULL,
max_total_tokens INTEGER NOT NULL,
max_output_tokens INTEGER NOT NULL,
reserved_cost_micros INTEGER NOT NULL,
catalog_digest TEXT NOT NULL,
quote_digest TEXT NOT NULL,
record_json TEXT NOT NULL,
FOREIGN KEY (invocation_id)
REFERENCES "ModelInvocationStarts" (invocation_id) ON DELETE RESTRICT,
CONSTRAINT ql3_model_invocation_price_quote_identity_check CHECK (
length(project_id) BETWEEN 1 AND 128 AND
length(model_policy_revision) BETWEEN 1 AND 128 AND
length(provider) BETWEEN 1 AND 128 AND
length(model) BETWEEN 1 AND 128 AND
length(price_revision) BETWEEN 1 AND 128
),
CONSTRAINT ql3_model_invocation_price_quote_value_check CHECK (
currency = 'USD' AND
input_micros_per_million_tokens BETWEEN 0 AND 1000000000000 AND
output_micros_per_million_tokens BETWEEN 0 AND 1000000000000 AND
max_total_tokens BETWEEN 1 AND 1000000000000 AND
max_output_tokens BETWEEN 1 AND max_total_tokens AND
reserved_cost_micros BETWEEN 0 AND 1000000000000000 AND
length(catalog_digest) = 64 AND
catalog_digest NOT GLOB '*[^0-9a-f]*' AND
length(quote_digest) = 64 AND
quote_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_model_invocation_price_quote_json_check CHECK (
length(CAST(record_json AS BLOB)) BETWEEN 2 AND 24576 AND
json_valid(record_json) AND json_type(record_json) = 'object' AND
json_extract(record_json, '$.schema') =
'qinglong/model-invocation-price-quote@v1' AND
json_extract(record_json, '$.invocationId') = invocation_id AND
json_extract(record_json, '$.projectId') = project_id AND
json_extract(record_json, '$.modelPolicyRevision') =
model_policy_revision AND
json_extract(record_json, '$.provider') = provider AND
json_extract(record_json, '$.model') = model AND
json_extract(record_json, '$.priceRevision') = price_revision AND
json_extract(record_json, '$.currency') = currency AND
json_extract(record_json, '$.inputMicrosPerMillionTokens') =
input_micros_per_million_tokens AND
json_extract(record_json, '$.outputMicrosPerMillionTokens') =
output_micros_per_million_tokens AND
json_extract(record_json, '$.maxTotalTokens') = max_total_tokens AND
json_extract(record_json, '$.maxOutputTokens') = max_output_tokens AND
json_extract(record_json, '$.reservedCostMicros') =
reserved_cost_micros AND
json_extract(record_json, '$.catalogDigest') = catalog_digest AND
json_extract(record_json, '$.quoteDigest') = quote_digest
)
)`;
const LOCAL_PRICE_SETTLEMENT_TABLE_SQL = `
CREATE TABLE "ModelInvocationPriceSettlements" (
invocation_id TEXT PRIMARY KEY NOT NULL,
project_id TEXT NOT NULL,
quote_digest TEXT NOT NULL,
completion_digest TEXT NOT NULL,
currency TEXT NOT NULL,
input_tokens INTEGER NOT NULL,
output_tokens INTEGER NOT NULL,
cost_micros INTEGER NOT NULL,
settled_at_ms INTEGER NOT NULL,
settlement_digest TEXT NOT NULL,
record_json TEXT NOT NULL,
FOREIGN KEY (invocation_id, quote_digest)
REFERENCES "ModelInvocationPriceQuotes"
(invocation_id, quote_digest) ON DELETE RESTRICT,
FOREIGN KEY (invocation_id, completion_digest)
REFERENCES "ModelInvocationCompletions"
(invocation_id, completion_digest) ON DELETE RESTRICT,
CONSTRAINT ql3_model_invocation_price_settlement_value_check CHECK (
currency = 'USD' AND
input_tokens >= 0 AND output_tokens >= 0 AND
cost_micros BETWEEN 0 AND 1000000000000000 AND
settled_at_ms >= 0 AND
length(quote_digest) = 64 AND
quote_digest NOT GLOB '*[^0-9a-f]*' AND
length(completion_digest) = 64 AND
completion_digest NOT GLOB '*[^0-9a-f]*' AND
length(settlement_digest) = 64 AND
settlement_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_model_invocation_price_settlement_json_check CHECK (
length(CAST(record_json AS BLOB)) BETWEEN 2 AND 24576 AND
json_valid(record_json) AND json_type(record_json) = 'object' AND
json_extract(record_json, '$.schema') =
'qinglong/model-invocation-price-settlement@v1' AND
json_extract(record_json, '$.invocationId') = invocation_id AND
json_extract(record_json, '$.projectId') = project_id AND
json_extract(record_json, '$.quoteDigest') = quote_digest AND
json_extract(record_json, '$.completionDigest') =
completion_digest AND
json_extract(record_json, '$.currency') = currency AND
json_extract(record_json, '$.inputTokens') = input_tokens AND
json_extract(record_json, '$.outputTokens') = output_tokens AND
json_extract(record_json, '$.costMicros') = cost_micros AND
json_extract(record_json, '$.settledAtMs') = settled_at_ms AND
json_extract(record_json, '$.settlementDigest') = settlement_digest
)
)`;
const localPricingMigration = defineSqlMigration<LocalMigrationContext>(
LOCAL_MODEL_INVOCATION_PRICING_MIGRATION_ID,
[
LOCAL_PRICE_QUOTE_TABLE_SQL,
`CREATE UNIQUE INDEX ql3_model_invocation_price_quote_identity_digest_uidx
ON "ModelInvocationPriceQuotes" (invocation_id, quote_digest)`,
`CREATE UNIQUE INDEX ql3_model_invocation_price_quote_digest_uidx
ON "ModelInvocationPriceQuotes" (quote_digest)`,
LOCAL_PRICE_SETTLEMENT_TABLE_SQL,
`CREATE UNIQUE INDEX ql3_model_invocation_price_settlement_digest_uidx
ON "ModelInvocationPriceSettlements" (settlement_digest)`,
],
(context, statement) => context.client.exec(statement),
);
export const sqliteUsagePricingMigrations = Object.freeze([
localUsageMigration,
localQuotaMigration,
localPricingMigration,
]);
@@ -0,0 +1,3 @@
export * from './model-invocation/identities';
export * from './model-invocation/postgres';
export * from './model-invocation/sqlite';
@@ -0,0 +1,769 @@
import { Buffer } from 'node:buffer';
import { createHash } from 'node:crypto';
import {
MAX_MODEL_PROVIDERS,
type GenerateRequest,
type GenerateResult,
type ModelChunk,
type ModelInvocationAuditRecord,
type ModelInvocationAuditResult,
type ModelInvocationAuditSink,
type ModelInvocationContext,
type ModelInvocationPolicy,
type ModelInvocationPolicyProvider,
type ModelProvider,
type ModelUsage,
} from './model';
import {
InvalidModelValueError,
measureModelInputBytes,
normalizeGenerateRequest,
normalizeGenerateResult,
normalizeModelChunk,
normalizeModelInvocationContext,
normalizeModelInvocationPolicy,
} from './validation';
import {
ModelInvocationProjectQuotaExceededError,
ModelInvocationQuotaConfigurationError,
createModelInvocationQuotaAdmission,
} from '../usage/usageQuota';
import {
ModelPriceUnavailableError,
ModelPricingConfigurationError,
createModelInvocationPriceQuote,
normalizeModelPriceCatalogEntry,
priceModelUsage,
type ModelInvocationPriceQuote,
type ModelPriceCatalogResolver,
} from '../pricing/pricing';
export const MAX_MODEL_GATEWAY_CONCURRENCY = 64;
export class ModelProviderUnavailableError extends Error {
readonly code = 'MODEL_PROVIDER_UNAVAILABLE';
constructor() {
super('The requested model provider is unavailable');
this.name = 'ModelProviderUnavailableError';
}
}
export class ModelPolicyDeniedError extends Error {
readonly code = 'MODEL_POLICY_DENIED';
constructor() {
super('The model invocation is denied by policy');
this.name = 'ModelPolicyDeniedError';
}
}
export class ModelBudgetExceededError extends Error {
readonly code = 'MODEL_BUDGET_EXCEEDED';
constructor() {
super('The model invocation exceeded its bounded budget');
this.name = 'ModelBudgetExceededError';
}
}
export class ModelGatewayBusyError extends Error {
readonly code = 'MODEL_GATEWAY_BUSY';
constructor() {
super('The model gateway concurrency budget is exhausted');
this.name = 'ModelGatewayBusyError';
}
}
export class ModelInvocationAbortedError extends Error {
readonly code = 'MODEL_INVOCATION_ABORTED';
constructor() {
super('The model invocation was aborted');
this.name = 'ModelInvocationAbortedError';
}
}
export class ModelInvocationDeadlineExceededError extends Error {
readonly code = 'MODEL_INVOCATION_DEADLINE_EXCEEDED';
constructor() {
super('The model invocation deadline was exceeded');
this.name = 'ModelInvocationDeadlineExceededError';
}
}
export class ModelInvocationReplayBlockedError extends Error {
readonly code = 'MODEL_INVOCATION_REPLAY_BLOCKED';
constructor() {
super(
'An existing model invocation cannot be executed again automatically',
);
this.name = 'ModelInvocationReplayBlockedError';
}
}
export class ModelAuditUnavailableError extends Error {
readonly code = 'MODEL_AUDIT_UNAVAILABLE';
constructor(options?: ErrorOptions) {
super('The model invocation audit sink is unavailable', options);
this.name = 'ModelAuditUnavailableError';
}
}
export interface BoundedModelGatewayOptions {
readonly providers: readonly ModelProvider[];
readonly policies: ModelInvocationPolicyProvider;
readonly pricing: ModelPriceCatalogResolver;
readonly audit: ModelInvocationAuditSink;
readonly successfulCompletion?: ModelInvocationSuccessfulCompletionSink;
readonly maxConcurrent: number;
readonly now?: () => number;
}
export interface ModelInvocationSuccessfulCompletionSink {
record(
audit: Readonly<ModelInvocationAuditRecord>,
result: Readonly<GenerateResult>,
): Promise<
Readonly<
| { handled: false }
| {
handled: true;
disposition: ModelInvocationAuditResult;
}
>
>;
}
interface PreparedInvocation {
readonly request: Readonly<GenerateRequest>;
readonly context: Readonly<ModelInvocationContext>;
readonly policy: Readonly<ModelInvocationPolicy>;
readonly provider: ModelProvider;
readonly requestDigest: string;
readonly inputBytes: number;
readonly priceQuote: Readonly<ModelInvocationPriceQuote> | null;
}
function errorCode(error: unknown): string {
if (
error &&
typeof error === 'object' &&
'code' in error &&
typeof error.code === 'string' &&
/^[A-Z][A-Z0-9_]{0,63}$/.test(error.code)
) {
return error.code;
}
if (error instanceof DOMException && error.name === 'AbortError') {
return 'MODEL_INVOCATION_ABORTED';
}
return 'MODEL_PROVIDER_FAILED';
}
function createRequestDigest(request: Readonly<GenerateRequest>): string {
const hash = createHash('sha256');
hash.update('qinglong/model-invocation-request@v1\0', 'utf8');
hash.update(request.provider, 'utf8');
hash.update('\0', 'utf8');
hash.update(request.model, 'utf8');
hash.update('\0', 'utf8');
hash.update(String(request.maxOutputTokens), 'utf8');
hash.update('\0', 'utf8');
hash.update(
request.temperature === undefined ? '' : String(request.temperature),
);
for (const message of request.messages) {
hash.update('\0', 'utf8');
hash.update(message.role, 'utf8');
hash.update('\0', 'utf8');
hash.update(message.content, 'utf8');
}
return `sha256:${hash.digest('hex')}`;
}
function assertWithinPolicy(
request: Readonly<GenerateRequest>,
inputBytes: number,
policy: Readonly<ModelInvocationPolicy>,
): void {
if (
!policy.allowedProviders.includes(request.provider) ||
!policy.allowedModels.includes(request.model)
) {
throw new ModelPolicyDeniedError();
}
if (
inputBytes > policy.maxInputBytes ||
request.maxOutputTokens > policy.maxOutputTokens
) {
throw new ModelBudgetExceededError();
}
}
function assertUsageWithinPolicy(
usage: Readonly<ModelUsage>,
policy: Readonly<ModelInvocationPolicy>,
): void {
if (
usage.outputTokens > policy.maxOutputTokens ||
usage.totalTokens > policy.maxTotalTokens ||
(policy.maxCostMicros !== null &&
(usage.costMicros === undefined ||
usage.costMicros > policy.maxCostMicros))
) {
throw new ModelBudgetExceededError();
}
}
function createInvocationAbort(
context: Readonly<ModelInvocationContext>,
nowMs: number,
): Readonly<{
signal: AbortSignal;
error(): ModelInvocationAbortedError | ModelInvocationDeadlineExceededError;
cancel(): void;
dispose(): void;
}> {
const controller = new AbortController();
let cause: 'caller' | 'deadline' | null = null;
const abortFromCaller = (): void => {
if (cause === null) cause = 'caller';
controller.abort();
};
const abortFromDeadline = (): void => {
if (cause === null) cause = 'deadline';
controller.abort();
};
context.signal?.addEventListener('abort', abortFromCaller, { once: true });
if (context.signal?.aborted) abortFromCaller();
const timer = setTimeout(
abortFromDeadline,
Math.max(1, context.deadlineAtMs - nowMs),
);
timer.unref();
return Object.freeze({
signal: controller.signal,
error():
| ModelInvocationAbortedError
| ModelInvocationDeadlineExceededError {
return cause === 'deadline'
? new ModelInvocationDeadlineExceededError()
: new ModelInvocationAbortedError();
},
cancel(): void {
abortFromCaller();
},
dispose(): void {
clearTimeout(timer);
context.signal?.removeEventListener('abort', abortFromCaller);
},
});
}
function withSignal(
context: Readonly<ModelInvocationContext>,
signal: AbortSignal,
): Readonly<ModelInvocationContext> {
return Object.freeze({
projectId: context.projectId,
runId: context.runId,
stepRunId: context.stepRunId,
traceId: context.traceId,
requestId: context.requestId,
deadlineAtMs: context.deadlineAtMs,
signal,
});
}
function awaitAbortable<T>(
operation: Promise<T>,
signal: AbortSignal,
abortError: () => Error = () => new ModelInvocationAbortedError(),
): Promise<T> {
if (signal.aborted) {
return Promise.reject(abortError());
}
return new Promise<T>((resolve, reject) => {
const abort = (): void => reject(abortError());
signal.addEventListener('abort', abort, { once: true });
operation.then(
(value) => {
signal.removeEventListener('abort', abort);
resolve(value);
},
(error) => {
signal.removeEventListener('abort', abort);
reject(error);
},
);
});
}
export class BoundedModelGateway {
readonly #providers: ReadonlyMap<string, ModelProvider>;
readonly #policies: ModelInvocationPolicyProvider;
readonly #pricing: ModelPriceCatalogResolver;
readonly #audit: ModelInvocationAuditSink;
readonly #successfulCompletion:
| ModelInvocationSuccessfulCompletionSink
| undefined;
readonly #maxConcurrent: number;
readonly #now: () => number;
#active = 0;
constructor(options: BoundedModelGatewayOptions) {
if (
!options ||
typeof options !== 'object' ||
Array.isArray(options) ||
!Array.isArray(options.providers) ||
options.providers.length < 1 ||
options.providers.length > MAX_MODEL_PROVIDERS ||
!options.policies ||
typeof options.policies.resolve !== 'function' ||
!options.pricing ||
typeof options.pricing.resolve !== 'function' ||
!options.audit ||
typeof options.audit.record !== 'function' ||
(options.successfulCompletion !== undefined &&
(!options.successfulCompletion ||
typeof options.successfulCompletion.record !== 'function')) ||
!Number.isSafeInteger(options.maxConcurrent) ||
options.maxConcurrent < 1 ||
options.maxConcurrent > MAX_MODEL_GATEWAY_CONCURRENCY ||
(options.now !== undefined && typeof options.now !== 'function')
) {
throw new InvalidModelValueError('gateway options are invalid');
}
const providers = new Map<string, ModelProvider>();
for (const provider of options.providers) {
if (
!provider ||
typeof provider.type !== 'string' ||
!/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/.test(provider.type) ||
typeof provider.generate !== 'function' ||
typeof provider.stream !== 'function' ||
typeof provider.listModels !== 'function' ||
providers.has(provider.type)
) {
throw new InvalidModelValueError('provider registry is invalid');
}
providers.set(provider.type, provider);
}
this.#providers = providers;
this.#policies = options.policies;
this.#pricing = options.pricing;
this.#audit = options.audit;
this.#successfulCompletion = options.successfulCompletion;
this.#maxConcurrent = options.maxConcurrent;
this.#now = options.now ?? Date.now;
}
get activeInvocations(): number {
return this.#active;
}
supportsSuccessfulCompletionSink(
sink: ModelInvocationSuccessfulCompletionSink,
): boolean {
return this.#successfulCompletion === sink;
}
async #prepare(
request: Readonly<GenerateRequest>,
context: Readonly<ModelInvocationContext>,
signal: AbortSignal,
abortError: () => Error,
): Promise<PreparedInvocation> {
const invocationContext = withSignal(context, signal);
const inputBytes = measureModelInputBytes(request.messages);
const policy = normalizeModelInvocationPolicy(
await awaitAbortable(
this.#policies.resolve(invocationContext),
signal,
abortError,
),
);
assertWithinPolicy(request, inputBytes, policy);
const provider = this.#providers.get(request.provider);
if (!provider) throw new ModelProviderUnavailableError();
const priceRevision = policy.priceRevision;
const priceQuote =
priceRevision === null
? null
: await (async () => {
const entryValue = await awaitAbortable(
this.#pricing.resolve({
provider: request.provider,
model: request.model,
priceRevision,
signal,
}),
signal,
abortError,
);
if (!entryValue) throw new ModelPriceUnavailableError();
const entry = normalizeModelPriceCatalogEntry(entryValue);
if (
entry.provider !== request.provider ||
entry.model !== request.model ||
entry.priceRevision !== priceRevision
) {
throw new ModelPriceUnavailableError();
}
const quote = createModelInvocationPriceQuote(entry, {
invocationId: context.requestId,
projectId: context.projectId,
modelPolicyRevision: policy.revision,
maxTotalTokens: policy.maxTotalTokens,
maxOutputTokens: request.maxOutputTokens,
});
if (
policy.maxCostMicros !== null &&
quote.reservedCostMicros > policy.maxCostMicros
) {
throw new ModelBudgetExceededError();
}
return quote;
})();
if (
priceQuote === null &&
(policy.maxCostMicros !== null ||
(policy.projectQuota !== undefined &&
policy.projectQuota.maxCostMicros !== null))
) {
throw new ModelPriceUnavailableError();
}
return Object.freeze({
request,
context: invocationContext,
policy,
provider,
requestDigest: createRequestDigest(request),
inputBytes,
priceQuote,
});
}
async #record(
prepared: PreparedInvocation,
phase: ModelInvocationAuditRecord['phase'],
outputBytes: number,
usage: Readonly<ModelUsage> | null,
invocationErrorCode: string | null,
result?: Readonly<GenerateResult>,
): Promise<ModelInvocationAuditResult> {
try {
const auditRecord = Object.freeze({
phase,
projectId: prepared.context.projectId,
runId: prepared.context.runId,
stepRunId: prepared.context.stepRunId,
traceId: prepared.context.traceId,
requestId: prepared.context.requestId,
provider: prepared.request.provider,
model: prepared.request.model,
policyRevision: prepared.policy.revision,
requestDigest: prepared.requestDigest,
deadlineAtMs: prepared.context.deadlineAtMs,
inputBytes: prepared.inputBytes,
maxOutputTokens: prepared.request.maxOutputTokens,
outputBytes,
usage,
errorCode: invocationErrorCode,
occurredAtMs: this.#now(),
});
const quotaAdmission =
phase === 'admitted' && prepared.policy.projectQuota
? createModelInvocationQuotaAdmission({
invocationId: prepared.context.requestId,
projectId: prepared.context.projectId,
modelPolicyRevision: prepared.policy.revision,
reservedTokens: prepared.policy.maxTotalTokens,
reservedCostMicros:
prepared.policy.projectQuota.maxCostMicros === null
? null
: prepared.priceQuote?.reservedCostMicros ?? null,
quota: prepared.policy.projectQuota,
})
: undefined;
const successfulCompletion =
phase === 'completed' && result && this.#successfulCompletion
? await this.#successfulCompletion.record(auditRecord, result)
: undefined;
if (
successfulCompletion !== undefined &&
(!successfulCompletion ||
typeof successfulCompletion !== 'object' ||
Array.isArray(successfulCompletion) ||
(successfulCompletion.handled !== true &&
successfulCompletion.handled !== false) ||
(successfulCompletion.handled === false &&
Object.keys(successfulCompletion).length !== 1) ||
(successfulCompletion.handled === true &&
Object.keys(successfulCompletion).sort().join('\0') !==
['disposition', 'handled'].join('\0')))
) {
throw new TypeError('Successful completion disposition is invalid');
}
const disposition =
successfulCompletion?.handled === true
? successfulCompletion.disposition
: phase === 'admitted' && prepared.priceQuote
? await (() => {
if (typeof this.#audit.recordWithPricing !== 'function') {
throw new ModelPricingConfigurationError();
}
return this.#audit.recordWithPricing(
auditRecord,
prepared.priceQuote,
quotaAdmission,
);
})()
: quotaAdmission
? await (() => {
if (typeof this.#audit.recordWithQuota !== 'function') {
throw new ModelInvocationQuotaConfigurationError();
}
return this.#audit.recordWithQuota(auditRecord, quotaAdmission);
})()
: await this.#audit.record(auditRecord);
if (disposition === undefined) return undefined;
if (
disposition &&
typeof disposition === 'object' &&
!Array.isArray(disposition) &&
(disposition.status === 'created' ||
disposition.status === 'existing') &&
Object.keys(disposition).length === 1
) {
return Object.freeze({ status: disposition.status });
}
throw new TypeError('Model audit disposition is invalid');
} catch (cause) {
if (
cause instanceof ModelInvocationProjectQuotaExceededError ||
cause instanceof ModelInvocationQuotaConfigurationError ||
cause instanceof ModelPricingConfigurationError
) {
throw cause;
}
throw new ModelAuditUnavailableError({ cause });
}
}
async generate(
request: GenerateRequest,
context: ModelInvocationContext,
): Promise<Readonly<GenerateResult>> {
const nowMs = this.#now();
const normalizedRequest = normalizeGenerateRequest(request);
const normalizedContext = normalizeModelInvocationContext(context, nowMs);
if (this.#active >= this.#maxConcurrent) {
throw new ModelGatewayBusyError();
}
this.#active += 1;
const abort = createInvocationAbort(normalizedContext, nowMs);
let prepared: PreparedInvocation | undefined;
let admitted = false;
let outputBytes = 0;
let usage: Readonly<ModelUsage> | null = null;
try {
prepared = await this.#prepare(
normalizedRequest,
normalizedContext,
abort.signal,
() => abort.error(),
);
const admission = await this.#record(prepared, 'admitted', 0, null, null);
if (admission?.status === 'existing') {
throw new ModelInvocationReplayBlockedError();
}
admitted = true;
if (abort.signal.aborted) throw abort.error();
const providerResult = normalizeGenerateResult(
await awaitAbortable(
prepared.provider.generate(prepared.request, prepared.context),
abort.signal,
() => abort.error(),
),
);
const result = prepared.priceQuote
? Object.freeze({
...providerResult,
usage: priceModelUsage(prepared.priceQuote, providerResult.usage),
})
: providerResult;
if (
result.provider !== prepared.request.provider ||
result.model !== prepared.request.model
) {
throw new InvalidModelValueError(
'provider result identity does not match the request',
);
}
outputBytes = Buffer.byteLength(result.text, 'utf8');
usage = result.usage;
if (outputBytes > prepared.policy.maxOutputBytes) {
throw new ModelBudgetExceededError();
}
assertUsageWithinPolicy(usage, prepared.policy);
await this.#record(
prepared,
'completed',
outputBytes,
usage,
null,
result,
);
return result;
} catch (error) {
const normalizedError =
abort.signal.aborted &&
!(error instanceof ModelAuditUnavailableError) &&
!(error instanceof ModelBudgetExceededError)
? abort.error()
: error;
if (
prepared &&
admitted &&
!(normalizedError instanceof ModelAuditUnavailableError)
) {
await this.#record(
prepared,
'failed',
outputBytes,
usage,
errorCode(normalizedError),
);
}
throw normalizedError;
} finally {
abort.dispose();
this.#active -= 1;
}
}
async *stream(
request: GenerateRequest,
context: ModelInvocationContext,
): AsyncIterable<Readonly<ModelChunk>> {
const nowMs = this.#now();
const normalizedRequest = normalizeGenerateRequest(request);
const normalizedContext = normalizeModelInvocationContext(context, nowMs);
if (this.#active >= this.#maxConcurrent) {
throw new ModelGatewayBusyError();
}
this.#active += 1;
const abort = createInvocationAbort(normalizedContext, nowMs);
let prepared: PreparedInvocation | undefined;
let iterator: AsyncIterator<Readonly<ModelChunk>> | undefined;
let admitted = false;
let outputBytes = 0;
let usage: Readonly<ModelUsage> | null = null;
let completed = false;
let failureRecorded = false;
try {
prepared = await this.#prepare(
normalizedRequest,
normalizedContext,
abort.signal,
() => abort.error(),
);
const admission = await this.#record(prepared, 'admitted', 0, null, null);
if (admission?.status === 'existing') {
throw new ModelInvocationReplayBlockedError();
}
admitted = true;
if (abort.signal.aborted) throw abort.error();
iterator = prepared.provider
.stream(prepared.request, prepared.context)
[Symbol.asyncIterator]();
while (true) {
const next = await awaitAbortable(iterator.next(), abort.signal, () =>
abort.error(),
);
if (next.done) break;
const rawChunk = next.value;
const normalizedChunk = normalizeModelChunk(rawChunk);
const chunk =
normalizedChunk.usage && prepared.priceQuote
? Object.freeze({
...normalizedChunk,
usage: priceModelUsage(
prepared.priceQuote,
normalizedChunk.usage,
),
})
: normalizedChunk;
outputBytes += Buffer.byteLength(chunk.delta, 'utf8');
if (outputBytes > prepared.policy.maxOutputBytes) {
throw new ModelBudgetExceededError();
}
if (chunk.usage) {
usage = chunk.usage;
assertUsageWithinPolicy(usage, prepared.policy);
}
yield chunk;
}
if (!usage) {
throw new InvalidModelValueError(
'stream completed without final usage',
);
}
completed = true;
await this.#record(prepared, 'completed', outputBytes, usage, null);
} catch (error) {
failureRecorded = true;
const normalizedError =
abort.signal.aborted &&
!(error instanceof ModelAuditUnavailableError) &&
!(error instanceof ModelBudgetExceededError)
? abort.error()
: error;
if (
prepared &&
admitted &&
!(normalizedError instanceof ModelAuditUnavailableError)
) {
await this.#record(
prepared,
'failed',
outputBytes,
usage,
errorCode(normalizedError),
);
}
throw normalizedError;
} finally {
try {
if (!completed && !failureRecorded && prepared && admitted) {
await this.#record(
prepared,
'failed',
outputBytes,
usage,
'MODEL_STREAM_CANCELLED',
);
}
} finally {
if (!completed) abort.cancel();
if (!completed && iterator?.return) {
try {
void Promise.resolve(iterator.return()).catch(() => undefined);
} catch {
// Cleanup cannot regain invocation authority or retain the slot.
}
}
abort.dispose();
this.#active -= 1;
}
}
}
}
+158
View File
@@ -0,0 +1,158 @@
export const MODEL_MESSAGE_ROLES = ['system', 'user', 'assistant'] as const;
export const MODEL_FINISH_REASONS = [
'stop',
'length',
'content_filter',
'tool_call',
'unknown',
] as const;
export const MAX_MODEL_PROVIDERS = 8;
export const MAX_MODEL_MESSAGES = 64;
export const MAX_MODEL_MESSAGE_BYTES = 64 * 1024;
export const MAX_MODEL_INPUT_BYTES = 256 * 1024;
export const MAX_MODEL_OUTPUT_BYTES = 1024 * 1024;
export const MAX_MODEL_OUTPUT_TOKENS = 32_768;
export const MAX_MODEL_INVOCATION_MS = 5 * 60_000;
export type ModelMessageRole = (typeof MODEL_MESSAGE_ROLES)[number];
export type ModelFinishReason = (typeof MODEL_FINISH_REASONS)[number];
export interface ModelMessage {
readonly role: ModelMessageRole;
readonly content: string;
}
export interface GenerateRequest {
readonly provider: string;
readonly model: string;
readonly messages: readonly ModelMessage[];
readonly maxOutputTokens: number;
readonly temperature?: number;
}
export interface ModelInvocationContext {
readonly projectId: string;
readonly runId: string;
readonly stepRunId: string;
readonly traceId: string;
readonly requestId: string;
readonly deadlineAtMs: number;
readonly signal?: AbortSignal;
}
export interface ModelInfo {
readonly id: string;
readonly displayName?: string;
readonly contextWindowTokens?: number;
}
export interface ModelUsage {
readonly inputTokens: number;
readonly outputTokens: number;
readonly totalTokens: number;
readonly costMicros?: number;
}
export interface GenerateResult {
readonly provider: string;
readonly model: string;
readonly text: string;
readonly finishReason: ModelFinishReason;
readonly usage: ModelUsage;
}
export interface ModelChunk {
readonly delta: string;
readonly finishReason?: ModelFinishReason;
readonly usage?: ModelUsage;
}
export interface ModelProvider {
readonly type: string;
listModels(
context?: Readonly<{ signal?: AbortSignal }>,
): Promise<readonly ModelInfo[]>;
generate(
request: Readonly<GenerateRequest>,
context: Readonly<ModelInvocationContext>,
): Promise<Readonly<GenerateResult>>;
stream(
request: Readonly<GenerateRequest>,
context: Readonly<ModelInvocationContext>,
): AsyncIterable<Readonly<ModelChunk>>;
}
export interface ModelInvocationPolicy {
readonly revision: string;
readonly allowedProviders: readonly string[];
readonly allowedModels: readonly string[];
readonly maxInputBytes: number;
readonly maxOutputBytes: number;
readonly maxOutputTokens: number;
readonly maxTotalTokens: number;
readonly maxCostMicros: number | null;
readonly priceRevision: string | null;
readonly projectQuota?: import('../usage/usageQuota').ModelInvocationProjectQuotaPolicy;
}
export interface ModelInvocationPolicyProvider {
resolve(
context: Readonly<ModelInvocationContext>,
): Promise<Readonly<ModelInvocationPolicy>>;
}
export const MODEL_INVOCATION_AUDIT_PHASES = [
'admitted',
'completed',
'failed',
] as const;
export type ModelInvocationAuditPhase =
(typeof MODEL_INVOCATION_AUDIT_PHASES)[number];
export interface ModelInvocationAuditRecord {
readonly phase: ModelInvocationAuditPhase;
readonly projectId: string;
readonly runId: string;
readonly stepRunId: string;
readonly traceId: string;
readonly requestId: string;
readonly provider: string;
readonly model: string;
readonly policyRevision: string;
readonly requestDigest: string;
readonly deadlineAtMs: number;
readonly inputBytes: number;
readonly maxOutputTokens: number;
readonly outputBytes: number;
readonly usage: Readonly<ModelUsage> | null;
readonly errorCode: string | null;
readonly occurredAtMs: number;
}
export interface ModelInvocationAuditDisposition {
readonly status: 'created' | 'existing';
}
export type ModelInvocationAuditResult =
void | Readonly<ModelInvocationAuditDisposition>;
export interface ModelInvocationAuditSink {
record(
record: Readonly<ModelInvocationAuditRecord>,
): Promise<ModelInvocationAuditResult>;
recordWithQuota?(
record: Readonly<ModelInvocationAuditRecord>,
admission: Readonly<
import('../usage/usageQuota').ModelInvocationQuotaAdmission
>,
): Promise<ModelInvocationAuditResult>;
recordWithPricing?(
record: Readonly<ModelInvocationAuditRecord>,
quote: Readonly<import('../pricing/pricing').ModelInvocationPriceQuote>,
quotaAdmission?: Readonly<
import('../usage/usageQuota').ModelInvocationQuotaAdmission
>,
): Promise<ModelInvocationAuditResult>;
}
@@ -0,0 +1,578 @@
import { Buffer } from 'node:buffer';
import {
MAX_MODEL_OUTPUT_BYTES,
type GenerateRequest,
type GenerateResult,
type ModelChunk,
type ModelFinishReason,
type ModelInfo,
type ModelInvocationContext,
type ModelProvider,
type ModelUsage,
} from './model';
import {
InvalidModelValueError,
normalizeGenerateRequest,
normalizeModelInvocationContext,
normalizeModelInfo,
normalizeModelUsage,
} from './validation';
import type {
ModelProviderAuthorizationLease,
ModelProviderAuthorizationProvider,
ModelProviderAuthorizationRequest,
} from '../model-provider-credential/providerCredential';
const MAX_OPENAI_RESPONSE_BYTES = 8 * 1024 * 1024;
const MAX_OPENAI_MODELS = 256;
const MAX_OPENAI_AUTHORIZATION_BYTES = 4 * 1024;
export class OpenAiCompatibleConfigurationError extends TypeError {
readonly code = 'OPENAI_COMPATIBLE_CONFIGURATION_INVALID';
constructor(message: string) {
super(`OpenAI-compatible provider configuration is invalid: ${message}`);
this.name = 'OpenAiCompatibleConfigurationError';
}
}
export class OpenAiCompatibleHttpError extends Error {
readonly code = 'OPENAI_COMPATIBLE_HTTP_ERROR';
readonly status: number;
constructor(status: number) {
super(`OpenAI-compatible provider returned HTTP ${status}`);
this.name = 'OpenAiCompatibleHttpError';
this.status = status;
}
}
export class OpenAiCompatibleProtocolError extends Error {
readonly code = 'OPENAI_COMPATIBLE_PROTOCOL_ERROR';
constructor(message: string, options?: ErrorOptions) {
super(
`OpenAI-compatible provider response is invalid: ${message}`,
options,
);
this.name = 'OpenAiCompatibleProtocolError';
}
}
export type OpenAiCompatibleCredentialProvider =
ModelProviderAuthorizationProvider;
export interface OpenAiCompatibleListModelsContext {
readonly projectId?: string;
readonly requestId?: string;
readonly signal?: AbortSignal;
}
export interface OpenAiCompatibleProviderOptions {
readonly type: string;
readonly baseUrl: string;
readonly credentials?: OpenAiCompatibleCredentialProvider;
readonly fetch?: typeof globalThis.fetch;
readonly allowPlaintextLoopback?: boolean;
readonly maxResponseBytes?: number;
}
type JsonRecord = Record<string, unknown>;
function isRecord(value: unknown): value is JsonRecord {
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
}
function normalizeBaseUrl(value: string, allowPlaintextLoopback: boolean): URL {
let url: URL;
try {
url = new URL(value);
} catch {
throw new OpenAiCompatibleConfigurationError('baseUrl is not a URL');
}
const loopback =
url.hostname === 'localhost' ||
url.hostname === '127.0.0.1' ||
url.hostname === '[::1]' ||
url.hostname === '::1';
if (
url.username ||
url.password ||
url.search ||
url.hash ||
(url.protocol !== 'https:' &&
!(url.protocol === 'http:' && loopback && allowPlaintextLoopback))
) {
throw new OpenAiCompatibleConfigurationError(
'baseUrl must use HTTPS without credentials, query, or fragment',
);
}
if (!url.pathname.endsWith('/')) url.pathname += '/';
return url;
}
function normalizeFinishReason(value: unknown): ModelFinishReason {
if (value === 'stop') return 'stop';
if (value === 'length') return 'length';
if (value === 'content_filter') return 'content_filter';
if (value === 'tool_calls' || value === 'function_call') return 'tool_call';
return 'unknown';
}
function parseUsage(value: unknown): Readonly<ModelUsage> {
if (!isRecord(value)) {
throw new OpenAiCompatibleProtocolError('usage is missing');
}
const inputTokens = value.prompt_tokens;
const outputTokens = value.completion_tokens;
const totalTokens = value.total_tokens;
try {
return normalizeModelUsage({
inputTokens: inputTokens as number,
outputTokens: outputTokens as number,
totalTokens: totalTokens as number,
});
} catch (cause) {
throw new OpenAiCompatibleProtocolError('usage is invalid', { cause });
}
}
async function readBoundedBytes(
response: Response,
maximumBytes: number,
): Promise<Uint8Array> {
if (!response.body) {
throw new OpenAiCompatibleProtocolError('response body is missing');
}
const reader = response.body.getReader();
const chunks: Uint8Array[] = [];
let total = 0;
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
total += value.byteLength;
if (total > maximumBytes) {
throw new OpenAiCompatibleProtocolError(
'response exceeds the byte limit',
);
}
chunks.push(value);
}
} finally {
reader.releaseLock();
}
const result = new Uint8Array(total);
let offset = 0;
for (const chunk of chunks) {
result.set(chunk, offset);
offset += chunk.byteLength;
}
return result;
}
async function readBoundedJson(
response: Response,
maximumBytes: number,
): Promise<unknown> {
const bytes = await readBoundedBytes(response, maximumBytes);
try {
return JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
} catch (cause) {
throw new OpenAiCompatibleProtocolError('response JSON is invalid', {
cause,
});
}
}
function createRequestBody(
request: Readonly<GenerateRequest>,
stream: boolean,
): string {
return JSON.stringify({
model: request.model,
messages: request.messages,
max_tokens: request.maxOutputTokens,
...(request.temperature === undefined
? {}
: { temperature: request.temperature }),
stream,
...(stream ? { stream_options: { include_usage: true } } : {}),
});
}
function parseGenerateResult(
provider: string,
requestedModel: string,
value: unknown,
): Readonly<GenerateResult> {
if (!isRecord(value) || !Array.isArray(value.choices)) {
throw new OpenAiCompatibleProtocolError('completion shape is invalid');
}
const choice = value.choices[0];
if (
!isRecord(choice) ||
!isRecord(choice.message) ||
typeof choice.message.content !== 'string'
) {
throw new OpenAiCompatibleProtocolError('completion choice is invalid');
}
if (
Buffer.byteLength(choice.message.content, 'utf8') > MAX_MODEL_OUTPUT_BYTES
) {
throw new OpenAiCompatibleProtocolError(
'completion text exceeds the hard byte limit',
);
}
const model =
typeof value.model === 'string' && value.model.length > 0
? value.model
: requestedModel;
if (model !== requestedModel) {
throw new OpenAiCompatibleProtocolError(
'completion model does not match the request',
);
}
return Object.freeze({
provider,
model,
text: choice.message.content,
finishReason: normalizeFinishReason(choice.finish_reason),
usage: parseUsage(value.usage),
});
}
function parseStreamEvent(value: unknown): Readonly<ModelChunk> | null {
if (!isRecord(value)) {
throw new OpenAiCompatibleProtocolError('stream event is invalid');
}
const choice = Array.isArray(value.choices) ? value.choices[0] : undefined;
let delta = '';
let finishReason: ModelFinishReason | undefined;
if (choice !== undefined) {
if (!isRecord(choice)) {
throw new OpenAiCompatibleProtocolError('stream choice is invalid');
}
if (isRecord(choice.delta) && choice.delta.content !== undefined) {
if (typeof choice.delta.content !== 'string') {
throw new OpenAiCompatibleProtocolError(
'stream delta content is invalid',
);
}
delta = choice.delta.content;
}
if (choice.finish_reason !== undefined && choice.finish_reason !== null) {
finishReason = normalizeFinishReason(choice.finish_reason);
}
}
const usage =
value.usage === undefined || value.usage === null
? undefined
: parseUsage(value.usage);
if (delta.length === 0 && finishReason === undefined && usage === undefined) {
return null;
}
return Object.freeze({
delta,
...(finishReason === undefined ? {} : { finishReason }),
...(usage === undefined ? {} : { usage }),
});
}
export class OpenAiCompatibleProvider implements ModelProvider {
readonly type: string;
readonly #baseUrl: URL;
readonly #credentials: OpenAiCompatibleCredentialProvider | undefined;
readonly #fetch: typeof globalThis.fetch;
readonly #maxResponseBytes: number;
constructor(options: OpenAiCompatibleProviderOptions) {
if (
!options ||
typeof options !== 'object' ||
Array.isArray(options) ||
typeof options.type !== 'string' ||
!/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/.test(options.type) ||
typeof options.baseUrl !== 'string' ||
(options.credentials !== undefined &&
(!options.credentials ||
typeof options.credentials.authorizationHeader !== 'function')) ||
(options.fetch !== undefined && typeof options.fetch !== 'function') ||
(options.allowPlaintextLoopback !== undefined &&
typeof options.allowPlaintextLoopback !== 'boolean')
) {
throw new OpenAiCompatibleConfigurationError('options are invalid');
}
const maxResponseBytes =
options.maxResponseBytes ?? MAX_OPENAI_RESPONSE_BYTES;
if (
!Number.isSafeInteger(maxResponseBytes) ||
maxResponseBytes < 1 ||
maxResponseBytes > MAX_OPENAI_RESPONSE_BYTES
) {
throw new OpenAiCompatibleConfigurationError(
'maxResponseBytes is invalid',
);
}
this.type = options.type;
this.#baseUrl = normalizeBaseUrl(
options.baseUrl,
options.allowPlaintextLoopback ?? false,
);
this.#credentials = options.credentials;
this.#fetch = options.fetch ?? globalThis.fetch;
this.#maxResponseBytes = maxResponseBytes;
}
async #request(
url: URL,
init: Omit<RequestInit, 'headers'>,
authorizationRequest: Readonly<ModelProviderAuthorizationRequest>,
): Promise<Response> {
let authorization: Readonly<ModelProviderAuthorizationLease> | null = null;
let authorizationValue: string | null = null;
let response: Response | undefined;
try {
authorization =
(await this.#credentials?.authorizationHeader(authorizationRequest)) ??
null;
if (
authorization !== null &&
(!authorization ||
typeof authorization !== 'object' ||
Array.isArray(authorization) ||
Object.keys(authorization).sort().join('\0') !==
['dispose', 'value'].join('\0') ||
typeof authorization.value !== 'string' ||
authorization.value.length < 1 ||
Buffer.byteLength(authorization.value, 'utf8') >
MAX_OPENAI_AUTHORIZATION_BYTES ||
/[\r\n]/.test(authorization.value) ||
typeof authorization.dispose !== 'function')
) {
throw new OpenAiCompatibleConfigurationError(
'authorization lease is invalid',
);
}
authorizationValue = authorization === null ? null : authorization.value;
response = await this.#fetch(url, {
...init,
headers: Object.freeze({
accept: 'application/json',
'content-type': 'application/json',
...(authorizationValue === null
? {}
: { authorization: authorizationValue }),
}),
});
return response;
} finally {
if (authorization && typeof authorization.dispose === 'function') {
try {
await authorization.dispose();
} catch (cause) {
await response?.body?.cancel().catch(() => undefined);
throw new OpenAiCompatibleConfigurationError(
cause instanceof Error
? 'authorization lease disposal failed'
: 'authorization lease disposal is invalid',
);
}
}
}
}
async listModels(
context: Readonly<OpenAiCompatibleListModelsContext> = {},
): Promise<readonly ModelInfo[]> {
const response = await this.#request(
new URL('models', this.#baseUrl),
{
method: 'GET',
...(context.signal === undefined ? {} : { signal: context.signal }),
},
Object.freeze({
operation: 'list_models',
provider: this.type,
...(context.projectId === undefined
? {}
: { projectId: context.projectId }),
...(context.requestId === undefined
? {}
: { requestId: context.requestId }),
...(context.signal === undefined ? {} : { signal: context.signal }),
}),
);
if (!response.ok) throw new OpenAiCompatibleHttpError(response.status);
const value = await readBoundedJson(response, this.#maxResponseBytes);
if (!isRecord(value) || !Array.isArray(value.data)) {
throw new OpenAiCompatibleProtocolError('model list shape is invalid');
}
if (value.data.length > MAX_OPENAI_MODELS) {
throw new OpenAiCompatibleProtocolError(
'model list exceeds the item limit',
);
}
return Object.freeze(
value.data.map((entry) => {
if (!isRecord(entry) || typeof entry.id !== 'string') {
throw new OpenAiCompatibleProtocolError(
'model list entry is invalid',
);
}
return normalizeModelInfo({ id: entry.id });
}),
);
}
async generate(
request: Readonly<GenerateRequest>,
context: Readonly<ModelInvocationContext>,
): Promise<Readonly<GenerateResult>> {
const normalizedRequest = normalizeGenerateRequest(request);
const normalizedContext = normalizeModelInvocationContext(
context,
Date.now(),
);
if (normalizedRequest.provider !== this.type) {
throw new InvalidModelValueError(
'request provider does not match the adapter',
);
}
const response = await this.#request(
new URL('chat/completions', this.#baseUrl),
{
method: 'POST',
body: createRequestBody(normalizedRequest, false),
...(normalizedContext.signal === undefined
? {}
: { signal: normalizedContext.signal }),
},
Object.freeze({
operation: 'generate',
provider: this.type,
projectId: normalizedContext.projectId,
requestId: normalizedContext.requestId,
...(normalizedContext.signal === undefined
? {}
: { signal: normalizedContext.signal }),
}),
);
if (!response.ok) throw new OpenAiCompatibleHttpError(response.status);
return parseGenerateResult(
this.type,
normalizedRequest.model,
await readBoundedJson(response, this.#maxResponseBytes),
);
}
async *stream(
request: Readonly<GenerateRequest>,
context: Readonly<ModelInvocationContext>,
): AsyncIterable<Readonly<ModelChunk>> {
const normalizedRequest = normalizeGenerateRequest(request);
const normalizedContext = normalizeModelInvocationContext(
context,
Date.now(),
);
if (normalizedRequest.provider !== this.type) {
throw new InvalidModelValueError(
'request provider does not match the adapter',
);
}
const response = await this.#request(
new URL('chat/completions', this.#baseUrl),
{
method: 'POST',
body: createRequestBody(normalizedRequest, true),
...(normalizedContext.signal === undefined
? {}
: { signal: normalizedContext.signal }),
},
Object.freeze({
operation: 'stream',
provider: this.type,
projectId: normalizedContext.projectId,
requestId: normalizedContext.requestId,
...(normalizedContext.signal === undefined
? {}
: { signal: normalizedContext.signal }),
}),
);
if (!response.ok) throw new OpenAiCompatibleHttpError(response.status);
if (!response.body) {
throw new OpenAiCompatibleProtocolError('stream body is missing');
}
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8', { fatal: true });
let buffered = '';
let receivedBytes = 0;
let doneEvent = false;
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
receivedBytes += value.byteLength;
if (receivedBytes > this.#maxResponseBytes) {
throw new OpenAiCompatibleProtocolError(
'stream exceeds the byte limit',
);
}
buffered += decoder.decode(value, { stream: true });
if (Buffer.byteLength(buffered, 'utf8') > this.#maxResponseBytes) {
throw new OpenAiCompatibleProtocolError(
'stream event buffer exceeds the byte limit',
);
}
while (true) {
const lfBoundary = buffered.indexOf('\n\n');
const crlfBoundary = buffered.indexOf('\r\n\r\n');
const boundary =
lfBoundary < 0
? crlfBoundary
: crlfBoundary < 0
? lfBoundary
: Math.min(lfBoundary, crlfBoundary);
if (boundary < 0) break;
const boundaryLength = buffered.startsWith('\r\n\r\n', boundary)
? 4
: 2;
const event = buffered.slice(0, boundary);
buffered = buffered.slice(boundary + boundaryLength);
const data = event
.split(/\r?\n/)
.filter((line) => line.startsWith('data:'))
.map((line) => line.slice(5).trimStart())
.join('\n');
if (data.length === 0) continue;
if (data === '[DONE]') {
doneEvent = true;
break;
}
let parsed: unknown;
try {
parsed = JSON.parse(data);
} catch (cause) {
throw new OpenAiCompatibleProtocolError(
'stream event JSON is invalid',
{ cause },
);
}
const chunk = parseStreamEvent(parsed);
if (chunk) yield chunk;
}
if (doneEvent) break;
}
buffered += decoder.decode();
if (!doneEvent) {
throw new OpenAiCompatibleProtocolError(
'stream ended without a done event',
);
}
} finally {
if (!doneEvent) await reader.cancel().catch(() => undefined);
reader.releaseLock();
}
}
}
@@ -0,0 +1,308 @@
import { Buffer } from 'node:buffer';
import { constants } from 'node:fs';
import { lstat, open, realpath } from 'node:fs/promises';
import { isAbsolute, normalize, parse, relative } from 'node:path';
import type { ModelInvocationContext, ModelInvocationPolicy } from './model';
import { OpenAiCompatibleProvider } from './openAiCompatibleProvider';
import type { ModelGatewayProviderAuthority } from '../profile/profileComposition';
import type { ModelProviderAuthorizationProvider } from '../model-provider-credential/providerCredential';
import { normalizeModelInvocationPolicy } from './validation';
export const PROJECTED_MODEL_GATEWAY_AUTHORITY_SCHEMA =
'qinglong/projected-model-gateway-authority@v1';
const MAX_CONFIG_BYTES = 256 * 1024;
const MAX_PATH_BYTES = 4096;
const MAX_PROVIDERS = 16;
const MAX_PROJECTS = 1024;
const IDENTITY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
export interface ProjectedModelGatewayProviderConfig {
readonly type: string;
readonly baseUrl: string;
readonly allowPlaintextLoopback: boolean;
readonly maxResponseBytes: number;
}
export interface ProjectedModelGatewayProjectConfig {
readonly projectId: string;
readonly policy: Readonly<ModelInvocationPolicy>;
}
export interface ProjectedModelGatewayAuthorityManifest {
readonly schema: typeof PROJECTED_MODEL_GATEWAY_AUTHORITY_SCHEMA;
readonly providers: readonly Readonly<ProjectedModelGatewayProviderConfig>[];
readonly projects: readonly Readonly<ProjectedModelGatewayProjectConfig>[];
}
export interface ProjectedModelGatewayAuthorityOptions {
readonly configFile: string;
readonly credentials: ModelProviderAuthorizationProvider;
}
export class ProjectedModelGatewayAuthorityUnavailableError extends Error {
readonly code = 'PROJECTED_MODEL_GATEWAY_AUTHORITY_UNAVAILABLE';
constructor(options?: ErrorOptions) {
super('Projected model gateway authority is unavailable', options);
this.name = 'ProjectedModelGatewayAuthorityUnavailableError';
}
}
function unavailable(
cause?: unknown,
): ProjectedModelGatewayAuthorityUnavailableError {
return new ProjectedModelGatewayAuthorityUnavailableError({
cause: cause instanceof Error ? cause : undefined,
});
}
function exactObject(
value: unknown,
expectedKeys: readonly string[],
): Record<string, unknown> {
if (
!value ||
typeof value !== 'object' ||
Array.isArray(value) ||
Object.keys(value).sort().join('\0') !== [...expectedKeys].sort().join('\0')
) {
throw unavailable();
}
return value as Record<string, unknown>;
}
function identifier(value: unknown): string {
if (typeof value !== 'string' || !IDENTITY_PATTERN.test(value)) {
throw unavailable();
}
return value;
}
function configFile(value: unknown): string {
if (
typeof value !== 'string' ||
!isAbsolute(value) ||
parse(value).root === value ||
normalize(value) !== value ||
value.includes('\0') ||
Buffer.byteLength(value, 'utf8') > MAX_PATH_BYTES
) {
throw unavailable();
}
return value;
}
function providerConfig(
value: unknown,
): Readonly<ProjectedModelGatewayProviderConfig> {
const candidate = exactObject(value, [
'allowPlaintextLoopback',
'baseUrl',
'maxResponseBytes',
'type',
]);
if (
typeof candidate.baseUrl !== 'string' ||
typeof candidate.allowPlaintextLoopback !== 'boolean' ||
!Number.isSafeInteger(candidate.maxResponseBytes) ||
(candidate.maxResponseBytes as number) < 1 ||
(candidate.maxResponseBytes as number) > 8 * 1024 * 1024
) {
throw unavailable();
}
return Object.freeze({
type: identifier(candidate.type),
baseUrl: candidate.baseUrl,
allowPlaintextLoopback: candidate.allowPlaintextLoopback,
maxResponseBytes: candidate.maxResponseBytes as number,
});
}
function projectConfig(
value: unknown,
): Readonly<ProjectedModelGatewayProjectConfig> {
const candidate = exactObject(value, ['policy', 'projectId']);
try {
return Object.freeze({
projectId: identifier(candidate.projectId),
policy: normalizeModelInvocationPolicy(
candidate.policy as ModelInvocationPolicy,
),
});
} catch (cause) {
throw unavailable(cause);
}
}
export function normalizeProjectedModelGatewayAuthorityManifest(
value: ProjectedModelGatewayAuthorityManifest,
): Readonly<ProjectedModelGatewayAuthorityManifest> {
const candidate = exactObject(value, ['projects', 'providers', 'schema']);
if (
candidate.schema !== PROJECTED_MODEL_GATEWAY_AUTHORITY_SCHEMA ||
!Array.isArray(candidate.providers) ||
candidate.providers.length < 1 ||
candidate.providers.length > MAX_PROVIDERS ||
!Array.isArray(candidate.projects) ||
candidate.projects.length < 1 ||
candidate.projects.length > MAX_PROJECTS
) {
throw unavailable();
}
const providers = candidate.providers.map(providerConfig);
const projects = candidate.projects.map(projectConfig);
const providerTypes = providers.map(({ type }) => type);
const projectIds = projects.map(({ projectId }) => projectId);
if (
new Set(providerTypes).size !== providerTypes.length ||
new Set(projectIds).size !== projectIds.length ||
providerTypes.some(
(value, index) => index > 0 && value <= providerTypes[index - 1]!,
) ||
projectIds.some(
(value, index) => index > 0 && value <= projectIds[index - 1]!,
) ||
projects.some(({ policy }) =>
policy.allowedProviders.some(
(provider) => !providerTypes.includes(provider),
),
)
) {
throw unavailable();
}
return Object.freeze({
schema: PROJECTED_MODEL_GATEWAY_AUTHORITY_SCHEMA,
providers: Object.freeze(providers),
projects: Object.freeze(projects),
});
}
export function canonicalProjectedModelGatewayAuthorityManifest(
value: ProjectedModelGatewayAuthorityManifest,
): Buffer {
return Buffer.from(
`${JSON.stringify(
normalizeProjectedModelGatewayAuthorityManifest(value),
)}\n`,
'utf8',
);
}
function remainsBelow(parent: string, candidate: string): boolean {
const suffix = relative(parent, candidate);
return (
suffix.length > 0 &&
!isAbsolute(suffix) &&
suffix !== '..' &&
!suffix.startsWith(`..${process.platform === 'win32' ? '\\' : '/'}`)
);
}
async function readManifest(
configuredFile: string,
): Promise<Readonly<ProjectedModelGatewayAuthorityManifest>> {
let handle: Awaited<ReturnType<typeof open>> | undefined;
let bytes: Buffer | undefined;
let canonical: Buffer | undefined;
try {
const configured = await lstat(configuredFile);
if (!configured.isFile() && !configured.isSymbolicLink())
throw unavailable();
const configuredParent = parse(configuredFile).dir;
const directParent = await lstat(configuredParent);
if (!directParent.isDirectory() || directParent.isSymbolicLink()) {
throw unavailable();
}
const parent = await realpath(configuredParent);
const target = await realpath(configuredFile);
if (!remainsBelow(parent, target)) throw unavailable();
handle = await open(
target,
constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0),
);
const before = await handle.stat();
if (
!before.isFile() ||
before.nlink !== 1 ||
before.size < 1 ||
before.size > MAX_CONFIG_BYTES ||
(before.mode & 0o222) !== 0 ||
(before.mode & 0o111) !== 0 ||
(before.mode & 0o007) !== 0 ||
(before.mode & 0o440) === 0
) {
throw unavailable();
}
bytes = await handle.readFile();
const after = await handle.stat();
if (
bytes.byteLength !== before.size ||
after.dev !== before.dev ||
after.ino !== before.ino ||
after.size !== before.size ||
after.mtimeMs !== before.mtimeMs ||
(await realpath(configuredFile)) !== target ||
(await realpath(configuredParent)) !== parent
) {
throw unavailable();
}
const parsed = JSON.parse(
new TextDecoder('utf8', { fatal: true }).decode(bytes),
);
const manifest = normalizeProjectedModelGatewayAuthorityManifest(
parsed as ProjectedModelGatewayAuthorityManifest,
);
canonical = canonicalProjectedModelGatewayAuthorityManifest(manifest);
if (!canonical.equals(bytes)) throw unavailable();
return manifest;
} catch (cause) {
throw cause instanceof ProjectedModelGatewayAuthorityUnavailableError
? cause
: unavailable(cause);
} finally {
bytes?.fill(0);
canonical?.fill(0);
await handle?.close().catch(() => undefined);
}
}
/** Loads one immutable, content-free provider/policy snapshot at activation. */
export async function loadProjectedModelGatewayProviderAuthority(
options: ProjectedModelGatewayAuthorityOptions,
): Promise<ModelGatewayProviderAuthority> {
if (
!options ||
typeof options !== 'object' ||
Array.isArray(options) ||
Object.keys(options).sort().join('\0') !== 'configFile\0credentials' ||
typeof options.credentials?.authorizationHeader !== 'function'
) {
throw unavailable();
}
const manifest = await readManifest(configFile(options.configFile));
const policies = new Map(
manifest.projects.map(({ projectId, policy }) => [projectId, policy]),
);
return Object.freeze({
providers: Object.freeze(
manifest.providers.map(
(provider) =>
new OpenAiCompatibleProvider({
...provider,
credentials: options.credentials,
}),
),
),
policies: Object.freeze({
async resolve(
context: Readonly<ModelInvocationContext>,
): Promise<Readonly<ModelInvocationPolicy>> {
const policy = policies.get(context.projectId);
if (!policy) throw unavailable();
return policy;
},
}),
});
}
@@ -0,0 +1,422 @@
import { Buffer } from 'node:buffer';
import {
MAX_MODEL_INPUT_BYTES,
MAX_MODEL_INVOCATION_MS,
MAX_MODEL_MESSAGE_BYTES,
MAX_MODEL_MESSAGES,
MAX_MODEL_OUTPUT_BYTES,
MAX_MODEL_OUTPUT_TOKENS,
MODEL_FINISH_REASONS,
MODEL_MESSAGE_ROLES,
type GenerateRequest,
type GenerateResult,
type ModelChunk,
type ModelInfo,
type ModelInvocationContext,
type ModelInvocationPolicy,
type ModelMessage,
type ModelUsage,
} from './model';
import { normalizeModelInvocationProjectQuotaPolicy } from '../usage/usageQuota';
const IDENTIFIER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
const REVISION_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
export class InvalidModelValueError extends TypeError {
readonly code = 'MODEL_VALUE_INVALID';
constructor(message: string) {
super(`Model value is invalid: ${message}`);
this.name = 'InvalidModelValueError';
}
}
function assertExactKeys(
value: object,
expected: readonly string[],
name: string,
): void {
const keys = Object.keys(value).sort();
const canonical = [...expected].sort();
if (
keys.length !== canonical.length ||
keys.some((key, index) => key !== canonical[index])
) {
throw new InvalidModelValueError(`${name} shape is invalid`);
}
}
function normalizeIdentifier(value: unknown, name: string): string {
if (typeof value !== 'string' || !IDENTIFIER_PATTERN.test(value)) {
throw new InvalidModelValueError(`${name} is invalid`);
}
return value;
}
function normalizeNonNegativeInteger(value: unknown, name: string): number {
if (!Number.isSafeInteger(value) || (value as number) < 0) {
throw new InvalidModelValueError(`${name} is invalid`);
}
return value as number;
}
function normalizePositiveInteger(
value: unknown,
name: string,
maximum: number,
): number {
const normalized = normalizeNonNegativeInteger(value, name);
if (normalized < 1 || normalized > maximum) {
throw new InvalidModelValueError(`${name} is out of range`);
}
return normalized;
}
function normalizeMessage(value: ModelMessage): Readonly<ModelMessage> {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new InvalidModelValueError('message must be an object');
}
assertExactKeys(value, ['role', 'content'], 'message');
if (!MODEL_MESSAGE_ROLES.includes(value.role)) {
throw new InvalidModelValueError('message role is invalid');
}
if (
typeof value.content !== 'string' ||
Buffer.byteLength(value.content, 'utf8') > MAX_MODEL_MESSAGE_BYTES
) {
throw new InvalidModelValueError('message content is invalid');
}
return Object.freeze({ role: value.role, content: value.content });
}
export function normalizeGenerateRequest(
value: GenerateRequest,
): Readonly<GenerateRequest> {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new InvalidModelValueError('request must be an object');
}
const expected = ['provider', 'model', 'messages', 'maxOutputTokens'];
if (Object.hasOwn(value, 'temperature')) expected.push('temperature');
assertExactKeys(value, expected, 'request');
const provider = normalizeIdentifier(value.provider, 'provider');
const model = normalizeIdentifier(value.model, 'model');
if (
!Array.isArray(value.messages) ||
value.messages.length < 1 ||
value.messages.length > MAX_MODEL_MESSAGES
) {
throw new InvalidModelValueError('messages are invalid');
}
const messages = Object.freeze(value.messages.map(normalizeMessage));
if (measureModelInputBytes(messages) > MAX_MODEL_INPUT_BYTES) {
throw new InvalidModelValueError('input exceeds the hard byte limit');
}
const maxOutputTokens = normalizePositiveInteger(
value.maxOutputTokens,
'maxOutputTokens',
MAX_MODEL_OUTPUT_TOKENS,
);
if (
value.temperature !== undefined &&
(typeof value.temperature !== 'number' ||
!Number.isFinite(value.temperature) ||
value.temperature < 0 ||
value.temperature > 2)
) {
throw new InvalidModelValueError('temperature is invalid');
}
return Object.freeze({
provider,
model,
messages,
maxOutputTokens,
...(value.temperature === undefined
? {}
: { temperature: value.temperature }),
});
}
export function normalizeModelInvocationContext(
value: ModelInvocationContext,
nowMs: number,
): Readonly<ModelInvocationContext> {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new InvalidModelValueError('context must be an object');
}
const expected = [
'projectId',
'runId',
'stepRunId',
'traceId',
'requestId',
'deadlineAtMs',
];
if (Object.hasOwn(value, 'signal')) expected.push('signal');
assertExactKeys(value, expected, 'context');
if (
!Number.isSafeInteger(nowMs) ||
!Number.isSafeInteger(value.deadlineAtMs) ||
value.deadlineAtMs <= nowMs ||
value.deadlineAtMs - nowMs > MAX_MODEL_INVOCATION_MS
) {
throw new InvalidModelValueError('deadlineAtMs is invalid');
}
if (
value.signal !== undefined &&
(!(value.signal instanceof AbortSignal) || value.signal.aborted)
) {
throw new InvalidModelValueError('signal is invalid');
}
return Object.freeze({
projectId: normalizeIdentifier(value.projectId, 'projectId'),
runId: normalizeIdentifier(value.runId, 'runId'),
stepRunId: normalizeIdentifier(value.stepRunId, 'stepRunId'),
traceId: normalizeIdentifier(value.traceId, 'traceId'),
requestId: normalizeIdentifier(value.requestId, 'requestId'),
deadlineAtMs: value.deadlineAtMs,
...(value.signal === undefined ? {} : { signal: value.signal }),
});
}
export function normalizeModelInvocationPolicy(
value: ModelInvocationPolicy,
): Readonly<ModelInvocationPolicy> {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new InvalidModelValueError('policy must be an object');
}
const expected = [
'revision',
'allowedProviders',
'allowedModels',
'maxInputBytes',
'maxOutputBytes',
'maxOutputTokens',
'maxTotalTokens',
'maxCostMicros',
'priceRevision',
];
if (Object.hasOwn(value, 'projectQuota')) expected.push('projectQuota');
assertExactKeys(value, expected, 'policy');
if (
typeof value.revision !== 'string' ||
!REVISION_PATTERN.test(value.revision)
) {
throw new InvalidModelValueError('policy revision is invalid');
}
const normalizeAllowlist = (
entries: readonly string[],
name: string,
): readonly string[] => {
if (!Array.isArray(entries) || entries.length < 1 || entries.length > 128) {
throw new InvalidModelValueError(`${name} is invalid`);
}
const normalized = entries.map((entry) => normalizeIdentifier(entry, name));
if (new Set(normalized).size !== normalized.length) {
throw new InvalidModelValueError(`${name} contains duplicates`);
}
return Object.freeze(normalized);
};
const maxInputBytes = normalizePositiveInteger(
value.maxInputBytes,
'maxInputBytes',
MAX_MODEL_INPUT_BYTES,
);
const maxOutputBytes = normalizePositiveInteger(
value.maxOutputBytes,
'maxOutputBytes',
MAX_MODEL_OUTPUT_BYTES,
);
const maxOutputTokens = normalizePositiveInteger(
value.maxOutputTokens,
'maxOutputTokens',
MAX_MODEL_OUTPUT_TOKENS,
);
const maxTotalTokens = normalizePositiveInteger(
value.maxTotalTokens,
'maxTotalTokens',
MAX_MODEL_OUTPUT_TOKENS * 16,
);
if (maxOutputTokens > maxTotalTokens) {
throw new InvalidModelValueError('maxOutputTokens exceeds maxTotalTokens');
}
if (
value.maxCostMicros !== null &&
(!Number.isSafeInteger(value.maxCostMicros) || value.maxCostMicros < 0)
) {
throw new InvalidModelValueError('maxCostMicros is invalid');
}
if (
value.priceRevision !== null &&
(typeof value.priceRevision !== 'string' ||
!REVISION_PATTERN.test(value.priceRevision))
) {
throw new InvalidModelValueError('priceRevision is invalid');
}
return Object.freeze({
revision: value.revision,
allowedProviders: normalizeAllowlist(
value.allowedProviders,
'allowedProviders',
),
allowedModels: normalizeAllowlist(value.allowedModels, 'allowedModels'),
maxInputBytes,
maxOutputBytes,
maxOutputTokens,
maxTotalTokens,
maxCostMicros: value.maxCostMicros,
priceRevision: value.priceRevision,
...(value.projectQuota === undefined
? {}
: {
projectQuota: normalizeModelInvocationProjectQuotaPolicy(
value.projectQuota,
),
}),
});
}
export function normalizeModelUsage(value: ModelUsage): Readonly<ModelUsage> {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new InvalidModelValueError('usage must be an object');
}
const expected = ['inputTokens', 'outputTokens', 'totalTokens'];
if (Object.hasOwn(value, 'costMicros')) expected.push('costMicros');
assertExactKeys(value, expected, 'usage');
const inputTokens = normalizeNonNegativeInteger(
value.inputTokens,
'inputTokens',
);
const outputTokens = normalizeNonNegativeInteger(
value.outputTokens,
'outputTokens',
);
const totalTokens = normalizeNonNegativeInteger(
value.totalTokens,
'totalTokens',
);
if (totalTokens !== inputTokens + outputTokens) {
throw new InvalidModelValueError('totalTokens is inconsistent');
}
if (
value.costMicros !== undefined &&
(!Number.isSafeInteger(value.costMicros) || value.costMicros < 0)
) {
throw new InvalidModelValueError('costMicros is invalid');
}
return Object.freeze({
inputTokens,
outputTokens,
totalTokens,
...(value.costMicros === undefined ? {} : { costMicros: value.costMicros }),
});
}
export function normalizeGenerateResult(
value: GenerateResult,
): Readonly<GenerateResult> {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new InvalidModelValueError('result must be an object');
}
assertExactKeys(
value,
['provider', 'model', 'text', 'finishReason', 'usage'],
'result',
);
if (
typeof value.text !== 'string' ||
Buffer.byteLength(value.text, 'utf8') > MAX_MODEL_OUTPUT_BYTES
) {
throw new InvalidModelValueError('result text is invalid');
}
if (!MODEL_FINISH_REASONS.includes(value.finishReason)) {
throw new InvalidModelValueError('finishReason is invalid');
}
return Object.freeze({
provider: normalizeIdentifier(value.provider, 'result provider'),
model: normalizeIdentifier(value.model, 'result model'),
text: value.text,
finishReason: value.finishReason,
usage: normalizeModelUsage(value.usage),
});
}
export function normalizeModelChunk(value: ModelChunk): Readonly<ModelChunk> {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new InvalidModelValueError('chunk must be an object');
}
const expected = ['delta'];
if (Object.hasOwn(value, 'finishReason')) expected.push('finishReason');
if (Object.hasOwn(value, 'usage')) expected.push('usage');
assertExactKeys(value, expected, 'chunk');
if (
typeof value.delta !== 'string' ||
Buffer.byteLength(value.delta, 'utf8') > MAX_MODEL_OUTPUT_BYTES
) {
throw new InvalidModelValueError('chunk delta is invalid');
}
if (
value.finishReason !== undefined &&
!MODEL_FINISH_REASONS.includes(value.finishReason)
) {
throw new InvalidModelValueError('chunk finishReason is invalid');
}
return Object.freeze({
delta: value.delta,
...(value.finishReason === undefined
? {}
: { finishReason: value.finishReason }),
...(value.usage === undefined
? {}
: { usage: normalizeModelUsage(value.usage) }),
});
}
export function normalizeModelInfo(value: ModelInfo): Readonly<ModelInfo> {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new InvalidModelValueError('model info must be an object');
}
const expected = ['id'];
if (Object.hasOwn(value, 'displayName')) expected.push('displayName');
if (Object.hasOwn(value, 'contextWindowTokens')) {
expected.push('contextWindowTokens');
}
assertExactKeys(value, expected, 'model info');
const id = normalizeIdentifier(value.id, 'model id');
if (
value.displayName !== undefined &&
(typeof value.displayName !== 'string' ||
value.displayName.length < 1 ||
Buffer.byteLength(value.displayName, 'utf8') > 256)
) {
throw new InvalidModelValueError('model displayName is invalid');
}
if (
value.contextWindowTokens !== undefined &&
(!Number.isSafeInteger(value.contextWindowTokens) ||
value.contextWindowTokens < 1)
) {
throw new InvalidModelValueError('contextWindowTokens is invalid');
}
return Object.freeze({
id,
...(value.displayName === undefined
? {}
: { displayName: value.displayName }),
...(value.contextWindowTokens === undefined
? {}
: { contextWindowTokens: value.contextWindowTokens }),
});
}
export function measureModelInputBytes(
messages: readonly ModelMessage[],
): number {
return messages.reduce(
(total, message) =>
total +
Buffer.byteLength(message.role, 'utf8') +
Buffer.byteLength(message.content, 'utf8'),
0,
);
}
@@ -0,0 +1,656 @@
import {
transitionStepRunMutation,
type StepRunStatus,
} from '@qinglong/runtime-core/step-run';
import type {
ModelInvocationAuditDisposition,
ModelInvocationAuditRecord,
ModelInvocationAuditSink,
ModelUsage,
} from '../model-gateway/model';
import {
MAX_MODEL_INVOCATION_RECOVERY_PAGE_SIZE,
ModelInvocationConflictError,
ModelInvocationRepositoryUnavailableError,
createModelInvocationCompletionCommand,
createModelInvocationMutationIdentity,
createModelInvocationStartCommand,
normalizeModelInvocationCompletionRecord,
normalizeModelInvocationStartRecord,
type ModelInvocationCompletionRecord,
type ModelInvocationRepository,
type ModelInvocationStartRecord,
} from './modelInvocation';
import {
isQuotaAwareModelInvocationRepository,
normalizeModelInvocationQuotaAdmission,
type ModelInvocationQuotaAdmission,
} from '../usage/usageQuota';
import {
isPricingAwareModelInvocationRepository,
normalizeModelInvocationPriceQuote,
type ModelInvocationPriceQuote,
} from '../pricing/pricing';
import {
isPluginPackagePromptOutputCompletionRepository,
type PluginPackagePromptOutputCompletionRepository,
} from '../prompt-output/pluginPackagePromptOutputCompletion';
import {
PluginPackagePromptOutputArtifactConflictError,
normalizePluginPackagePromptOutputArtifact,
pluginPackagePromptOutputArtifactReference,
type PluginPackagePromptOutputArtifact,
type PluginPackagePromptOutputArtifactReference,
} from '../prompt-output/pluginPackagePromptOutputArtifact';
const MAX_COORDINATOR_ATTEMPTS = 3;
interface CompletionTransition {
readonly to: StepRunStatus;
readonly outputRef?: string;
readonly resultCode?: string;
readonly errorSummary?: string;
}
export interface ModelInvocationRecoverySummary {
readonly observedAtMs: number;
readonly scanned: number;
readonly recovered: number;
readonly alreadyCompleted: number;
readonly failed: number;
readonly hasMore: boolean;
}
function sameUsage(
left: Readonly<ModelUsage> | null,
right: Readonly<ModelUsage> | null,
): boolean {
return JSON.stringify(left) === JSON.stringify(right);
}
function assertStartMatchesAudit(
startValue: ModelInvocationStartRecord,
audit: Readonly<ModelInvocationAuditRecord>,
): Readonly<ModelInvocationStartRecord> {
const start = normalizeModelInvocationStartRecord(startValue);
if (
start.invocationId !== audit.requestId ||
start.projectId !== audit.projectId ||
start.runId !== audit.runId ||
start.stepRunId !== audit.stepRunId ||
start.traceId !== audit.traceId ||
start.provider !== audit.provider ||
start.model !== audit.model ||
start.policyRevision !== audit.policyRevision ||
start.requestDigest !== audit.requestDigest ||
start.inputBytes !== audit.inputBytes ||
start.maxOutputTokens !== audit.maxOutputTokens ||
start.deadlineAtMs !== audit.deadlineAtMs
) {
throw new ModelInvocationConflictError();
}
return start;
}
function completionTransition(
audit: Readonly<ModelInvocationAuditRecord>,
successOutputRef?: string,
): Readonly<CompletionTransition> {
if (audit.phase === 'completed') {
return Object.freeze({
to: 'succeeded',
outputRef: successOutputRef ?? `model-invocation:${audit.requestId}`,
});
}
if (audit.errorCode === 'MODEL_INVOCATION_DEADLINE_EXCEEDED') {
return Object.freeze({
to: 'timed_out',
resultCode: 'model_deadline_exceeded',
errorSummary: 'Model invocation deadline exceeded',
});
}
if (
audit.errorCode === 'MODEL_INVOCATION_ABORTED' ||
audit.errorCode === 'MODEL_STREAM_CANCELLED' ||
audit.errorCode === 'MODEL_INVOCATION_OUTCOME_UNKNOWN'
) {
return Object.freeze({
to: 'lost',
resultCode: 'model_outcome_unknown',
errorSummary: 'Model invocation outcome is unknown',
});
}
return Object.freeze({
to: 'failed',
resultCode: 'model_provider_failed',
errorSummary: 'Model invocation failed',
});
}
function expectedOutcome(
transition: Readonly<CompletionTransition>,
): ModelInvocationCompletionRecord['outcome'] {
if (transition.to === 'succeeded') return 'succeeded';
if (transition.to === 'timed_out') return 'timed_out';
if (transition.to === 'lost') return 'outcome_unknown';
return 'failed';
}
function assertCompletionMatchesAudit(
completionValue: ModelInvocationCompletionRecord,
start: Readonly<ModelInvocationStartRecord>,
audit: Readonly<ModelInvocationAuditRecord>,
): Readonly<ModelInvocationCompletionRecord> {
const completion = normalizeModelInvocationCompletionRecord(completionValue);
const transition = completionTransition(audit);
if (
completion.invocationId !== start.invocationId ||
completion.projectId !== start.projectId ||
completion.runId !== start.runId ||
completion.stepRunId !== start.stepRunId ||
completion.traceId !== start.traceId ||
completion.startDigest !== start.startDigest ||
completion.outcome !== expectedOutcome(transition) ||
completion.outputBytes !== audit.outputBytes ||
!sameUsage(completion.usage, audit.usage) ||
completion.errorCode !== audit.errorCode
) {
throw new ModelInvocationConflictError();
}
return completion;
}
function identity(record: Readonly<ModelInvocationAuditRecord>): Readonly<{
projectId: string;
runId: string;
stepRunId: string;
}> {
return Object.freeze({
projectId: record.projectId,
runId: record.runId,
stepRunId: record.stepRunId,
});
}
export class DurableModelInvocationCoordinator
implements ModelInvocationAuditSink
{
constructor(private readonly repository: ModelInvocationRepository) {
if (
!repository ||
typeof repository.findStart !== 'function' ||
typeof repository.findCompletion !== 'function' ||
typeof repository.readAuthority !== 'function' ||
typeof repository.admit !== 'function' ||
typeof repository.complete !== 'function'
) {
throw new ModelInvocationRepositoryUnavailableError();
}
}
async record(
record: Readonly<ModelInvocationAuditRecord>,
): Promise<Readonly<ModelInvocationAuditDisposition>> {
return record.phase === 'admitted'
? this.#admit(record)
: this.#complete(record);
}
async recordWithQuota(
record: Readonly<ModelInvocationAuditRecord>,
admissionValue: Readonly<ModelInvocationQuotaAdmission>,
): Promise<Readonly<ModelInvocationAuditDisposition>> {
const admission = normalizeModelInvocationQuotaAdmission(admissionValue);
if (
record.phase !== 'admitted' ||
record.requestId !== admission.invocationId ||
record.projectId !== admission.projectId ||
record.policyRevision !== admission.modelPolicyRevision ||
!isQuotaAwareModelInvocationRepository(this.repository)
) {
throw new ModelInvocationConflictError();
}
return this.#admit(record, admission);
}
async recordWithPricing(
record: Readonly<ModelInvocationAuditRecord>,
quoteValue: Readonly<ModelInvocationPriceQuote>,
admissionValue?: Readonly<ModelInvocationQuotaAdmission>,
): Promise<Readonly<ModelInvocationAuditDisposition>> {
const quote = normalizeModelInvocationPriceQuote(quoteValue);
const admission =
admissionValue === undefined
? undefined
: normalizeModelInvocationQuotaAdmission(admissionValue);
if (
record.phase !== 'admitted' ||
record.requestId !== quote.invocationId ||
record.projectId !== quote.projectId ||
record.policyRevision !== quote.modelPolicyRevision ||
record.provider !== quote.provider ||
record.model !== quote.model ||
(admission !== undefined &&
(admission.invocationId !== quote.invocationId ||
admission.projectId !== quote.projectId ||
admission.modelPolicyRevision !== quote.modelPolicyRevision)) ||
!isPricingAwareModelInvocationRepository(this.repository)
) {
throw new ModelInvocationConflictError();
}
return this.#admit(record, admission, quote);
}
async recordWithPromptOutputArtifact(
record: Readonly<ModelInvocationAuditRecord>,
artifactValue: PluginPackagePromptOutputArtifact,
): Promise<
Readonly<{
status: 'created' | 'existing';
reference: Readonly<PluginPackagePromptOutputArtifactReference>;
}>
> {
const artifact = normalizePluginPackagePromptOutputArtifact(artifactValue);
if (
record.phase !== 'completed' ||
record.requestId !== artifact.invocationId ||
record.projectId !== artifact.projectId ||
record.runId !== artifact.runId ||
record.stepRunId !== artifact.stepRunId ||
record.provider !== artifact.provider ||
record.model !== artifact.model ||
record.outputBytes !== artifact.outputBytes ||
!isPluginPackagePromptOutputCompletionRepository(this.repository)
) {
throw new PluginPackagePromptOutputArtifactConflictError();
}
const result = await this.#complete(record, artifact);
if (!result.reference) {
throw new PluginPackagePromptOutputArtifactConflictError();
}
return Object.freeze({
status: result.status,
reference: result.reference,
});
}
async #admit(
audit: Readonly<ModelInvocationAuditRecord>,
admission?: Readonly<ModelInvocationQuotaAdmission>,
quote?: Readonly<ModelInvocationPriceQuote>,
): Promise<Readonly<ModelInvocationAuditDisposition>> {
const existing = await this.repository.findStart(audit.requestId);
if (existing) {
assertStartMatchesAudit(existing, audit);
if (admission) {
if (!isQuotaAwareModelInvocationRepository(this.repository)) {
throw new ModelInvocationConflictError();
}
const reservation = await this.repository.findQuotaReservation(
audit.requestId,
);
if (
!reservation ||
reservation.admissionDigest !== admission.admissionDigest
) {
throw new ModelInvocationConflictError();
}
}
if (quote) {
if (!isPricingAwareModelInvocationRepository(this.repository)) {
throw new ModelInvocationConflictError();
}
const storedQuote = await this.repository.findPriceQuote(
audit.requestId,
);
if (!storedQuote || storedQuote.quoteDigest !== quote.quoteDigest) {
throw new ModelInvocationConflictError();
}
}
return Object.freeze({ status: 'existing' });
}
for (let attempt = 0; attempt < MAX_COORDINATOR_ATTEMPTS; attempt += 1) {
const authority = await this.repository.readAuthority(identity(audit));
if (
!authority ||
authority.stepRun.status !== 'ready' ||
authority.stepRun.kind !== 'model'
) {
throw new ModelInvocationConflictError();
}
const mutationIdentity = createModelInvocationMutationIdentity(
audit.requestId,
'start',
);
const command = createModelInvocationStartCommand(
audit,
transitionStepRunMutation(
authority.stepRun,
{
expectedVersion: authority.stepRun.version,
expectedDigest: authority.stepRun.stepRunDigest,
mutationId: mutationIdentity.mutationId,
to: 'running',
atMs: audit.occurredAtMs,
},
{
expectedRunVersion: authority.runVersion,
expectedRunEventSequence: authority.runEventSequence,
eventId: mutationIdentity.eventId,
dedupeKey: mutationIdentity.dedupeKey,
actor: { type: 'executor', id: 'model-gateway' },
},
),
);
try {
const result = quote
? await (() => {
if (!isPricingAwareModelInvocationRepository(this.repository)) {
throw new ModelInvocationConflictError();
}
return this.repository.admitWithPricing(
command,
quote,
admission,
);
})()
: admission
? await (() => {
if (!isQuotaAwareModelInvocationRepository(this.repository)) {
throw new ModelInvocationConflictError();
}
return this.repository.admitWithQuota(command, admission);
})()
: await this.repository.admit(command);
return Object.freeze({ status: result.status });
} catch (error) {
const stored = await this.#startAfterFailure(
audit,
error,
admission,
quote,
);
if (stored) return Object.freeze({ status: 'existing' });
if (
!(error instanceof ModelInvocationConflictError) ||
attempt + 1 >= MAX_COORDINATOR_ATTEMPTS
) {
throw error;
}
}
}
throw new ModelInvocationConflictError();
}
async #complete(
audit: Readonly<ModelInvocationAuditRecord>,
artifactValue?: Readonly<PluginPackagePromptOutputArtifact>,
): Promise<
Readonly<
ModelInvocationAuditDisposition & {
reference?: Readonly<PluginPackagePromptOutputArtifactReference>;
}
>
> {
const artifact = artifactValue
? normalizePluginPackagePromptOutputArtifact(artifactValue)
: undefined;
const artifactRepository = artifact
? (this.repository as ModelInvocationRepository &
PluginPackagePromptOutputCompletionRepository)
: undefined;
const startValue = await this.repository.findStart(audit.requestId);
if (!startValue) throw new ModelInvocationConflictError();
const start = assertStartMatchesAudit(startValue, audit);
const existing = await this.repository.findCompletion(audit.requestId);
if (existing) {
assertCompletionMatchesAudit(existing, start, audit);
if (artifact && artifactRepository) {
const stored = await artifactRepository.findPromptOutputArtifact(
artifact.artifactId,
);
if (!stored || JSON.stringify(stored) !== JSON.stringify(artifact)) {
throw new PluginPackagePromptOutputArtifactConflictError();
}
return Object.freeze({
status: 'existing' as const,
reference: pluginPackagePromptOutputArtifactReference(stored),
});
}
return Object.freeze({ status: 'existing' as const });
}
for (let attempt = 0; attempt < MAX_COORDINATOR_ATTEMPTS; attempt += 1) {
const authority = await this.repository.readAuthority(identity(audit));
if (
!authority ||
authority.stepRun.status !== 'running' ||
authority.stepRun.version !== start.startedStepRunVersion ||
authority.stepRun.stepRunDigest !== start.startedStepRunDigest
) {
throw new ModelInvocationConflictError();
}
const transition = completionTransition(audit, artifact?.artifactId);
const mutationIdentity = createModelInvocationMutationIdentity(
audit.requestId,
'completion',
);
const command = createModelInvocationCompletionCommand(
start,
audit,
transitionStepRunMutation(
authority.stepRun,
{
expectedVersion: authority.stepRun.version,
expectedDigest: authority.stepRun.stepRunDigest,
mutationId: mutationIdentity.mutationId,
to: transition.to,
atMs: audit.occurredAtMs,
...(transition.outputRef === undefined
? {}
: { outputRef: transition.outputRef }),
...(transition.resultCode === undefined
? {}
: { resultCode: transition.resultCode }),
...(transition.errorSummary === undefined
? {}
: { errorSummary: transition.errorSummary }),
},
{
expectedRunVersion: authority.runVersion,
expectedRunEventSequence: authority.runEventSequence,
eventId: mutationIdentity.eventId,
dedupeKey: mutationIdentity.dedupeKey,
actor: { type: 'executor', id: 'model-gateway' },
},
),
artifact?.artifactId,
);
try {
const pricingAware = isPricingAwareModelInvocationRepository(
this.repository,
);
const quote = pricingAware
? await this.repository.findPriceQuote(audit.requestId)
: null;
const quotaAware = isQuotaAwareModelInvocationRepository(
this.repository,
);
const reservation = quotaAware
? await this.repository.findQuotaReservation(audit.requestId)
: null;
const result =
artifactRepository && artifact
? await artifactRepository.completeWithPromptOutputArtifact(
command,
artifact,
)
: pricingAware && quote
? await this.repository.completeWithPricing(command)
: quotaAware && reservation
? await this.repository.completeWithQuota(command)
: await this.repository.complete(command);
return Object.freeze({
status: result.status,
...(artifact && artifactRepository
? {
reference: pluginPackagePromptOutputArtifactReference(artifact),
}
: {}),
});
} catch (error) {
const stored = await this.#completionAfterFailure(
start,
audit,
error,
artifact,
artifactRepository,
);
if (stored) {
return Object.freeze({
status: 'existing' as const,
...(artifact
? {
reference:
pluginPackagePromptOutputArtifactReference(artifact),
}
: {}),
});
}
if (
!(error instanceof ModelInvocationConflictError) ||
attempt + 1 >= MAX_COORDINATOR_ATTEMPTS
) {
throw error;
}
}
}
throw new ModelInvocationConflictError();
}
async #startAfterFailure(
audit: Readonly<ModelInvocationAuditRecord>,
original: unknown,
admission?: Readonly<ModelInvocationQuotaAdmission>,
quote?: Readonly<ModelInvocationPriceQuote>,
): Promise<Readonly<ModelInvocationStartRecord> | null> {
try {
const stored = await this.repository.findStart(audit.requestId);
if (!stored) return null;
const start = assertStartMatchesAudit(stored, audit);
if (admission) {
if (!isQuotaAwareModelInvocationRepository(this.repository)) {
throw original;
}
const reservation = await this.repository.findQuotaReservation(
audit.requestId,
);
if (
!reservation ||
reservation.admissionDigest !== admission.admissionDigest
) {
throw original;
}
}
if (quote) {
if (!isPricingAwareModelInvocationRepository(this.repository)) {
throw original;
}
const storedQuote = await this.repository.findPriceQuote(
audit.requestId,
);
if (!storedQuote || storedQuote.quoteDigest !== quote.quoteDigest) {
throw original;
}
}
return start;
} catch {
throw original;
}
}
async #completionAfterFailure(
start: Readonly<ModelInvocationStartRecord>,
audit: Readonly<ModelInvocationAuditRecord>,
original: unknown,
artifact?: Readonly<PluginPackagePromptOutputArtifact>,
artifactRepository?: PluginPackagePromptOutputCompletionRepository,
): Promise<Readonly<ModelInvocationCompletionRecord> | null> {
try {
const stored = await this.repository.findCompletion(audit.requestId);
if (!stored) return null;
const completion = assertCompletionMatchesAudit(stored, start, audit);
if (artifact) {
if (!artifactRepository) throw original;
const storedArtifact =
await artifactRepository.findPromptOutputArtifact(
artifact.artifactId,
);
if (
!storedArtifact ||
JSON.stringify(storedArtifact) !== JSON.stringify(artifact)
) {
throw original;
}
}
return completion;
} catch {
throw original;
}
}
}
export class DurableModelInvocationRecovery {
constructor(
private readonly repository: ModelInvocationRepository,
private readonly coordinator = new DurableModelInvocationCoordinator(
repository,
),
) {}
async recover(
limit = MAX_MODEL_INVOCATION_RECOVERY_PAGE_SIZE,
): Promise<Readonly<ModelInvocationRecoverySummary>> {
const page = await this.repository.listIncomplete(limit);
let recovered = 0;
let alreadyCompleted = 0;
let failed = 0;
for (const start of page.candidates) {
try {
const result = await this.coordinator.record(
Object.freeze({
phase: 'failed',
projectId: start.projectId,
runId: start.runId,
stepRunId: start.stepRunId,
traceId: start.traceId,
requestId: start.invocationId,
provider: start.provider,
model: start.model,
policyRevision: start.policyRevision,
requestDigest: start.requestDigest,
deadlineAtMs: start.deadlineAtMs,
inputBytes: start.inputBytes,
maxOutputTokens: start.maxOutputTokens,
outputBytes: 0,
usage: null,
errorCode: 'MODEL_INVOCATION_OUTCOME_UNKNOWN',
occurredAtMs: page.observedAtMs,
}),
);
if (result.status === 'created') recovered += 1;
else alreadyCompleted += 1;
} catch {
failed += 1;
}
}
return Object.freeze({
observedAtMs: page.observedAtMs,
scanned: page.candidates.length,
recovered,
alreadyCompleted,
failed,
hasMore: page.hasMore,
});
}
}
@@ -0,0 +1,305 @@
import type { DatabaseSync } from 'node:sqlite';
import {
normalizeModelInvocationPriceQuote,
type ModelInvocationPriceQuote,
} from '../../pricing/pricing';
import {
ModelInvocationProjectQuotaExceededError,
createModelInvocationQuotaReservation,
normalizeModelInvocationQuotaAdmission,
type ModelInvocationQuotaAdmission,
type ModelInvocationQuotaReservation,
} from '../../usage/usageQuota';
import {
ModelInvocationConflictError,
normalizeModelInvocationStartCommand,
type CommitModelInvocationResult,
type ModelInvocationStartCommand,
type ModelInvocationStartRecord,
} from '../modelInvocation';
import type { LocalModelInvocationOperationAuthority, Row } from './authority';
import {
assertLocalFeatureActive,
enqueueLocalModelInvocation,
integer,
unavailable,
} from './authority';
import { parsePriceQuote, parseQuotaReservation, parseStart } from './codec';
import {
applyMutation,
assertCurrent,
insertPriceQuote,
insertQuotaReservation,
insertStart,
quotaWindowUsage,
} from './mutations';
import { priceQuoteRows, quotaReservationRows, startRows } from './queries';
export function admitOperation(
authority: LocalModelInvocationOperationAuthority,
client: DatabaseSync,
commandValue: ModelInvocationStartCommand,
): Promise<Readonly<CommitModelInvocationResult<ModelInvocationStartRecord>>> {
const command = normalizeModelInvocationStartCommand(commandValue);
const start = command.start;
return enqueueLocalModelInvocation(authority, () => {
let began = false;
try {
client.exec('BEGIN IMMEDIATE');
began = true;
const existing = startRows(
client,
`start.invocation_id = ? OR
start.mutation_id = ? OR start.run_event_id = ?`,
[start.invocationId, start.stepRunMutationId, start.runEventId],
);
if (existing.length > 1) throw new ModelInvocationConflictError();
if (existing[0]) {
const stored = parseStart(existing[0]);
if (JSON.stringify(stored) !== JSON.stringify(start)) {
throw new ModelInvocationConflictError();
}
client.exec('COMMIT');
began = false;
return Object.freeze({ status: 'existing' as const, record: stored });
}
assertLocalFeatureActive(client);
assertCurrent(client, command.stepRunMutation, start.projectId);
applyMutation(client, command.stepRunMutation);
insertStart(client, start);
client.exec('COMMIT');
began = false;
return Object.freeze({ status: 'created' as const, record: start });
} catch (error) {
if (began && client.isTransaction) {
try {
client.exec('ROLLBACK');
} catch {
// Preserve the original transaction failure.
}
}
throw error;
}
});
}
export function admitWithQuotaOperation(
authority: LocalModelInvocationOperationAuthority,
client: DatabaseSync,
commandValue: ModelInvocationStartCommand,
admissionValue: ModelInvocationQuotaAdmission,
): Promise<Readonly<CommitModelInvocationResult<ModelInvocationStartRecord>>> {
const command = normalizeModelInvocationStartCommand(commandValue);
const admission = normalizeModelInvocationQuotaAdmission(admissionValue);
const start = command.start;
if (
admission.invocationId !== start.invocationId ||
admission.projectId !== start.projectId ||
admission.modelPolicyRevision !== start.policyRevision
) {
throw new ModelInvocationConflictError();
}
return enqueueLocalModelInvocation(authority, () => {
let began = false;
try {
client.exec('BEGIN IMMEDIATE');
began = true;
const existing = startRows(
client,
`start.invocation_id = ? OR
start.mutation_id = ? OR start.run_event_id = ?`,
[start.invocationId, start.stepRunMutationId, start.runEventId],
);
if (existing.length > 1) throw new ModelInvocationConflictError();
if (existing[0]) {
const stored = parseStart(existing[0]);
const reservations = quotaReservationRows(
client,
'reservation.invocation_id = ?',
[start.invocationId],
);
if (
JSON.stringify(stored) !== JSON.stringify(start) ||
reservations.length !== 1 ||
parseQuotaReservation(reservations[0]!).admissionDigest !==
admission.admissionDigest
) {
throw new ModelInvocationConflictError();
}
client.exec('COMMIT');
began = false;
return Object.freeze({ status: 'existing' as const, record: stored });
}
assertLocalFeatureActive(client);
const observed = client
.prepare(
`SELECT CAST(unixepoch('subsec') * 1000 AS INTEGER)
AS "observedAtMs"`,
)
.get() as Row | undefined;
if (!observed) throw unavailable();
const reservation = createModelInvocationQuotaReservation(
admission,
integer(observed, 'observedAtMs'),
);
const usage = quotaWindowUsage(
client,
start.projectId,
reservation.windowStartMs,
reservation.windowMs,
);
if (
usage.invocationCount + 1 > reservation.maxInvocations ||
usage.effectiveTokens + reservation.reservedTokens >
reservation.maxTokens ||
(reservation.maxCostMicros !== null &&
(usage.unknownCostInvocations !== 0 ||
usage.effectiveCostMicros + reservation.reservedCostMicros! >
reservation.maxCostMicros))
) {
throw new ModelInvocationProjectQuotaExceededError();
}
assertCurrent(client, command.stepRunMutation, start.projectId);
applyMutation(client, command.stepRunMutation);
insertStart(client, start);
insertQuotaReservation(client, reservation);
client.exec('COMMIT');
began = false;
return Object.freeze({ status: 'created' as const, record: start });
} catch (error) {
if (began && client.isTransaction) {
try {
client.exec('ROLLBACK');
} catch {
// Preserve the original transaction failure.
}
}
throw error;
}
});
}
export function admitWithPricingOperation(
authority: LocalModelInvocationOperationAuthority,
client: DatabaseSync,
commandValue: ModelInvocationStartCommand,
quoteValue: ModelInvocationPriceQuote,
admissionValue?: ModelInvocationQuotaAdmission,
): Promise<Readonly<CommitModelInvocationResult<ModelInvocationStartRecord>>> {
const command = normalizeModelInvocationStartCommand(commandValue);
const quote = normalizeModelInvocationPriceQuote(quoteValue);
const admission =
admissionValue === undefined
? undefined
: normalizeModelInvocationQuotaAdmission(admissionValue);
const start = command.start;
if (
quote.invocationId !== start.invocationId ||
quote.projectId !== start.projectId ||
quote.modelPolicyRevision !== start.policyRevision ||
quote.provider !== start.provider ||
quote.model !== start.model ||
quote.maxOutputTokens !== start.maxOutputTokens ||
(admission !== undefined &&
(admission.invocationId !== start.invocationId ||
admission.projectId !== start.projectId ||
admission.modelPolicyRevision !== start.policyRevision ||
(admission.maxCostMicros !== null &&
admission.reservedCostMicros !== quote.reservedCostMicros)))
) {
throw new ModelInvocationConflictError();
}
return enqueueLocalModelInvocation(authority, () => {
let began = false;
try {
client.exec('BEGIN IMMEDIATE');
began = true;
const existing = startRows(
client,
`start.invocation_id = ? OR
start.mutation_id = ? OR start.run_event_id = ?`,
[start.invocationId, start.stepRunMutationId, start.runEventId],
);
if (existing.length > 1) throw new ModelInvocationConflictError();
if (existing[0]) {
const stored = parseStart(existing[0]);
const quotes = priceQuoteRows(client, 'quote.invocation_id = ?', [
start.invocationId,
]);
const reservations = quotaReservationRows(
client,
'reservation.invocation_id = ?',
[start.invocationId],
);
if (
JSON.stringify(stored) !== JSON.stringify(start) ||
quotes.length !== 1 ||
JSON.stringify(parsePriceQuote(quotes[0]!)) !==
JSON.stringify(quote) ||
reservations.length !== (admission ? 1 : 0) ||
(admission !== undefined &&
parseQuotaReservation(reservations[0]!).admissionDigest !==
admission.admissionDigest)
) {
throw new ModelInvocationConflictError();
}
client.exec('COMMIT');
began = false;
return Object.freeze({ status: 'existing' as const, record: stored });
}
assertLocalFeatureActive(client);
let reservation: Readonly<ModelInvocationQuotaReservation> | undefined;
if (admission) {
const observed = client
.prepare(
`SELECT CAST(unixepoch('subsec') * 1000 AS INTEGER)
AS "observedAtMs"`,
)
.get() as Row | undefined;
if (!observed) throw unavailable();
reservation = createModelInvocationQuotaReservation(
admission,
integer(observed, 'observedAtMs'),
);
const usage = quotaWindowUsage(
client,
start.projectId,
reservation.windowStartMs,
reservation.windowMs,
);
if (
usage.invocationCount + 1 > reservation.maxInvocations ||
usage.effectiveTokens + reservation.reservedTokens >
reservation.maxTokens ||
(reservation.maxCostMicros !== null &&
(usage.unknownCostInvocations !== 0 ||
usage.effectiveCostMicros + reservation.reservedCostMicros! >
reservation.maxCostMicros))
) {
throw new ModelInvocationProjectQuotaExceededError();
}
}
assertCurrent(client, command.stepRunMutation, start.projectId);
applyMutation(client, command.stepRunMutation);
insertStart(client, start);
insertPriceQuote(client, quote);
if (reservation) {
insertQuotaReservation(client, reservation);
}
client.exec('COMMIT');
began = false;
return Object.freeze({ status: 'created' as const, record: start });
} catch (error) {
if (began && client.isTransaction) {
try {
client.exec('ROLLBACK');
} catch {
// Preserve the original transaction failure.
}
}
throw error;
}
});
}
@@ -0,0 +1,161 @@
import type { DatabaseSync } from 'node:sqlite';
import { assertLocalModelInvocationFeatureActive } from '../../feature-activation/localModelInvocationFeatureActivation';
import {
PluginPackagePromptOutputArtifactConflictError,
PluginPackagePromptOutputArtifactUnavailableError,
} from '../../prompt-output/pluginPackagePromptOutputArtifact';
import { ModelInvocationUsageSummaryLimitExceededError } from '../../usage/usageLedger';
import { ModelInvocationProjectQuotaExceededError } from '../../usage/usageQuota';
import {
MAX_MODEL_INVOCATION_RECOVERY_PAGE_SIZE,
ModelInvocationConflictError,
ModelInvocationRepositoryUnavailableError,
} from '../modelInvocation';
export type Row = Record<string, unknown>;
export interface LocalModelInvocationOperationAuthority {
readonly client: DatabaseSync;
enqueue<T>(
work: () => Promise<T>,
rejection: (reason: 'closed' | 'busy') => Error,
): Promise<T>;
}
export const IDENTIFIER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
export const TERMINAL_RUN_STATUSES = new Set([
'succeeded',
'failed',
'cancelled',
'timed_out',
]);
export function unavailable(
cause?: unknown,
): ModelInvocationRepositoryUnavailableError {
return new ModelInvocationRepositoryUnavailableError({
cause: cause instanceof Error ? cause : undefined,
});
}
export function assertLocalFeatureActive(client: DatabaseSync): void {
try {
assertLocalModelInvocationFeatureActive(client);
} catch (error) {
throw unavailable(error);
}
}
export function identifier(value: unknown): string {
if (typeof value !== 'string' || !IDENTIFIER_PATTERN.test(value)) {
throw unavailable();
}
return value;
}
export function text(row: Row, key: string): string {
const value = row[key];
if (typeof value !== 'string') throw unavailable();
return value;
}
export function integer(row: Row, key: string): number {
const value = row[key];
if (!Number.isSafeInteger(value) || (value as number) < 0) {
throw unavailable();
}
return value as number;
}
export function nullableInteger(row: Row, key: string): number | null {
return row[key] === null ? null : integer(row, key);
}
export function recoveryLimit(value: number): number {
if (
!Number.isSafeInteger(value) ||
value < 1 ||
value > MAX_MODEL_INVOCATION_RECOVERY_PAGE_SIZE
) {
throw unavailable();
}
return value;
}
export function sqliteCode(error: unknown): string {
if (!error || typeof error !== 'object') return '';
const value = (error as { code?: unknown }).code;
return typeof value === 'string' ? value : '';
}
export function mapStorageError(error: unknown): Error {
if (
error instanceof ModelInvocationConflictError ||
error instanceof ModelInvocationRepositoryUnavailableError ||
error instanceof ModelInvocationUsageSummaryLimitExceededError ||
error instanceof ModelInvocationProjectQuotaExceededError ||
error instanceof PluginPackagePromptOutputArtifactConflictError ||
error instanceof PluginPackagePromptOutputArtifactUnavailableError
) {
return error;
}
const code = sqliteCode(error);
if (code.startsWith('ERR_SQLITE_CONSTRAINT')) {
return new ModelInvocationConflictError();
}
return unavailable(error);
}
export class PrivateLocalAuthority
implements LocalModelInvocationOperationAuthority
{
readonly client: DatabaseSync;
#tail: Promise<void> = Promise.resolve();
#pending = 0;
constructor(client: DatabaseSync) {
this.client = client;
}
enqueue<T>(
work: () => Promise<T>,
rejection: (reason: 'closed' | 'busy') => Error,
): Promise<T> {
if (this.#pending >= 64) return Promise.reject(rejection('busy'));
this.#pending += 1;
const result = this.#tail.then(work, work);
this.#tail = result.then(
() => undefined,
() => undefined,
);
return result.finally(() => {
this.#pending -= 1;
});
}
}
export function isAuthority(
value: LocalModelInvocationOperationAuthority | DatabaseSync,
): value is LocalModelInvocationOperationAuthority {
return (
!!value &&
typeof value === 'object' &&
'client' in value &&
'enqueue' in value &&
typeof value.enqueue === 'function'
);
}
export function enqueueLocalModelInvocation<T>(
authority: LocalModelInvocationOperationAuthority,
work: () => T,
): Promise<T> {
return authority.enqueue(async () => {
try {
return work();
} catch (error) {
throw mapStorageError(error);
}
}, unavailable);
}
@@ -0,0 +1,342 @@
import {
normalizeStepRunRecord,
type StepRunRecord,
} from '@qinglong/runtime-core/step-run';
import {
normalizeModelInvocationPriceQuote,
normalizeModelInvocationPriceSettlement,
type ModelInvocationPriceQuote,
type ModelInvocationPriceSettlement,
} from '../../pricing/pricing';
import {
normalizeModelInvocationUsageLedgerRecord,
type ModelInvocationUsageLedgerRecord,
} from '../../usage/usageLedger';
import {
normalizeModelInvocationQuotaReservation,
normalizeModelInvocationQuotaSettlement,
type ModelInvocationQuotaReservation,
type ModelInvocationQuotaSettlement,
} from '../../usage/usageQuota';
import {
normalizeModelInvocationCompletionRecord,
normalizeModelInvocationStartRecord,
type ModelInvocationAuthoritySnapshot,
type ModelInvocationCompletionRecord,
type ModelInvocationStartRecord,
} from '../modelInvocation';
import {
normalizeModelInvocationResolutionRecord,
type ModelInvocationResolutionRecord,
} from '../modelInvocationResolution';
import type { Row } from './authority';
import { integer, nullableInteger, text, unavailable } from './authority';
export function parseStart(row: Row): Readonly<ModelInvocationStartRecord> {
let start: Readonly<ModelInvocationStartRecord>;
try {
start = normalizeModelInvocationStartRecord(
JSON.parse(text(row, 'recordJson')) as ModelInvocationStartRecord,
);
} catch (error) {
throw unavailable(error);
}
if (
start.invocationId !== text(row, 'invocationId') ||
start.projectId !== text(row, 'projectId') ||
start.runId !== text(row, 'runId') ||
start.stepRunId !== text(row, 'stepRunId') ||
start.traceId !== text(row, 'traceId') ||
start.provider !== text(row, 'provider') ||
start.model !== text(row, 'model') ||
start.policyRevision !== text(row, 'policyRevision') ||
start.requestDigest !== text(row, 'requestDigest') ||
start.inputBytes !== integer(row, 'inputBytes') ||
start.maxOutputTokens !== integer(row, 'maxOutputTokens') ||
start.deadlineAtMs !== integer(row, 'deadlineAtMs') ||
start.admittedAtMs !== integer(row, 'admittedAtMs') ||
start.stepRunMutationId !== text(row, 'mutationId') ||
start.stepRunMutationDigest !== text(row, 'mutationDigest') ||
start.startedStepRunDigest !== text(row, 'stepRunDigest') ||
start.startedStepRunVersion !== integer(row, 'stepRunVersion') ||
start.runEventId !== text(row, 'runEventId') ||
start.startDigest !== text(row, 'startDigest')
) {
throw unavailable();
}
return start;
}
export function parseCompletion(
row: Row,
): Readonly<ModelInvocationCompletionRecord> {
let completion: Readonly<ModelInvocationCompletionRecord>;
try {
completion = normalizeModelInvocationCompletionRecord(
JSON.parse(text(row, 'recordJson')) as ModelInvocationCompletionRecord,
);
} catch (error) {
throw unavailable(error);
}
if (
completion.invocationId !== text(row, 'invocationId') ||
completion.projectId !== text(row, 'projectId') ||
completion.runId !== text(row, 'runId') ||
completion.stepRunId !== text(row, 'stepRunId') ||
completion.traceId !== text(row, 'traceId') ||
completion.startDigest !== text(row, 'startDigest') ||
completion.outcome !== text(row, 'outcome') ||
completion.outputBytes !== integer(row, 'outputBytes') ||
completion.errorCode !== row.errorCode ||
completion.completedAtMs !== integer(row, 'completedAtMs') ||
completion.stepRunMutationId !== text(row, 'mutationId') ||
completion.stepRunMutationDigest !== text(row, 'mutationDigest') ||
completion.completedStepRunDigest !== text(row, 'stepRunDigest') ||
completion.completedStepRunVersion !== integer(row, 'stepRunVersion') ||
completion.runEventId !== text(row, 'runEventId') ||
completion.completionDigest !== text(row, 'completionDigest')
) {
throw unavailable();
}
return completion;
}
export function parseUsage(
row: Row,
): Readonly<ModelInvocationUsageLedgerRecord> {
let usage: Readonly<ModelInvocationUsageLedgerRecord>;
try {
usage = normalizeModelInvocationUsageLedgerRecord(
JSON.parse(text(row, 'recordJson')) as ModelInvocationUsageLedgerRecord,
);
} catch (error) {
throw unavailable(error);
}
if (
usage.invocationId !== text(row, 'invocationId') ||
usage.projectId !== text(row, 'projectId') ||
usage.runId !== text(row, 'runId') ||
usage.stepRunId !== text(row, 'stepRunId') ||
usage.traceId !== text(row, 'traceId') ||
usage.provider !== text(row, 'provider') ||
usage.model !== text(row, 'model') ||
usage.policyRevision !== text(row, 'policyRevision') ||
usage.completionDigest !== text(row, 'completionDigest') ||
usage.outcome !== text(row, 'outcome') ||
usage.settledAtMs !== integer(row, 'settledAtMs') ||
usage.inputBytes !== integer(row, 'inputBytes') ||
usage.outputBytes !== integer(row, 'outputBytes') ||
usage.inputTokens !== integer(row, 'inputTokens') ||
usage.outputTokens !== integer(row, 'outputTokens') ||
usage.totalTokens !== integer(row, 'totalTokens') ||
usage.costMicros !== nullableInteger(row, 'costMicros') ||
usage.ledgerDigest !== text(row, 'ledgerDigest')
) {
throw unavailable();
}
return usage;
}
export function parseQuotaReservation(
row: Row,
): Readonly<ModelInvocationQuotaReservation> {
try {
return normalizeModelInvocationQuotaReservation(
JSON.parse(text(row, 'recordJson')) as ModelInvocationQuotaReservation,
);
} catch (error) {
throw unavailable(error);
}
}
export function parseQuotaSettlement(
row: Row,
reservation: Readonly<ModelInvocationQuotaReservation>,
completion: Readonly<ModelInvocationCompletionRecord>,
): Readonly<ModelInvocationQuotaSettlement> {
try {
return normalizeModelInvocationQuotaSettlement(
JSON.parse(text(row, 'recordJson')) as ModelInvocationQuotaSettlement,
reservation,
completion,
);
} catch (error) {
throw unavailable(error);
}
}
export function parsePriceQuote(row: Row): Readonly<ModelInvocationPriceQuote> {
try {
return normalizeModelInvocationPriceQuote(
JSON.parse(text(row, 'recordJson')) as ModelInvocationPriceQuote,
);
} catch (error) {
throw unavailable(error);
}
}
export function parsePriceSettlement(
row: Row,
quote: Readonly<ModelInvocationPriceQuote>,
completion: Readonly<ModelInvocationCompletionRecord>,
): Readonly<ModelInvocationPriceSettlement> {
try {
return normalizeModelInvocationPriceSettlement(
JSON.parse(text(row, 'recordJson')) as ModelInvocationPriceSettlement,
quote,
completion,
);
} catch (error) {
throw unavailable(error);
}
}
export function parseResolution(
row: Row,
): Readonly<ModelInvocationResolutionRecord> {
let resolution: Readonly<ModelInvocationResolutionRecord>;
try {
resolution = normalizeModelInvocationResolutionRecord(
JSON.parse(text(row, 'recordJson')) as ModelInvocationResolutionRecord,
);
} catch (error) {
throw unavailable(error);
}
if (
resolution.resolutionId !== text(row, 'resolutionId') ||
resolution.invocationId !== text(row, 'invocationId') ||
resolution.projectId !== text(row, 'projectId') ||
resolution.runId !== text(row, 'runId') ||
resolution.stepRunId !== text(row, 'stepRunId') ||
resolution.traceId !== text(row, 'traceId') ||
resolution.completionDigest !== text(row, 'completionDigest') ||
resolution.decision !== text(row, 'decision') ||
resolution.resolvedByUserId !== text(row, 'resolvedByUserId') ||
resolution.resolvedAtMs !== integer(row, 'resolvedAtMs') ||
resolution.stepRunMutationId !== text(row, 'mutationId') ||
resolution.stepRunMutationDigest !== text(row, 'mutationDigest') ||
resolution.resolvedStepRunDigest !== text(row, 'stepRunDigest') ||
resolution.resolvedStepRunVersion !== integer(row, 'stepRunVersion') ||
resolution.runEventId !== text(row, 'runEventId') ||
resolution.resolutionDigest !== text(row, 'resolutionDigest')
) {
throw unavailable();
}
return resolution;
}
export function parseAuthority(
row: Row,
): Readonly<ModelInvocationAuthoritySnapshot> {
let stepRun: Readonly<StepRunRecord>;
try {
stepRun = normalizeStepRunRecord(
JSON.parse(text(row, 'stepRunJson')) as StepRunRecord,
);
} catch (error) {
throw unavailable(error);
}
if (
stepRun.id !== text(row, 'stepRunId') ||
stepRun.runId !== text(row, 'runId') ||
stepRun.kind !== 'model' ||
stepRun.status !== text(row, 'stepStatus') ||
stepRun.version !== integer(row, 'stepVersion') ||
stepRun.stepRunDigest !== text(row, 'stepDigest')
) {
throw unavailable();
}
return Object.freeze({
projectId: text(row, 'projectId'),
runId: text(row, 'runId'),
runVersion: integer(row, 'runVersion'),
runEventSequence: integer(row, 'runEventSequence'),
stepRun,
});
}
export const START_SELECT = `
start.invocation_id AS "invocationId",
start.project_id AS "projectId",
start.run_id AS "runId",
start.step_run_id AS "stepRunId",
start.trace_id AS "traceId",
start.provider AS "provider",
start.model AS "model",
start.policy_revision AS "policyRevision",
start.request_digest AS "requestDigest",
start.input_bytes AS "inputBytes",
start.max_output_tokens AS "maxOutputTokens",
start.deadline_at_ms AS "deadlineAtMs",
start.admitted_at_ms AS "admittedAtMs",
start.mutation_id AS "mutationId",
start.mutation_digest AS "mutationDigest",
start.run_event_id AS "runEventId",
start.start_digest AS "startDigest",
start.record_json AS "recordJson",
mutation.step_run_digest AS "stepRunDigest",
json_extract(mutation.step_run_json, '$.version') AS "stepRunVersion"
`;
export const COMPLETION_SELECT = `
completion.invocation_id AS "invocationId",
completion.project_id AS "projectId",
completion.run_id AS "runId",
completion.step_run_id AS "stepRunId",
completion.trace_id AS "traceId",
completion.start_digest AS "startDigest",
completion.outcome AS "outcome",
completion.output_bytes AS "outputBytes",
completion.error_code AS "errorCode",
completion.completed_at_ms AS "completedAtMs",
completion.mutation_id AS "mutationId",
completion.mutation_digest AS "mutationDigest",
completion.run_event_id AS "runEventId",
completion.completion_digest AS "completionDigest",
completion.record_json AS "recordJson",
mutation.step_run_digest AS "stepRunDigest",
json_extract(mutation.step_run_json, '$.version') AS "stepRunVersion"
`;
export const USAGE_SELECT = `
usage.invocation_id AS "invocationId",
usage.project_id AS "projectId",
usage.run_id AS "runId",
usage.step_run_id AS "stepRunId",
usage.trace_id AS "traceId",
usage.provider AS "provider",
usage.model AS "model",
usage.policy_revision AS "policyRevision",
usage.completion_digest AS "completionDigest",
usage.outcome AS "outcome",
usage.settled_at_ms AS "settledAtMs",
usage.input_bytes AS "inputBytes",
usage.output_bytes AS "outputBytes",
usage.input_tokens AS "inputTokens",
usage.output_tokens AS "outputTokens",
usage.total_tokens AS "totalTokens",
usage.cost_micros AS "costMicros",
usage.ledger_digest AS "ledgerDigest",
usage.record_json AS "recordJson"
`;
export const RESOLUTION_SELECT = `
resolution.resolution_id AS "resolutionId",
resolution.invocation_id AS "invocationId",
resolution.project_id AS "projectId",
resolution.run_id AS "runId",
resolution.step_run_id AS "stepRunId",
resolution.trace_id AS "traceId",
resolution.completion_digest AS "completionDigest",
resolution.decision AS "decision",
resolution.resolved_by_user_id AS "resolvedByUserId",
resolution.resolved_at_ms AS "resolvedAtMs",
resolution.mutation_id AS "mutationId",
resolution.mutation_digest AS "mutationDigest",
resolution.run_event_id AS "runEventId",
resolution.resolution_digest AS "resolutionDigest",
resolution.record_json AS "recordJson",
mutation.step_run_digest AS "stepRunDigest",
json_extract(mutation.step_run_json, '$.version') AS "stepRunVersion"
`;
@@ -0,0 +1,538 @@
import type { DatabaseSync } from 'node:sqlite';
import { createModelInvocationPriceSettlement } from '../../pricing/pricing';
import { type PluginPackagePromptOutputArtifact } from '../../prompt-output/pluginPackagePromptOutputArtifact';
import {
assertPluginPackagePromptOutputCompletionBinding,
type CommitPluginPackagePromptOutputResult,
} from '../../prompt-output/pluginPackagePromptOutputCompletion';
import {
putLocalPluginPackagePromptOutputArtifactInTransaction,
readLocalPluginPackagePromptOutputArtifactInTransaction,
} from '../../prompt-output/storage/localPluginPackagePromptOutputArtifactRepository';
import { createModelInvocationUsageLedgerRecord } from '../../usage/usageLedger';
import { createModelInvocationQuotaSettlement } from '../../usage/usageQuota';
import {
ModelInvocationConflictError,
normalizeModelInvocationCompletionCommand,
type CommitModelInvocationResult,
type ModelInvocationCompletionCommand,
type ModelInvocationCompletionRecord,
} from '../modelInvocation';
import type { LocalModelInvocationOperationAuthority } from './authority';
import { enqueueLocalModelInvocation } from './authority';
import {
parseCompletion,
parsePriceQuote,
parsePriceSettlement,
parseQuotaReservation,
parseQuotaSettlement,
parseStart,
parseUsage,
} from './codec';
import {
applyMutation,
assertCurrent,
insertCompletion,
insertPriceSettlement,
insertQuotaSettlement,
insertUsage,
} from './mutations';
import {
completionRows,
priceQuoteRows,
priceSettlementRows,
quotaReservationRows,
quotaSettlementRows,
startRows,
usageRows,
} from './queries';
export function completeOperation(
authority: LocalModelInvocationOperationAuthority,
client: DatabaseSync,
commandValue: ModelInvocationCompletionCommand,
): Promise<
Readonly<CommitModelInvocationResult<ModelInvocationCompletionRecord>>
> {
const command = normalizeModelInvocationCompletionCommand(commandValue);
const completion = command.completion;
const expectedUsage = createModelInvocationUsageLedgerRecord(
command.start,
completion,
);
return enqueueLocalModelInvocation(authority, () => {
let began = false;
try {
client.exec('BEGIN IMMEDIATE');
began = true;
const existing = completionRows(
client,
`completion.invocation_id = ? OR
completion.mutation_id = ? OR completion.run_event_id = ?`,
[
completion.invocationId,
completion.stepRunMutationId,
completion.runEventId,
],
);
if (existing.length > 1) throw new ModelInvocationConflictError();
if (existing[0]) {
const stored = parseCompletion(existing[0]);
if (JSON.stringify(stored) !== JSON.stringify(completion)) {
throw new ModelInvocationConflictError();
}
const usage = usageRows(client, 'usage.invocation_id = ?', [
completion.invocationId,
]);
if (
usage.length !== (expectedUsage ? 1 : 0) ||
(expectedUsage &&
JSON.stringify(parseUsage(usage[0]!)) !==
JSON.stringify(expectedUsage))
) {
throw new ModelInvocationConflictError();
}
client.exec('COMMIT');
began = false;
return Object.freeze({ status: 'existing' as const, record: stored });
}
const starts = startRows(client, 'start.invocation_id = ?', [
completion.invocationId,
]);
if (
starts.length !== 1 ||
JSON.stringify(parseStart(starts[0]!)) !== JSON.stringify(command.start)
) {
throw new ModelInvocationConflictError();
}
assertCurrent(client, command.stepRunMutation, completion.projectId);
applyMutation(client, command.stepRunMutation);
insertCompletion(client, completion);
if (expectedUsage) insertUsage(client, expectedUsage);
client.exec('COMMIT');
began = false;
return Object.freeze({
status: 'created' as const,
record: completion,
});
} catch (error) {
if (began && client.isTransaction) {
try {
client.exec('ROLLBACK');
} catch {
// Preserve the original transaction failure.
}
}
throw error;
}
});
}
export function completeWithQuotaOperation(
authority: LocalModelInvocationOperationAuthority,
client: DatabaseSync,
commandValue: ModelInvocationCompletionCommand,
): Promise<
Readonly<CommitModelInvocationResult<ModelInvocationCompletionRecord>>
> {
const command = normalizeModelInvocationCompletionCommand(commandValue);
const completion = command.completion;
const expectedUsage = createModelInvocationUsageLedgerRecord(
command.start,
completion,
);
return enqueueLocalModelInvocation(authority, () => {
let began = false;
try {
client.exec('BEGIN IMMEDIATE');
began = true;
const reservationRows = quotaReservationRows(
client,
'reservation.invocation_id = ?',
[completion.invocationId],
);
if (reservationRows.length !== 1) {
throw new ModelInvocationConflictError();
}
const reservation = parseQuotaReservation(reservationRows[0]!);
const expectedSettlement = createModelInvocationQuotaSettlement(
reservation,
completion,
);
const existing = completionRows(
client,
`completion.invocation_id = ? OR
completion.mutation_id = ? OR completion.run_event_id = ?`,
[
completion.invocationId,
completion.stepRunMutationId,
completion.runEventId,
],
);
if (existing.length > 1) throw new ModelInvocationConflictError();
if (existing[0]) {
const stored = parseCompletion(existing[0]);
const usage = usageRows(client, 'usage.invocation_id = ?', [
completion.invocationId,
]);
const settlements = quotaSettlementRows(
client,
'settlement.invocation_id = ?',
[completion.invocationId],
);
if (
JSON.stringify(stored) !== JSON.stringify(completion) ||
usage.length !== (expectedUsage ? 1 : 0) ||
(expectedUsage &&
JSON.stringify(parseUsage(usage[0]!)) !==
JSON.stringify(expectedUsage)) ||
settlements.length !== 1 ||
JSON.stringify(
parseQuotaSettlement(settlements[0]!, reservation, completion),
) !== JSON.stringify(expectedSettlement)
) {
throw new ModelInvocationConflictError();
}
client.exec('COMMIT');
began = false;
return Object.freeze({ status: 'existing' as const, record: stored });
}
const starts = startRows(client, 'start.invocation_id = ?', [
completion.invocationId,
]);
if (
starts.length !== 1 ||
JSON.stringify(parseStart(starts[0]!)) !== JSON.stringify(command.start)
) {
throw new ModelInvocationConflictError();
}
assertCurrent(client, command.stepRunMutation, completion.projectId);
applyMutation(client, command.stepRunMutation);
insertCompletion(client, completion);
if (expectedUsage) insertUsage(client, expectedUsage);
insertQuotaSettlement(client, expectedSettlement);
client.exec('COMMIT');
began = false;
return Object.freeze({
status: 'created' as const,
record: completion,
});
} catch (error) {
if (began && client.isTransaction) {
try {
client.exec('ROLLBACK');
} catch {
// Preserve the original transaction failure.
}
}
throw error;
}
});
}
export function completeWithPricingOperation(
authority: LocalModelInvocationOperationAuthority,
client: DatabaseSync,
commandValue: ModelInvocationCompletionCommand,
): Promise<
Readonly<CommitModelInvocationResult<ModelInvocationCompletionRecord>>
> {
const command = normalizeModelInvocationCompletionCommand(commandValue);
const completion = command.completion;
const expectedUsage = createModelInvocationUsageLedgerRecord(
command.start,
completion,
);
return enqueueLocalModelInvocation(authority, () => {
let began = false;
try {
client.exec('BEGIN IMMEDIATE');
began = true;
const quoteRows = priceQuoteRows(client, 'quote.invocation_id = ?', [
completion.invocationId,
]);
if (quoteRows.length !== 1) {
throw new ModelInvocationConflictError();
}
const quote = parsePriceQuote(quoteRows[0]!);
if (
quote.invocationId !== command.start.invocationId ||
quote.projectId !== command.start.projectId ||
quote.modelPolicyRevision !== command.start.policyRevision ||
quote.provider !== command.start.provider ||
quote.model !== command.start.model
) {
throw new ModelInvocationConflictError();
}
const expectedPriceSettlement = createModelInvocationPriceSettlement(
quote,
completion,
);
const reservationRows = quotaReservationRows(
client,
'reservation.invocation_id = ?',
[completion.invocationId],
);
if (reservationRows.length > 1) {
throw new ModelInvocationConflictError();
}
const reservation = reservationRows[0]
? parseQuotaReservation(reservationRows[0])
: null;
const expectedQuotaSettlement = reservation
? createModelInvocationQuotaSettlement(reservation, completion)
: null;
const existing = completionRows(
client,
`completion.invocation_id = ? OR
completion.mutation_id = ? OR completion.run_event_id = ?`,
[
completion.invocationId,
completion.stepRunMutationId,
completion.runEventId,
],
);
if (existing.length > 1) throw new ModelInvocationConflictError();
if (existing[0]) {
const stored = parseCompletion(existing[0]);
const usage = usageRows(client, 'usage.invocation_id = ?', [
completion.invocationId,
]);
const priceSettlements = priceSettlementRows(
client,
'settlement.invocation_id = ?',
[completion.invocationId],
);
const quotaSettlements = quotaSettlementRows(
client,
'settlement.invocation_id = ?',
[completion.invocationId],
);
if (
JSON.stringify(stored) !== JSON.stringify(completion) ||
usage.length !== (expectedUsage ? 1 : 0) ||
(expectedUsage &&
JSON.stringify(parseUsage(usage[0]!)) !==
JSON.stringify(expectedUsage)) ||
priceSettlements.length !== (expectedPriceSettlement ? 1 : 0) ||
(expectedPriceSettlement &&
JSON.stringify(
parsePriceSettlement(priceSettlements[0]!, quote, completion),
) !== JSON.stringify(expectedPriceSettlement)) ||
quotaSettlements.length !== (expectedQuotaSettlement ? 1 : 0) ||
(expectedQuotaSettlement &&
JSON.stringify(
parseQuotaSettlement(
quotaSettlements[0]!,
reservation!,
completion,
),
) !== JSON.stringify(expectedQuotaSettlement))
) {
throw new ModelInvocationConflictError();
}
client.exec('COMMIT');
began = false;
return Object.freeze({ status: 'existing' as const, record: stored });
}
const starts = startRows(client, 'start.invocation_id = ?', [
completion.invocationId,
]);
if (
starts.length !== 1 ||
JSON.stringify(parseStart(starts[0]!)) !== JSON.stringify(command.start)
) {
throw new ModelInvocationConflictError();
}
assertCurrent(client, command.stepRunMutation, completion.projectId);
applyMutation(client, command.stepRunMutation);
insertCompletion(client, completion);
if (expectedUsage) insertUsage(client, expectedUsage);
if (expectedPriceSettlement) {
insertPriceSettlement(client, expectedPriceSettlement);
}
if (expectedQuotaSettlement) {
insertQuotaSettlement(client, expectedQuotaSettlement);
}
client.exec('COMMIT');
began = false;
return Object.freeze({
status: 'created' as const,
record: completion,
});
} catch (error) {
if (began && client.isTransaction) {
try {
client.exec('ROLLBACK');
} catch {
// Preserve the original transaction failure.
}
}
throw error;
}
});
}
export function completeWithPromptOutputArtifactOperation(
authority: LocalModelInvocationOperationAuthority,
client: DatabaseSync,
commandValue: ModelInvocationCompletionCommand,
artifactValue: PluginPackagePromptOutputArtifact,
): Promise<Readonly<CommitPluginPackagePromptOutputResult>> {
const command = normalizeModelInvocationCompletionCommand(commandValue);
const completion = command.completion;
const binding = assertPluginPackagePromptOutputCompletionBinding(
command,
artifactValue,
);
const expectedUsage = createModelInvocationUsageLedgerRecord(
command.start,
completion,
);
return enqueueLocalModelInvocation(authority, () => {
let began = false;
try {
client.exec('BEGIN IMMEDIATE');
began = true;
const quoteRows = priceQuoteRows(client, 'quote.invocation_id = ?', [
completion.invocationId,
]);
if (quoteRows.length > 1) throw new ModelInvocationConflictError();
const quote = quoteRows[0] ? parsePriceQuote(quoteRows[0]) : null;
if (
quote &&
(quote.invocationId !== command.start.invocationId ||
quote.projectId !== command.start.projectId ||
quote.modelPolicyRevision !== command.start.policyRevision ||
quote.provider !== command.start.provider ||
quote.model !== command.start.model)
) {
throw new ModelInvocationConflictError();
}
const expectedPriceSettlement = quote
? createModelInvocationPriceSettlement(quote, completion)
: null;
const reservationRows = quotaReservationRows(
client,
'reservation.invocation_id = ?',
[completion.invocationId],
);
if (reservationRows.length > 1) {
throw new ModelInvocationConflictError();
}
const reservation = reservationRows[0]
? parseQuotaReservation(reservationRows[0])
: null;
const expectedQuotaSettlement = reservation
? createModelInvocationQuotaSettlement(reservation, completion)
: null;
const existing = completionRows(
client,
`completion.invocation_id = ? OR
completion.mutation_id = ? OR completion.run_event_id = ?`,
[
completion.invocationId,
completion.stepRunMutationId,
completion.runEventId,
],
);
if (existing.length > 1) throw new ModelInvocationConflictError();
if (existing[0]) {
const stored = parseCompletion(existing[0]);
const storedArtifact =
readLocalPluginPackagePromptOutputArtifactInTransaction(
client,
binding.artifact.artifactId,
);
const usage = usageRows(client, 'usage.invocation_id = ?', [
completion.invocationId,
]);
const priceSettlements = priceSettlementRows(
client,
'settlement.invocation_id = ?',
[completion.invocationId],
);
const quotaSettlements = quotaSettlementRows(
client,
'settlement.invocation_id = ?',
[completion.invocationId],
);
if (
JSON.stringify(stored) !== JSON.stringify(completion) ||
!storedArtifact ||
JSON.stringify(storedArtifact) !== JSON.stringify(binding.artifact) ||
usage.length !== (expectedUsage ? 1 : 0) ||
(expectedUsage &&
JSON.stringify(parseUsage(usage[0]!)) !==
JSON.stringify(expectedUsage)) ||
priceSettlements.length !== (expectedPriceSettlement ? 1 : 0) ||
(expectedPriceSettlement &&
JSON.stringify(
parsePriceSettlement(priceSettlements[0]!, quote!, completion),
) !== JSON.stringify(expectedPriceSettlement)) ||
quotaSettlements.length !== (expectedQuotaSettlement ? 1 : 0) ||
(expectedQuotaSettlement &&
JSON.stringify(
parseQuotaSettlement(
quotaSettlements[0]!,
reservation!,
completion,
),
) !== JSON.stringify(expectedQuotaSettlement))
) {
throw new ModelInvocationConflictError();
}
client.exec('COMMIT');
began = false;
return Object.freeze({
status: 'existing' as const,
record: stored,
artifact: storedArtifact,
reference: binding.reference,
});
}
const starts = startRows(client, 'start.invocation_id = ?', [
completion.invocationId,
]);
if (
starts.length !== 1 ||
JSON.stringify(parseStart(starts[0]!)) !== JSON.stringify(command.start)
) {
throw new ModelInvocationConflictError();
}
assertCurrent(client, command.stepRunMutation, completion.projectId);
const artifact = putLocalPluginPackagePromptOutputArtifactInTransaction(
client,
binding.artifact,
).artifact;
applyMutation(client, command.stepRunMutation);
insertCompletion(client, completion);
if (expectedUsage) insertUsage(client, expectedUsage);
if (expectedPriceSettlement) {
insertPriceSettlement(client, expectedPriceSettlement);
}
if (expectedQuotaSettlement) {
insertQuotaSettlement(client, expectedQuotaSettlement);
}
client.exec('COMMIT');
began = false;
return Object.freeze({
status: 'created' as const,
record: completion,
artifact,
reference: binding.reference,
});
} catch (error) {
if (began && client.isTransaction) {
try {
client.exec('ROLLBACK');
} catch {
// Preserve the original transaction failure.
}
}
throw error;
}
});
}
@@ -0,0 +1,479 @@
import type { DatabaseSync } from 'node:sqlite';
import { type StepRunMutation } from '@qinglong/runtime-core/step-run';
import {
type ModelInvocationPriceQuote,
type ModelInvocationPriceSettlement,
} from '../../pricing/pricing';
import { type ModelInvocationUsageLedgerRecord } from '../../usage/usageLedger';
import {
type ModelInvocationQuotaReservation,
type ModelInvocationQuotaSettlement,
type ModelInvocationQuotaWindowUsage,
} from '../../usage/usageQuota';
import {
ModelInvocationConflictError,
type ModelInvocationCompletionRecord,
type ModelInvocationStartRecord,
} from '../modelInvocation';
import { type ModelInvocationResolutionRecord } from '../modelInvocationResolution';
import type { Row } from './authority';
import { TERMINAL_RUN_STATUSES, integer, text, unavailable } from './authority';
export function updateStepRun(
client: DatabaseSync,
mutation: Readonly<StepRunMutation>,
): void {
const step = mutation.stepRun;
const result = client
.prepare(
`UPDATE "StepRuns"
SET status = ?, version = ?, attempt_count = ?, output_ref = ?,
approval_request_id = ?, ready_at_ms = ?, started_at_ms = ?,
finished_at_ms = ?, result_code = ?, error_summary = ?,
updated_at_ms = ?, last_mutation_id = ?, step_run_digest = ?,
step_run_json = ?
WHERE id = ? AND run_id = ? AND version = ?
AND step_run_digest = ? AND status = ?`,
)
.run(
step.status,
step.version,
step.attemptCount,
step.outputRef,
step.approvalRequestId,
step.readyAtMs,
step.startedAtMs,
step.finishedAtMs,
step.resultCode,
step.errorSummary,
step.updatedAtMs,
step.lastMutationId,
step.stepRunDigest,
JSON.stringify(step),
step.id,
step.runId,
mutation.expectedStepRunVersion,
mutation.expectedStepRunDigest,
mutation.previousStatus,
);
if (result.changes !== 1) throw new ModelInvocationConflictError();
}
export function updateRun(
client: DatabaseSync,
mutation: Readonly<StepRunMutation>,
): void {
const result = client
.prepare(
`UPDATE "Runs"
SET version = version + 1, event_sequence = event_sequence + 1
WHERE id = ? AND version = ? AND event_sequence = ?`,
)
.run(
mutation.runId,
mutation.expectedRunVersion,
mutation.expectedRunEventSequence,
);
if (result.changes !== 1) throw new ModelInvocationConflictError();
}
export function insertRunEvent(
client: DatabaseSync,
mutation: Readonly<StepRunMutation>,
): void {
const event = mutation.event;
client
.prepare(
`INSERT INTO "RunEvents" (
id, run_id, sequence, type, dedupe_key, actor_type, actor_id,
attempt_id, step_run_id, payload, created_at_ms
) VALUES (?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?)`,
)
.run(
event.id,
event.runId,
event.sequence,
event.type,
event.dedupeKey!,
event.actorType,
event.actorId ?? null,
mutation.stepRun.id,
JSON.stringify(event.payload),
event.createdAtMs,
);
}
export function insertMutation(
client: DatabaseSync,
mutation: Readonly<StepRunMutation>,
): void {
client
.prepare(
`INSERT INTO "StepRunMutations" (
mutation_id, mutation_digest, run_id, step_run_id,
step_run_digest, event_id, event_sequence, run_version,
step_run_json, committed_at_ms
) VALUES (
?, ?, ?, ?, ?, ?, ?, ?, ?,
CAST(unixepoch('subsec') * 1000 AS INTEGER)
)`,
)
.run(
mutation.mutationId,
mutation.mutationDigest,
mutation.runId,
mutation.stepRun.id,
mutation.stepRun.stepRunDigest,
mutation.event.id,
mutation.event.sequence,
mutation.expectedRunVersion + 1,
JSON.stringify(mutation.stepRun),
);
}
export function applyMutation(
client: DatabaseSync,
mutation: Readonly<StepRunMutation>,
): void {
updateStepRun(client, mutation);
updateRun(client, mutation);
insertRunEvent(client, mutation);
insertMutation(client, mutation);
}
export function insertStart(
client: DatabaseSync,
start: Readonly<ModelInvocationStartRecord>,
): void {
client
.prepare(
`INSERT INTO "ModelInvocationStarts" (
invocation_id, project_id, run_id, step_run_id, trace_id,
provider, model, policy_revision, request_digest, input_bytes,
max_output_tokens, deadline_at_ms, admitted_at_ms, mutation_id,
mutation_digest, run_event_id, start_digest, record_json
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(
start.invocationId,
start.projectId,
start.runId,
start.stepRunId,
start.traceId,
start.provider,
start.model,
start.policyRevision,
start.requestDigest,
start.inputBytes,
start.maxOutputTokens,
start.deadlineAtMs,
start.admittedAtMs,
start.stepRunMutationId,
start.stepRunMutationDigest,
start.runEventId,
start.startDigest,
JSON.stringify(start),
);
}
export function insertCompletion(
client: DatabaseSync,
completion: Readonly<ModelInvocationCompletionRecord>,
): void {
client
.prepare(
`INSERT INTO "ModelInvocationCompletions" (
invocation_id, project_id, run_id, step_run_id, trace_id,
start_digest, outcome, output_bytes, error_code, completed_at_ms,
mutation_id, mutation_digest, run_event_id, completion_digest,
record_json
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(
completion.invocationId,
completion.projectId,
completion.runId,
completion.stepRunId,
completion.traceId,
completion.startDigest,
completion.outcome,
completion.outputBytes,
completion.errorCode,
completion.completedAtMs,
completion.stepRunMutationId,
completion.stepRunMutationDigest,
completion.runEventId,
completion.completionDigest,
JSON.stringify(completion),
);
}
export function insertUsage(
client: DatabaseSync,
usage: Readonly<ModelInvocationUsageLedgerRecord>,
): void {
client
.prepare(
`INSERT INTO "ModelInvocationUsageLedger" (
invocation_id, project_id, run_id, step_run_id, trace_id,
provider, model, policy_revision, completion_digest, outcome,
settled_at_ms, input_bytes, output_bytes, input_tokens,
output_tokens, total_tokens, cost_micros, ledger_digest, record_json
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(
usage.invocationId,
usage.projectId,
usage.runId,
usage.stepRunId,
usage.traceId,
usage.provider,
usage.model,
usage.policyRevision,
usage.completionDigest,
usage.outcome,
usage.settledAtMs,
usage.inputBytes,
usage.outputBytes,
usage.inputTokens,
usage.outputTokens,
usage.totalTokens,
usage.costMicros,
usage.ledgerDigest,
JSON.stringify(usage),
);
}
export function insertQuotaReservation(
client: DatabaseSync,
reservation: Readonly<ModelInvocationQuotaReservation>,
): void {
client
.prepare(
`INSERT INTO "ModelInvocationQuotaReservations" (
invocation_id, project_id, model_policy_revision,
quota_policy_revision, window_ms, window_start_ms, window_end_ms,
max_invocations, max_tokens, max_cost_micros, reserved_tokens,
reserved_cost_micros, reserved_at_ms, admission_digest,
reservation_digest, record_json
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(
reservation.invocationId,
reservation.projectId,
reservation.modelPolicyRevision,
reservation.quotaPolicyRevision,
reservation.windowMs,
reservation.windowStartMs,
reservation.windowEndMs,
reservation.maxInvocations,
reservation.maxTokens,
reservation.maxCostMicros,
reservation.reservedTokens,
reservation.reservedCostMicros,
reservation.reservedAtMs,
reservation.admissionDigest,
reservation.reservationDigest,
JSON.stringify(reservation),
);
}
export function insertQuotaSettlement(
client: DatabaseSync,
settlement: Readonly<ModelInvocationQuotaSettlement>,
): void {
client
.prepare(
`INSERT INTO "ModelInvocationQuotaSettlements" (
invocation_id, project_id, reservation_digest, completion_digest,
effective_tokens, effective_cost_micros,
retained_token_reservation, retained_cost_reservation,
settled_at_ms, settlement_digest, record_json
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(
settlement.invocationId,
settlement.projectId,
settlement.reservationDigest,
settlement.completionDigest,
settlement.effectiveTokens,
settlement.effectiveCostMicros,
settlement.retainedTokenReservation ? 1 : 0,
settlement.retainedCostReservation ? 1 : 0,
settlement.settledAtMs,
settlement.settlementDigest,
JSON.stringify(settlement),
);
}
export function insertPriceQuote(
client: DatabaseSync,
quote: Readonly<ModelInvocationPriceQuote>,
): void {
client
.prepare(
`INSERT INTO "ModelInvocationPriceQuotes" (
invocation_id, project_id, model_policy_revision, provider, model,
price_revision, currency, input_micros_per_million_tokens,
output_micros_per_million_tokens, max_total_tokens, max_output_tokens,
reserved_cost_micros, catalog_digest, quote_digest, record_json
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(
quote.invocationId,
quote.projectId,
quote.modelPolicyRevision,
quote.provider,
quote.model,
quote.priceRevision,
quote.currency,
quote.inputMicrosPerMillionTokens,
quote.outputMicrosPerMillionTokens,
quote.maxTotalTokens,
quote.maxOutputTokens,
quote.reservedCostMicros,
quote.catalogDigest,
quote.quoteDigest,
JSON.stringify(quote),
);
}
export function insertPriceSettlement(
client: DatabaseSync,
settlement: Readonly<ModelInvocationPriceSettlement>,
): void {
client
.prepare(
`INSERT INTO "ModelInvocationPriceSettlements" (
invocation_id, project_id, quote_digest, completion_digest, currency,
input_tokens, output_tokens, cost_micros, settled_at_ms,
settlement_digest, record_json
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(
settlement.invocationId,
settlement.projectId,
settlement.quoteDigest,
settlement.completionDigest,
settlement.currency,
settlement.inputTokens,
settlement.outputTokens,
settlement.costMicros,
settlement.settledAtMs,
settlement.settlementDigest,
JSON.stringify(settlement),
);
}
export function quotaWindowUsage(
client: DatabaseSync,
projectId: string,
windowStartMs: number,
windowMs: number,
): Readonly<ModelInvocationQuotaWindowUsage> {
const row = client
.prepare(
`SELECT
COUNT(*) AS "invocationCount",
COALESCE(SUM(COALESCE(
settlement.effective_tokens, reservation.reserved_tokens
)), 0) AS "effectiveTokens",
COALESCE(SUM(COALESCE(
settlement.effective_cost_micros,
reservation.reserved_cost_micros,
0
)), 0) AS "effectiveCostMicros",
COALESCE(SUM(CASE WHEN
settlement.effective_cost_micros IS NULL
AND reservation.reserved_cost_micros IS NULL
THEN 1 ELSE 0 END), 0) AS "unknownCostInvocations"
FROM "ModelInvocationQuotaReservations" AS reservation
LEFT JOIN "ModelInvocationQuotaSettlements" AS settlement
ON settlement.invocation_id = reservation.invocation_id
WHERE reservation.project_id = ?
AND reservation.window_start_ms = ?
AND reservation.window_ms = ?`,
)
.get(projectId, windowStartMs, windowMs) as Row | undefined;
if (!row) throw unavailable();
return Object.freeze({
projectId,
windowStartMs,
windowEndMs: windowStartMs + windowMs,
invocationCount: integer(row, 'invocationCount'),
effectiveTokens: integer(row, 'effectiveTokens'),
effectiveCostMicros: integer(row, 'effectiveCostMicros'),
unknownCostInvocations: integer(row, 'unknownCostInvocations'),
});
}
export function insertResolution(
client: DatabaseSync,
resolution: Readonly<ModelInvocationResolutionRecord>,
): void {
client
.prepare(
`INSERT INTO "ModelInvocationResolutions" (
resolution_id, invocation_id, project_id, run_id, step_run_id,
trace_id, completion_digest, decision, resolved_by_user_id,
resolved_at_ms, mutation_id, mutation_digest, run_event_id,
resolution_digest, record_json
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(
resolution.resolutionId,
resolution.invocationId,
resolution.projectId,
resolution.runId,
resolution.stepRunId,
resolution.traceId,
resolution.completionDigest,
resolution.decision,
resolution.resolvedByUserId,
resolution.resolvedAtMs,
resolution.stepRunMutationId,
resolution.stepRunMutationDigest,
resolution.runEventId,
resolution.resolutionDigest,
JSON.stringify(resolution),
);
}
export function assertCurrent(
client: DatabaseSync,
mutation: Readonly<StepRunMutation>,
projectId: string,
): void {
const rows = client
.prepare(
`SELECT
step.kind AS "stepKind", step.status AS "stepStatus",
step.version AS "stepVersion",
step.step_run_digest AS "stepDigest",
run.project_id AS "projectId", run.status AS "runStatus",
run.version AS "runVersion",
run.event_sequence AS "runEventSequence"
FROM "StepRuns" AS step
JOIN "Runs" AS run ON run.id = step.run_id
WHERE step.id = ? AND step.run_id = ?
LIMIT 2`,
)
.all(mutation.stepRun.id, mutation.runId) as Row[];
const row = rows[0];
if (
rows.length !== 1 ||
!row ||
text(row, 'stepKind') !== 'model' ||
text(row, 'stepStatus') !== mutation.previousStatus ||
integer(row, 'stepVersion') !== mutation.expectedStepRunVersion ||
text(row, 'stepDigest') !== mutation.expectedStepRunDigest ||
text(row, 'projectId') !== projectId ||
integer(row, 'runVersion') !== mutation.expectedRunVersion ||
integer(row, 'runEventSequence') !== mutation.expectedRunEventSequence ||
TERMINAL_RUN_STATUSES.has(text(row, 'runStatus'))
) {
throw new ModelInvocationConflictError();
}
}
@@ -0,0 +1,142 @@
import type { DatabaseSync } from 'node:sqlite';
import type { Row } from './authority';
import {
COMPLETION_SELECT,
RESOLUTION_SELECT,
START_SELECT,
USAGE_SELECT,
} from './codec';
export function startRows(
client: DatabaseSync,
where: string,
values: readonly (string | number)[],
): readonly Row[] {
return client
.prepare(
`SELECT ${START_SELECT}
FROM "ModelInvocationStarts" AS start
JOIN "StepRunMutations" AS mutation
ON mutation.mutation_id = start.mutation_id
JOIN "RunEvents" AS event ON event.id = start.run_event_id
WHERE ${where}
LIMIT 2`,
)
.all(...values) as Row[];
}
export function completionRows(
client: DatabaseSync,
where: string,
values: readonly (string | number)[],
): readonly Row[] {
return client
.prepare(
`SELECT ${COMPLETION_SELECT}
FROM "ModelInvocationCompletions" AS completion
JOIN "StepRunMutations" AS mutation
ON mutation.mutation_id = completion.mutation_id
JOIN "RunEvents" AS event ON event.id = completion.run_event_id
WHERE ${where}
LIMIT 2`,
)
.all(...values) as Row[];
}
export function usageRows(
client: DatabaseSync,
where: string,
values: readonly (string | number)[],
limit = 2,
): readonly Row[] {
return client
.prepare(
`SELECT ${USAGE_SELECT}
FROM "ModelInvocationUsageLedger" AS usage
WHERE ${where}
ORDER BY usage.settled_at_ms, usage.invocation_id
LIMIT ?`,
)
.all(...values, limit) as Row[];
}
export function quotaReservationRows(
client: DatabaseSync,
where: string,
values: readonly (string | number)[],
limit = 2,
): readonly Row[] {
return client
.prepare(
`SELECT reservation.record_json AS "recordJson"
FROM "ModelInvocationQuotaReservations" AS reservation
WHERE ${where}
ORDER BY reservation.window_start_ms, reservation.invocation_id
LIMIT ?`,
)
.all(...values, limit) as Row[];
}
export function quotaSettlementRows(
client: DatabaseSync,
where: string,
values: readonly (string | number)[],
): readonly Row[] {
return client
.prepare(
`SELECT settlement.record_json AS "recordJson"
FROM "ModelInvocationQuotaSettlements" AS settlement
WHERE ${where}
LIMIT 2`,
)
.all(...values) as Row[];
}
export function priceQuoteRows(
client: DatabaseSync,
where: string,
values: readonly (string | number)[],
): readonly Row[] {
return client
.prepare(
`SELECT quote.record_json AS "recordJson"
FROM "ModelInvocationPriceQuotes" AS quote
WHERE ${where}
LIMIT 2`,
)
.all(...values) as Row[];
}
export function priceSettlementRows(
client: DatabaseSync,
where: string,
values: readonly (string | number)[],
): readonly Row[] {
return client
.prepare(
`SELECT settlement.record_json AS "recordJson"
FROM "ModelInvocationPriceSettlements" AS settlement
WHERE ${where}
LIMIT 2`,
)
.all(...values) as Row[];
}
export function resolutionRows(
client: DatabaseSync,
where: string,
values: readonly (string | number)[],
): readonly Row[] {
return client
.prepare(
`SELECT ${RESOLUTION_SELECT}
FROM "ModelInvocationResolutions" AS resolution
JOIN "StepRunMutations" AS mutation
ON mutation.mutation_id = resolution.mutation_id
JOIN "RunEvents" AS event ON event.id = resolution.run_event_id
WHERE ${where}
LIMIT 2`,
)
.all(...values) as Row[];
}
@@ -0,0 +1,356 @@
import type { DatabaseSync } from 'node:sqlite';
import {
type ModelInvocationPriceQuote,
type ModelInvocationPriceSettlement,
} from '../../pricing/pricing';
import { type PluginPackagePromptOutputArtifact } from '../../prompt-output/pluginPackagePromptOutputArtifact';
import type { PluginPackagePromptOutputArtifactTombstone } from '../../prompt-output/pluginPackagePromptOutputRetention';
import { readLocalPluginPackagePromptOutputArtifactInTransaction } from '../../prompt-output/storage/localPluginPackagePromptOutputArtifactRepository';
import { readLocalPluginPackagePromptOutputArtifactTombstoneInTransaction } from '../../prompt-output/storage/localPluginPackagePromptOutputRetentionRepository';
import {
MAX_MODEL_INVOCATION_USAGE_SUMMARY_ROWS,
ModelInvocationUsageSummaryLimitExceededError,
normalizeModelInvocationUsageLedgerQuery,
normalizeModelInvocationUsageLedgerSummaryQuery,
type ModelInvocationUsageLedgerPage,
type ModelInvocationUsageLedgerQuery,
type ModelInvocationUsageLedgerRecord,
type ModelInvocationUsageLedgerSummary,
type ModelInvocationUsageLedgerSummaryQuery,
} from '../../usage/usageLedger';
import {
type ModelInvocationQuotaReservation,
type ModelInvocationQuotaSettlement,
type ModelInvocationQuotaWindowUsage,
} from '../../usage/usageQuota';
import {
type ModelInvocationCompletionRecord,
type ModelInvocationStartRecord,
} from '../modelInvocation';
import type { LocalModelInvocationOperationAuthority, Row } from './authority';
import {
enqueueLocalModelInvocation,
identifier,
integer,
unavailable,
} from './authority';
import {
parseCompletion,
parsePriceQuote,
parsePriceSettlement,
parseQuotaReservation,
parseQuotaSettlement,
parseStart,
parseUsage,
} from './codec';
import { quotaWindowUsage } from './mutations';
import {
completionRows,
priceQuoteRows,
priceSettlementRows,
quotaReservationRows,
quotaSettlementRows,
startRows,
usageRows,
} from './queries';
export function findStartOperation(
authority: LocalModelInvocationOperationAuthority,
client: DatabaseSync,
invocationIdValue: string,
): Promise<Readonly<ModelInvocationStartRecord> | null> {
const invocationId = identifier(invocationIdValue);
return enqueueLocalModelInvocation(authority, () => {
const rows = startRows(client, 'start.invocation_id = ?', [invocationId]);
if (rows.length > 1) throw unavailable();
return rows[0] ? parseStart(rows[0]) : null;
});
}
export function findCompletionOperation(
authority: LocalModelInvocationOperationAuthority,
client: DatabaseSync,
invocationIdValue: string,
): Promise<Readonly<ModelInvocationCompletionRecord> | null> {
const invocationId = identifier(invocationIdValue);
return enqueueLocalModelInvocation(authority, () => {
const rows = completionRows(client, 'completion.invocation_id = ?', [
invocationId,
]);
if (rows.length > 1) throw unavailable();
return rows[0] ? parseCompletion(rows[0]) : null;
});
}
export function findPromptOutputArtifactOperation(
authority: LocalModelInvocationOperationAuthority,
client: DatabaseSync,
artifactIdValue: string,
): Promise<Readonly<PluginPackagePromptOutputArtifact> | null> {
const artifactId = identifier(artifactIdValue);
return enqueueLocalModelInvocation(authority, () =>
readLocalPluginPackagePromptOutputArtifactInTransaction(client, artifactId),
);
}
export function findPromptOutputArtifactTombstoneOperation(
authority: LocalModelInvocationOperationAuthority,
client: DatabaseSync,
artifactIdValue: string,
): Promise<Readonly<PluginPackagePromptOutputArtifactTombstone> | null> {
const artifactId = identifier(artifactIdValue);
return enqueueLocalModelInvocation(authority, () =>
readLocalPluginPackagePromptOutputArtifactTombstoneInTransaction(
client,
artifactId,
),
);
}
export function findUsageOperation(
authority: LocalModelInvocationOperationAuthority,
client: DatabaseSync,
invocationIdValue: string,
): Promise<Readonly<ModelInvocationUsageLedgerRecord> | null> {
const invocationId = identifier(invocationIdValue);
return enqueueLocalModelInvocation(authority, () => {
const rows = usageRows(client, 'usage.invocation_id = ?', [invocationId]);
if (rows.length > 1) throw unavailable();
return rows[0] ? parseUsage(rows[0]) : null;
});
}
export function findPriceQuoteOperation(
authority: LocalModelInvocationOperationAuthority,
client: DatabaseSync,
invocationIdValue: string,
): Promise<Readonly<ModelInvocationPriceQuote> | null> {
const invocationId = identifier(invocationIdValue);
return enqueueLocalModelInvocation(authority, () => {
const rows = priceQuoteRows(client, 'quote.invocation_id = ?', [
invocationId,
]);
if (rows.length > 1) throw unavailable();
return rows[0] ? parsePriceQuote(rows[0]) : null;
});
}
export function findPriceSettlementOperation(
authority: LocalModelInvocationOperationAuthority,
client: DatabaseSync,
invocationIdValue: string,
): Promise<Readonly<ModelInvocationPriceSettlement> | null> {
const invocationId = identifier(invocationIdValue);
return enqueueLocalModelInvocation(authority, () => {
const quotes = priceQuoteRows(client, 'quote.invocation_id = ?', [
invocationId,
]);
const completions = completionRows(client, 'completion.invocation_id = ?', [
invocationId,
]);
const settlements = priceSettlementRows(
client,
'settlement.invocation_id = ?',
[invocationId],
);
if (quotes.length > 1 || completions.length > 1 || settlements.length > 1) {
throw unavailable();
}
if (!settlements[0]) return null;
if (!quotes[0] || !completions[0]) throw unavailable();
return parsePriceSettlement(
settlements[0],
parsePriceQuote(quotes[0]),
parseCompletion(completions[0]),
);
});
}
export function findQuotaReservationOperation(
authority: LocalModelInvocationOperationAuthority,
client: DatabaseSync,
invocationIdValue: string,
): Promise<Readonly<ModelInvocationQuotaReservation> | null> {
const invocationId = identifier(invocationIdValue);
return enqueueLocalModelInvocation(authority, () => {
const rows = quotaReservationRows(client, 'reservation.invocation_id = ?', [
invocationId,
]);
if (rows.length > 1) throw unavailable();
return rows[0] ? parseQuotaReservation(rows[0]) : null;
});
}
export function findQuotaSettlementOperation(
authority: LocalModelInvocationOperationAuthority,
client: DatabaseSync,
invocationIdValue: string,
): Promise<Readonly<ModelInvocationQuotaSettlement> | null> {
const invocationId = identifier(invocationIdValue);
return enqueueLocalModelInvocation(authority, () => {
const reservations = quotaReservationRows(
client,
'reservation.invocation_id = ?',
[invocationId],
);
const completions = completionRows(client, 'completion.invocation_id = ?', [
invocationId,
]);
const settlements = quotaSettlementRows(
client,
'settlement.invocation_id = ?',
[invocationId],
);
if (
reservations.length > 1 ||
completions.length > 1 ||
settlements.length > 1
) {
throw unavailable();
}
if (!settlements[0]) return null;
if (!reservations[0] || !completions[0]) throw unavailable();
return parseQuotaSettlement(
settlements[0],
parseQuotaReservation(reservations[0]),
parseCompletion(completions[0]),
);
});
}
export function readQuotaWindowUsageOperation(
authority: LocalModelInvocationOperationAuthority,
client: DatabaseSync,
projectIdValue: string,
atMsValue?: number,
): Promise<Readonly<ModelInvocationQuotaWindowUsage> | null> {
const projectId = identifier(projectIdValue);
if (
atMsValue !== undefined &&
(!Number.isSafeInteger(atMsValue) || atMsValue < 0)
) {
throw unavailable();
}
return enqueueLocalModelInvocation(authority, () => {
const atMs =
atMsValue ??
integer(
client
.prepare(
`SELECT CAST(unixepoch('subsec') * 1000 AS INTEGER) AS "atMs"`,
)
.get() as Row,
'atMs',
);
const row = client
.prepare(
`SELECT reservation.record_json AS "recordJson"
FROM "ModelInvocationQuotaReservations" AS reservation
WHERE reservation.project_id = ?
AND reservation.window_start_ms <= ?
AND reservation.window_end_ms > ?
ORDER BY reservation.reserved_at_ms DESC,
reservation.invocation_id DESC
LIMIT 1`,
)
.get(projectId, atMs, atMs) as Row | undefined;
if (!row) return null;
const reservation = parseQuotaReservation(row);
return quotaWindowUsage(
client,
projectId,
reservation.windowStartMs,
reservation.windowMs,
);
});
}
export function listProjectUsageOperation(
authority: LocalModelInvocationOperationAuthority,
client: DatabaseSync,
queryValue: ModelInvocationUsageLedgerQuery,
): Promise<Readonly<ModelInvocationUsageLedgerPage>> {
const query = normalizeModelInvocationUsageLedgerQuery(queryValue);
return enqueueLocalModelInvocation(authority, () => {
const cursor = query.after;
const rows = usageRows(
client,
`usage.project_id = ?
AND usage.settled_at_ms >= ? AND usage.settled_at_ms < ?
${
cursor
? `AND (
usage.settled_at_ms > ? OR
(usage.settled_at_ms = ? AND usage.invocation_id > ?)
)`
: ''
}`,
[
query.projectId,
query.fromMsInclusive,
query.toMsExclusive,
...(cursor
? [cursor.settledAtMs, cursor.settledAtMs, cursor.invocationId]
: []),
],
query.limit + 1,
);
return Object.freeze({
records: Object.freeze(
rows.slice(0, query.limit).map((row) => parseUsage(row)),
),
hasMore: rows.length > query.limit,
});
});
}
export function summarizeProjectUsageOperation(
authority: LocalModelInvocationOperationAuthority,
client: DatabaseSync,
queryValue: ModelInvocationUsageLedgerSummaryQuery,
): Promise<Readonly<ModelInvocationUsageLedgerSummary>> {
const query = normalizeModelInvocationUsageLedgerSummaryQuery(queryValue);
return enqueueLocalModelInvocation(authority, () => {
const row = client
.prepare(
`SELECT
COUNT(*) AS "invocationCount",
COALESCE(SUM(input_tokens), 0) AS "inputTokens",
COALESCE(SUM(output_tokens), 0) AS "outputTokens",
COALESCE(SUM(total_tokens), 0) AS "totalTokens",
COALESCE(SUM(cost_micros), 0) AS "knownCostMicros",
COALESCE(SUM(CASE WHEN cost_micros IS NULL THEN 1 ELSE 0 END), 0)
AS "unknownCostInvocations"
FROM (
SELECT input_tokens, output_tokens, total_tokens, cost_micros
FROM "ModelInvocationUsageLedger"
WHERE project_id = ?
AND settled_at_ms >= ? AND settled_at_ms < ?
ORDER BY settled_at_ms, invocation_id
LIMIT ?
) AS bounded_usage`,
)
.get(
query.projectId,
query.fromMsInclusive,
query.toMsExclusive,
MAX_MODEL_INVOCATION_USAGE_SUMMARY_ROWS + 1,
) as Row | undefined;
if (!row) throw unavailable();
if (
integer(row, 'invocationCount') > MAX_MODEL_INVOCATION_USAGE_SUMMARY_ROWS
) {
throw new ModelInvocationUsageSummaryLimitExceededError();
}
return Object.freeze({
invocationCount: integer(row, 'invocationCount'),
inputTokens: integer(row, 'inputTokens'),
outputTokens: integer(row, 'outputTokens'),
totalTokens: integer(row, 'totalTokens'),
knownCostMicros: integer(row, 'knownCostMicros'),
unknownCostInvocations: integer(row, 'unknownCostInvocations'),
});
});
}
@@ -0,0 +1,198 @@
import type { DatabaseSync } from 'node:sqlite';
import {
ModelInvocationConflictError,
type CommitModelInvocationResult,
type ModelInvocationAuthoritySnapshot,
type ModelInvocationRecoveryPage,
} from '../modelInvocation';
import {
normalizeModelInvocationResolutionCommand,
type ModelInvocationResolutionCommand,
type ModelInvocationResolutionRecord,
} from '../modelInvocationResolution';
import type { LocalModelInvocationOperationAuthority, Row } from './authority';
import {
TERMINAL_RUN_STATUSES,
enqueueLocalModelInvocation,
identifier,
integer,
recoveryLimit,
text,
unavailable,
} from './authority';
import {
START_SELECT,
parseAuthority,
parseCompletion,
parseResolution,
parseStart,
} from './codec';
import { applyMutation, assertCurrent, insertResolution } from './mutations';
import { completionRows, resolutionRows } from './queries';
export function findResolutionOperation(
authority: LocalModelInvocationOperationAuthority,
client: DatabaseSync,
invocationIdValue: string,
): Promise<Readonly<ModelInvocationResolutionRecord> | null> {
const invocationId = identifier(invocationIdValue);
return enqueueLocalModelInvocation(authority, () => {
const rows = resolutionRows(client, 'resolution.invocation_id = ?', [
invocationId,
]);
if (rows.length > 1) throw unavailable();
return rows[0] ? parseResolution(rows[0]) : null;
});
}
export function readAuthorityOperation(
authority: LocalModelInvocationOperationAuthority,
client: DatabaseSync,
identity: Readonly<{
projectId: string;
runId: string;
stepRunId: string;
}>,
): Promise<Readonly<ModelInvocationAuthoritySnapshot> | null> {
const projectId = identifier(identity?.projectId);
const runId = identifier(identity?.runId);
const stepRunId = identifier(identity?.stepRunId);
return enqueueLocalModelInvocation(authority, () => {
const rows = client
.prepare(
`SELECT
run.project_id AS "projectId", run.id AS "runId",
run.status AS "runStatus", run.version AS "runVersion",
run.event_sequence AS "runEventSequence",
step.id AS "stepRunId", step.status AS "stepStatus",
step.version AS "stepVersion",
step.step_run_digest AS "stepDigest",
step.step_run_json AS "stepRunJson"
FROM "Runs" AS run
JOIN "StepRuns" AS step ON step.run_id = run.id
WHERE run.project_id = ? AND run.id = ? AND step.id = ?
LIMIT 2`,
)
.all(projectId, runId, stepRunId) as Row[];
if (rows.length > 1) throw unavailable();
const row = rows[0];
if (!row || TERMINAL_RUN_STATUSES.has(text(row, 'runStatus'))) {
return null;
}
return parseAuthority(row);
});
}
export function listIncompleteOperation(
authority: LocalModelInvocationOperationAuthority,
client: DatabaseSync,
limitValue: number,
): Promise<Readonly<ModelInvocationRecoveryPage>> {
const limit = recoveryLimit(limitValue);
return enqueueLocalModelInvocation(authority, () => {
const observed = client
.prepare(
`SELECT CAST(unixepoch('subsec') * 1000 AS INTEGER)
AS "observedAtMs"`,
)
.get() as Row | undefined;
if (!observed) throw unavailable();
const observedAtMs = integer(observed, 'observedAtMs');
const rows = client
.prepare(
`SELECT ${START_SELECT}
FROM "ModelInvocationStarts" AS start
JOIN "StepRunMutations" AS mutation
ON mutation.mutation_id = start.mutation_id
JOIN "RunEvents" AS event ON event.id = start.run_event_id
LEFT JOIN "ModelInvocationCompletions" AS completion
ON completion.invocation_id = start.invocation_id
WHERE completion.invocation_id IS NULL
AND start.deadline_at_ms <= ?
ORDER BY start.deadline_at_ms, start.invocation_id
LIMIT ?`,
)
.all(observedAtMs, limit + 1) as Row[];
const hasMore = rows.length > limit;
return Object.freeze({
observedAtMs,
candidates: Object.freeze(
rows.slice(0, limit).map((row) => parseStart(row)),
),
hasMore,
});
});
}
export function resolveOperation(
authority: LocalModelInvocationOperationAuthority,
client: DatabaseSync,
commandValue: ModelInvocationResolutionCommand,
): Promise<
Readonly<CommitModelInvocationResult<ModelInvocationResolutionRecord>>
> {
const command = normalizeModelInvocationResolutionCommand(commandValue);
const resolution = command.resolution;
return enqueueLocalModelInvocation(authority, () => {
let began = false;
try {
client.exec('BEGIN IMMEDIATE');
began = true;
const existing = resolutionRows(
client,
`resolution.invocation_id = ? OR
resolution.resolution_id = ? OR
resolution.mutation_id = ? OR resolution.run_event_id = ?`,
[
resolution.invocationId,
resolution.resolutionId,
resolution.stepRunMutationId,
resolution.runEventId,
],
);
if (existing.length > 1) throw new ModelInvocationConflictError();
if (existing[0]) {
const stored = parseResolution(existing[0]);
if (JSON.stringify(stored) !== JSON.stringify(resolution)) {
throw new ModelInvocationConflictError();
}
client.exec('COMMIT');
began = false;
return Object.freeze({ status: 'existing' as const, record: stored });
}
const completions = completionRows(
client,
'completion.invocation_id = ?',
[resolution.invocationId],
);
if (
completions.length !== 1 ||
JSON.stringify(parseCompletion(completions[0]!)) !==
JSON.stringify(command.completion) ||
command.completion.outcome !== 'outcome_unknown'
) {
throw new ModelInvocationConflictError();
}
assertCurrent(client, command.stepRunMutation, resolution.projectId);
applyMutation(client, command.stepRunMutation);
insertResolution(client, resolution);
client.exec('COMMIT');
began = false;
return Object.freeze({
status: 'created' as const,
record: resolution,
});
} catch (error) {
if (began && client.isTransaction) {
try {
client.exec('ROLLBACK');
} catch {
// Preserve the original transaction failure.
}
}
throw error;
}
});
}
@@ -0,0 +1,328 @@
import type { DatabaseSync } from 'node:sqlite';
import {
type ModelInvocationPriceQuote,
type ModelInvocationPriceSettlement,
type PricingAwareModelInvocationRepository,
} from '../../pricing/pricing';
import { type PluginPackagePromptOutputArtifact } from '../../prompt-output/pluginPackagePromptOutputArtifact';
import {
type CommitPluginPackagePromptOutputResult,
type PluginPackagePromptOutputCompletionRepository,
} from '../../prompt-output/pluginPackagePromptOutputCompletion';
import type { PluginPackagePromptOutputArtifactTombstone } from '../../prompt-output/pluginPackagePromptOutputRetention';
import {
type ModelInvocationUsageLedgerPage,
type ModelInvocationUsageLedgerQuery,
type ModelInvocationUsageLedgerRecord,
type ModelInvocationUsageLedgerRepository,
type ModelInvocationUsageLedgerSummary,
type ModelInvocationUsageLedgerSummaryQuery,
} from '../../usage/usageLedger';
import {
type ModelInvocationQuotaAdmission,
type ModelInvocationQuotaReservation,
type ModelInvocationQuotaSettlement,
type ModelInvocationQuotaWindowUsage,
type QuotaAwareModelInvocationRepository,
} from '../../usage/usageQuota';
import {
type CommitModelInvocationResult,
type ModelInvocationAuthoritySnapshot,
type ModelInvocationCompletionCommand,
type ModelInvocationCompletionRecord,
type ModelInvocationRecoveryPage,
type ModelInvocationRepository,
type ModelInvocationStartCommand,
type ModelInvocationStartRecord,
} from '../modelInvocation';
import {
type ModelInvocationResolutionCommand,
type ModelInvocationResolutionRecord,
type ModelInvocationResolutionRepository,
} from '../modelInvocationResolution';
import {
admitOperation,
admitWithPricingOperation,
admitWithQuotaOperation,
} from './admissionOperations';
import {
PrivateLocalAuthority,
isAuthority,
type LocalModelInvocationOperationAuthority,
} from './authority';
import {
completeOperation,
completeWithPricingOperation,
completeWithPromptOutputArtifactOperation,
completeWithQuotaOperation,
} from './completionOperations';
import {
findCompletionOperation,
findPriceQuoteOperation,
findPriceSettlementOperation,
findPromptOutputArtifactOperation,
findPromptOutputArtifactTombstoneOperation,
findQuotaReservationOperation,
findQuotaSettlementOperation,
findStartOperation,
findUsageOperation,
listProjectUsageOperation,
readQuotaWindowUsageOperation,
summarizeProjectUsageOperation,
} from './readOperations';
import {
findResolutionOperation,
listIncompleteOperation,
readAuthorityOperation,
resolveOperation,
} from './recoveryResolutionOperations';
export class LocalModelInvocationRepository
implements
ModelInvocationRepository,
ModelInvocationResolutionRepository,
ModelInvocationUsageLedgerRepository,
QuotaAwareModelInvocationRepository,
PricingAwareModelInvocationRepository,
PluginPackagePromptOutputCompletionRepository
{
readonly #authority: LocalModelInvocationOperationAuthority;
readonly #client: DatabaseSync;
constructor(
authority: LocalModelInvocationOperationAuthority | DatabaseSync,
) {
this.#authority = isAuthority(authority)
? authority
: new PrivateLocalAuthority(authority);
this.#client = this.#authority.client;
}
findStart(
invocationIdValue: string,
): Promise<Readonly<ModelInvocationStartRecord> | null> {
return findStartOperation(this.#authority, this.#client, invocationIdValue);
}
findCompletion(
invocationIdValue: string,
): Promise<Readonly<ModelInvocationCompletionRecord> | null> {
return findCompletionOperation(
this.#authority,
this.#client,
invocationIdValue,
);
}
findPromptOutputArtifact(
artifactIdValue: string,
): Promise<Readonly<PluginPackagePromptOutputArtifact> | null> {
return findPromptOutputArtifactOperation(
this.#authority,
this.#client,
artifactIdValue,
);
}
findPromptOutputArtifactTombstone(
artifactIdValue: string,
): Promise<Readonly<PluginPackagePromptOutputArtifactTombstone> | null> {
return findPromptOutputArtifactTombstoneOperation(
this.#authority,
this.#client,
artifactIdValue,
);
}
findUsage(
invocationIdValue: string,
): Promise<Readonly<ModelInvocationUsageLedgerRecord> | null> {
return findUsageOperation(this.#authority, this.#client, invocationIdValue);
}
findPriceQuote(
invocationIdValue: string,
): Promise<Readonly<ModelInvocationPriceQuote> | null> {
return findPriceQuoteOperation(
this.#authority,
this.#client,
invocationIdValue,
);
}
findPriceSettlement(
invocationIdValue: string,
): Promise<Readonly<ModelInvocationPriceSettlement> | null> {
return findPriceSettlementOperation(
this.#authority,
this.#client,
invocationIdValue,
);
}
findQuotaReservation(
invocationIdValue: string,
): Promise<Readonly<ModelInvocationQuotaReservation> | null> {
return findQuotaReservationOperation(
this.#authority,
this.#client,
invocationIdValue,
);
}
findQuotaSettlement(
invocationIdValue: string,
): Promise<Readonly<ModelInvocationQuotaSettlement> | null> {
return findQuotaSettlementOperation(
this.#authority,
this.#client,
invocationIdValue,
);
}
readQuotaWindowUsage(
projectIdValue: string,
atMsValue?: number,
): Promise<Readonly<ModelInvocationQuotaWindowUsage> | null> {
return readQuotaWindowUsageOperation(
this.#authority,
this.#client,
projectIdValue,
atMsValue,
);
}
listProjectUsage(
queryValue: ModelInvocationUsageLedgerQuery,
): Promise<Readonly<ModelInvocationUsageLedgerPage>> {
return listProjectUsageOperation(this.#authority, this.#client, queryValue);
}
summarizeProjectUsage(
queryValue: ModelInvocationUsageLedgerSummaryQuery,
): Promise<Readonly<ModelInvocationUsageLedgerSummary>> {
return summarizeProjectUsageOperation(
this.#authority,
this.#client,
queryValue,
);
}
findResolution(
invocationIdValue: string,
): Promise<Readonly<ModelInvocationResolutionRecord> | null> {
return findResolutionOperation(
this.#authority,
this.#client,
invocationIdValue,
);
}
readAuthority(
identity: Readonly<{
projectId: string;
runId: string;
stepRunId: string;
}>,
): Promise<Readonly<ModelInvocationAuthoritySnapshot> | null> {
return readAuthorityOperation(this.#authority, this.#client, identity);
}
listIncomplete(
limitValue: number,
): Promise<Readonly<ModelInvocationRecoveryPage>> {
return listIncompleteOperation(this.#authority, this.#client, limitValue);
}
admit(
commandValue: ModelInvocationStartCommand,
): Promise<
Readonly<CommitModelInvocationResult<ModelInvocationStartRecord>>
> {
return admitOperation(this.#authority, this.#client, commandValue);
}
admitWithQuota(
commandValue: ModelInvocationStartCommand,
admissionValue: ModelInvocationQuotaAdmission,
): Promise<
Readonly<CommitModelInvocationResult<ModelInvocationStartRecord>>
> {
return admitWithQuotaOperation(
this.#authority,
this.#client,
commandValue,
admissionValue,
);
}
admitWithPricing(
commandValue: ModelInvocationStartCommand,
quoteValue: ModelInvocationPriceQuote,
admissionValue?: ModelInvocationQuotaAdmission,
): Promise<
Readonly<CommitModelInvocationResult<ModelInvocationStartRecord>>
> {
return admitWithPricingOperation(
this.#authority,
this.#client,
commandValue,
quoteValue,
admissionValue,
);
}
complete(
commandValue: ModelInvocationCompletionCommand,
): Promise<
Readonly<CommitModelInvocationResult<ModelInvocationCompletionRecord>>
> {
return completeOperation(this.#authority, this.#client, commandValue);
}
completeWithQuota(
commandValue: ModelInvocationCompletionCommand,
): Promise<
Readonly<CommitModelInvocationResult<ModelInvocationCompletionRecord>>
> {
return completeWithQuotaOperation(
this.#authority,
this.#client,
commandValue,
);
}
completeWithPricing(
commandValue: ModelInvocationCompletionCommand,
): Promise<
Readonly<CommitModelInvocationResult<ModelInvocationCompletionRecord>>
> {
return completeWithPricingOperation(
this.#authority,
this.#client,
commandValue,
);
}
completeWithPromptOutputArtifact(
commandValue: ModelInvocationCompletionCommand,
artifactValue: PluginPackagePromptOutputArtifact,
): Promise<Readonly<CommitPluginPackagePromptOutputResult>> {
return completeWithPromptOutputArtifactOperation(
this.#authority,
this.#client,
commandValue,
artifactValue,
);
}
resolve(
commandValue: ModelInvocationResolutionCommand,
): Promise<
Readonly<CommitModelInvocationResult<ModelInvocationResolutionRecord>>
> {
return resolveOperation(this.#authority, this.#client, commandValue);
}
}
@@ -0,0 +1,2 @@
export { LocalModelInvocationRepository } from './local-model-invocation-repository/repository';
export type { LocalModelInvocationOperationAuthority } from './local-model-invocation-repository/authority';
@@ -0,0 +1,151 @@
import type { StepRunStatus } from '@qinglong/runtime-core/step-run';
import type { ModelInvocationAuditRecord } from '../../model-gateway/model';
import { normalizeModelUsage } from '../../model-gateway/validation';
import type { ModelInvocationOutcome } from './contracts';
import {
ERROR_CODE_PATTERN,
dataRecord,
exactKeys,
identifier,
integer,
invalid,
requestDigest,
} from './common';
export function normalizeAdmissionAudit(
value: ModelInvocationAuditRecord,
): Readonly<ModelInvocationAuditRecord> {
const candidate = normalizeAuditCommon(value);
if (
candidate.phase !== 'admitted' ||
candidate.outputBytes !== 0 ||
candidate.usage !== null ||
candidate.errorCode !== null
) {
invalid('admission audit facts are invalid');
}
return candidate;
}
export function normalizeAuditCommon(
value: ModelInvocationAuditRecord,
): Readonly<ModelInvocationAuditRecord> {
const candidate = dataRecord(value, 'audit record');
exactKeys(
candidate,
[
'deadlineAtMs',
'errorCode',
'inputBytes',
'maxOutputTokens',
'model',
'occurredAtMs',
'outputBytes',
'phase',
'policyRevision',
'projectId',
'provider',
'requestDigest',
'requestId',
'runId',
'stepRunId',
'traceId',
'usage',
],
'audit record',
);
if (!['admitted', 'completed', 'failed'].includes(value.phase)) {
invalid('audit phase is invalid');
}
const usage = value.usage === null ? null : normalizeModelUsage(value.usage);
const errorCode =
value.errorCode === null
? null
: typeof value.errorCode === 'string' &&
ERROR_CODE_PATTERN.test(value.errorCode)
? value.errorCode
: invalid('audit error code is invalid');
return Object.freeze({
phase: value.phase,
projectId: identifier(value.projectId, 'audit project id'),
runId: identifier(value.runId, 'audit Run id'),
stepRunId: identifier(value.stepRunId, 'audit StepRun id'),
traceId: identifier(value.traceId, 'audit trace id'),
requestId: identifier(value.requestId, 'audit request id'),
provider: identifier(value.provider, 'audit provider'),
model: identifier(value.model, 'audit model'),
policyRevision: identifier(value.policyRevision, 'audit policy revision'),
requestDigest: requestDigest(value.requestDigest),
deadlineAtMs: integer(
value.deadlineAtMs,
0,
Number.MAX_SAFE_INTEGER,
'audit deadline',
),
inputBytes: integer(value.inputBytes, 1, 256 * 1024, 'audit input bytes'),
maxOutputTokens: integer(
value.maxOutputTokens,
1,
32_768,
'audit max output tokens',
),
outputBytes: integer(
value.outputBytes,
0,
1024 * 1024,
'audit output bytes',
),
usage,
errorCode,
occurredAtMs: integer(
value.occurredAtMs,
0,
Number.MAX_SAFE_INTEGER,
'audit time',
),
});
}
export function outcomeFor(audit: Readonly<ModelInvocationAuditRecord>): Readonly<{
outcome: ModelInvocationOutcome;
stepStatus: StepRunStatus;
resultCode?: string;
errorSummary?: string;
}> {
if (audit.phase === 'completed') {
if (!audit.usage || audit.errorCode !== null) {
invalid('successful completion facts are invalid');
}
return Object.freeze({ outcome: 'succeeded', stepStatus: 'succeeded' });
}
if (audit.phase !== 'failed' || audit.errorCode === null) {
invalid('failed completion facts are invalid');
}
if (audit.errorCode === 'MODEL_INVOCATION_DEADLINE_EXCEEDED') {
return Object.freeze({
outcome: 'timed_out',
stepStatus: 'timed_out',
resultCode: 'model_deadline_exceeded',
errorSummary: 'Model invocation deadline exceeded',
});
}
if (
audit.errorCode === 'MODEL_INVOCATION_ABORTED' ||
audit.errorCode === 'MODEL_STREAM_CANCELLED' ||
audit.errorCode === 'MODEL_INVOCATION_OUTCOME_UNKNOWN'
) {
return Object.freeze({
outcome: 'outcome_unknown',
stepStatus: 'lost',
resultCode: 'model_outcome_unknown',
errorSummary: 'Model invocation outcome is unknown',
});
}
return Object.freeze({
outcome: 'failed',
stepStatus: 'failed',
resultCode: 'model_provider_failed',
errorSummary: 'Model invocation failed',
});
}
@@ -0,0 +1,152 @@
import { Buffer } from 'node:buffer';
import { createHash } from 'node:crypto';
import {
MAX_MODEL_INVOCATION_RECORD_JSON_BYTES,
MODEL_INVOCATION_MUTATION_PHASES,
InvalidModelInvocationError,
type ModelInvocationMutationPhase,
} from './contracts';
const IDENTITY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
export const ERROR_CODE_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/;
const SHA256_PATTERN = /^sha256:[0-9a-f]{64}$/;
const DIGEST_PATTERN = /^[0-9a-f]{64}$/;
export const START_DIGEST_DOMAIN = Buffer.from(
'qinglong/model-invocation-start-digest@v1\0',
'utf8',
);
export const START_COMMAND_DIGEST_DOMAIN = Buffer.from(
'qinglong/model-invocation-start-command-digest@v1\0',
'utf8',
);
export const COMPLETION_DIGEST_DOMAIN = Buffer.from(
'qinglong/model-invocation-completion-digest@v1\0',
'utf8',
);
export const COMPLETION_COMMAND_DIGEST_DOMAIN = Buffer.from(
'qinglong/model-invocation-completion-command-digest@v1\0',
'utf8',
);
const MUTATION_IDENTITY_DOMAIN = Buffer.from(
'qinglong/model-invocation-mutation-identity@v1\0',
'utf8',
);
export function invalid(message: string): never {
throw new InvalidModelInvocationError(message);
}
export function dataRecord(value: unknown, label: string): Record<string, unknown> {
if (
!value ||
typeof value !== 'object' ||
Array.isArray(value) ||
Object.getPrototypeOf(value) !== Object.prototype
) {
return invalid(`${label} must be a plain object`);
}
return value as Record<string, unknown>;
}
export function exactKeys(
value: object,
expected: readonly string[],
label: string,
): void {
const actual = Object.keys(value).sort();
const canonical = [...expected].sort();
if (
actual.length !== canonical.length ||
actual.some((key, index) => key !== canonical[index])
) {
invalid(`${label} shape is invalid`);
}
}
export function identifier(value: unknown, label: string): string {
if (typeof value !== 'string' || !IDENTITY_PATTERN.test(value)) {
return invalid(`${label} is invalid`);
}
return value;
}
export function digest(value: unknown, label: string): string {
if (typeof value !== 'string' || !DIGEST_PATTERN.test(value)) {
return invalid(`${label} is invalid`);
}
return value;
}
export function requestDigest(value: unknown): string {
if (typeof value !== 'string' || !SHA256_PATTERN.test(value)) {
return invalid('request digest is invalid');
}
return value;
}
export function createModelInvocationMutationIdentity(
invocationIdValue: string,
phaseValue: ModelInvocationMutationPhase,
): Readonly<{
mutationId: string;
eventId: string;
dedupeKey: string;
}> {
const invocationId = identifier(invocationIdValue, 'invocation id');
if (!MODEL_INVOCATION_MUTATION_PHASES.includes(phaseValue)) {
invalid('mutation phase is invalid');
}
const identityDigest = createHash('sha256')
.update(MUTATION_IDENTITY_DOMAIN)
.update(phaseValue, 'utf8')
.update('\0', 'utf8')
.update(invocationId, 'utf8')
.digest('hex');
const eventHex =
identityDigest.slice(0, 12) +
'8' +
identityDigest.slice(13, 16) +
'8' +
identityDigest.slice(17, 32);
return Object.freeze({
mutationId: `ql3mi.${phaseValue}.mutation.${identityDigest}`,
eventId: `${eventHex.slice(0, 8)}-${eventHex.slice(8, 12)}-${eventHex.slice(
12,
16,
)}-${eventHex.slice(16, 20)}-${eventHex.slice(20, 32)}`,
dedupeKey: `ql3mi.${phaseValue}.dedupe.${identityDigest}`,
});
}
export function integer(
value: unknown,
minimum: number,
maximum: number,
label: string,
): number {
if (
!Number.isSafeInteger(value) ||
(value as number) < minimum ||
(value as number) > maximum
) {
return invalid(`${label} is invalid`);
}
return value as number;
}
export function hash(domain: Uint8Array, value: unknown): string {
return createHash('sha256')
.update(domain)
.update(JSON.stringify(value))
.digest('hex');
}
export function assertJsonBudget(value: unknown, label: string): void {
if (
Buffer.byteLength(JSON.stringify(value), 'utf8') >
MAX_MODEL_INVOCATION_RECORD_JSON_BYTES
) {
invalid(`${label} exceeds its JSON budget`);
}
}
@@ -0,0 +1,269 @@
import { normalizeStepRunMutation, type StepRunMutation } from '@qinglong/runtime-core/step-run';
import type { ModelInvocationAuditRecord } from '../../model-gateway/model';
import { normalizeModelUsage } from '../../model-gateway/validation';
import {
MODEL_INVOCATION_COMPLETION_COMMAND_SCHEMA,
MODEL_INVOCATION_COMPLETION_SCHEMA,
MODEL_INVOCATION_OUTCOMES,
type ModelInvocationCompletionCommand,
type ModelInvocationCompletionRecord,
type ModelInvocationStartRecord,
} from './contracts';
import {
COMPLETION_COMMAND_DIGEST_DOMAIN,
COMPLETION_DIGEST_DOMAIN,
ERROR_CODE_PATTERN,
assertJsonBudget,
createModelInvocationMutationIdentity,
dataRecord,
digest,
exactKeys,
hash,
identifier,
integer,
invalid,
} from './common';
import { normalizeAuditCommon, outcomeFor } from './audit';
import { normalizeModelInvocationStartRecord } from './startProtocol';
function completionWithoutDigest(
value: Readonly<ModelInvocationCompletionRecord>,
): Omit<ModelInvocationCompletionRecord, 'completionDigest'> {
const { completionDigest: _completionDigest, ...unsigned } = value;
return unsigned;
}
export function normalizeModelInvocationCompletionRecord(
value: ModelInvocationCompletionRecord,
): Readonly<ModelInvocationCompletionRecord> {
const candidate = dataRecord(value, 'completion record');
exactKeys(
candidate,
[
'completedAtMs',
'completedStepRunDigest',
'completedStepRunVersion',
'completionDigest',
'errorCode',
'invocationId',
'outcome',
'outputBytes',
'projectId',
'runEventId',
'runId',
'schema',
'startDigest',
'stepRunId',
'stepRunMutationDigest',
'stepRunMutationId',
'traceId',
'usage',
],
'completion record',
);
if (
value.schema !== MODEL_INVOCATION_COMPLETION_SCHEMA ||
!MODEL_INVOCATION_OUTCOMES.includes(value.outcome)
) {
invalid('completion schema or outcome is invalid');
}
const usage = value.usage === null ? null : normalizeModelUsage(value.usage);
const errorCode =
value.errorCode === null
? null
: typeof value.errorCode === 'string' &&
ERROR_CODE_PATTERN.test(value.errorCode)
? value.errorCode
: invalid('completion error code is invalid');
const normalized = Object.freeze({
schema: MODEL_INVOCATION_COMPLETION_SCHEMA,
invocationId: identifier(value.invocationId, 'invocation id'),
projectId: identifier(value.projectId, 'project id'),
runId: identifier(value.runId, 'Run id'),
stepRunId: identifier(value.stepRunId, 'StepRun id'),
traceId: identifier(value.traceId, 'trace id'),
startDigest: digest(value.startDigest, 'start digest'),
outcome: value.outcome,
outputBytes: integer(value.outputBytes, 0, 1024 * 1024, 'output bytes'),
usage,
errorCode,
completedStepRunVersion: integer(
value.completedStepRunVersion,
3,
2_147_483_647,
'completed StepRun version',
),
stepRunMutationId: identifier(
value.stepRunMutationId,
'StepRun mutation id',
),
stepRunMutationDigest: digest(
value.stepRunMutationDigest,
'StepRun mutation digest',
),
completedStepRunDigest: digest(
value.completedStepRunDigest,
'completed StepRun digest',
),
runEventId: identifier(value.runEventId, 'RunEvent id'),
completedAtMs: integer(
value.completedAtMs,
0,
Number.MAX_SAFE_INTEGER,
'completed time',
),
completionDigest: digest(value.completionDigest, 'completion digest'),
});
const identity = createModelInvocationMutationIdentity(
normalized.invocationId,
'completion',
);
if (
(normalized.outcome === 'succeeded') !==
(normalized.usage !== null && normalized.errorCode === null) ||
(normalized.outcome !== 'succeeded' && normalized.errorCode === null) ||
normalized.stepRunMutationId !== identity.mutationId ||
normalized.runEventId !== identity.eventId ||
hash(COMPLETION_DIGEST_DOMAIN, completionWithoutDigest(normalized)) !==
normalized.completionDigest
) {
invalid('completion facts or digest are invalid');
}
assertJsonBudget(normalized, 'completion record');
return normalized;
}
export function createModelInvocationCompletionCommand(
startValue: ModelInvocationStartRecord,
auditValue: ModelInvocationAuditRecord,
mutationValue: StepRunMutation,
successOutputRefValue?: string,
): Readonly<ModelInvocationCompletionCommand> {
const start = normalizeModelInvocationStartRecord(startValue);
const audit = normalizeAuditCommon(auditValue);
const mutation = normalizeStepRunMutation(mutationValue);
const outcome = outcomeFor(audit);
const identity = createModelInvocationMutationIdentity(
audit.requestId,
'completion',
);
const successOutputRef =
successOutputRefValue ?? `model-invocation:${start.invocationId}`;
if (
audit.requestId !== start.invocationId ||
audit.projectId !== start.projectId ||
audit.runId !== start.runId ||
audit.stepRunId !== start.stepRunId ||
audit.traceId !== start.traceId ||
audit.provider !== start.provider ||
audit.model !== start.model ||
audit.policyRevision !== start.policyRevision ||
audit.requestDigest !== start.requestDigest ||
audit.deadlineAtMs !== start.deadlineAtMs ||
audit.inputBytes !== start.inputBytes ||
audit.maxOutputTokens !== start.maxOutputTokens ||
audit.occurredAtMs < start.admittedAtMs ||
mutation.previousStatus !== 'running' ||
mutation.mutationId !== identity.mutationId ||
mutation.expectedStepRunVersion !== start.startedStepRunVersion ||
mutation.expectedStepRunDigest !== start.startedStepRunDigest ||
mutation.stepRun.runId !== start.runId ||
mutation.stepRun.id !== start.stepRunId ||
mutation.stepRun.kind !== 'model' ||
mutation.stepRun.status !== outcome.stepStatus ||
mutation.stepRun.updatedAtMs !== audit.occurredAtMs ||
mutation.event.id !== identity.eventId ||
mutation.event.dedupeKey !== identity.dedupeKey ||
mutation.event.type !== `step.${outcome.stepStatus}` ||
(outcome.stepStatus === 'succeeded'
? mutation.stepRun.outputRef !== successOutputRef
: mutation.stepRun.resultCode !== outcome.resultCode ||
mutation.stepRun.errorSummary !== outcome.errorSummary)
) {
invalid('completion identity or StepRun mutation is not exact');
}
const unsigned = Object.freeze({
schema: MODEL_INVOCATION_COMPLETION_SCHEMA,
invocationId: start.invocationId,
projectId: start.projectId,
runId: start.runId,
stepRunId: start.stepRunId,
traceId: start.traceId,
startDigest: start.startDigest,
outcome: outcome.outcome,
outputBytes: audit.outputBytes,
usage: audit.usage,
errorCode: audit.errorCode,
completedStepRunVersion: mutation.stepRun.version,
stepRunMutationId: mutation.mutationId,
stepRunMutationDigest: mutation.mutationDigest,
completedStepRunDigest: mutation.stepRun.stepRunDigest,
runEventId: mutation.event.id,
completedAtMs: audit.occurredAtMs,
});
const completion = normalizeModelInvocationCompletionRecord({
...unsigned,
completionDigest: hash(COMPLETION_DIGEST_DOMAIN, unsigned),
});
const commandUnsigned = Object.freeze({
schema: MODEL_INVOCATION_COMPLETION_COMMAND_SCHEMA,
start,
completion,
stepRunMutation: mutation,
});
return Object.freeze({
...commandUnsigned,
commandDigest: hash(COMPLETION_COMMAND_DIGEST_DOMAIN, commandUnsigned),
});
}
export function normalizeModelInvocationCompletionCommand(
value: ModelInvocationCompletionCommand,
): Readonly<ModelInvocationCompletionCommand> {
const candidate = dataRecord(value, 'completion command');
exactKeys(
candidate,
['commandDigest', 'completion', 'schema', 'start', 'stepRunMutation'],
'completion command',
);
if (value.schema !== MODEL_INVOCATION_COMPLETION_COMMAND_SCHEMA) {
invalid('completion command schema is invalid');
}
const start = normalizeModelInvocationStartRecord(value.start);
const completion = normalizeModelInvocationCompletionRecord(value.completion);
const mutation = normalizeStepRunMutation(value.stepRunMutation);
const canonical = createModelInvocationCompletionCommand(
start,
{
phase: completion.outcome === 'succeeded' ? 'completed' : 'failed',
projectId: completion.projectId,
runId: completion.runId,
stepRunId: completion.stepRunId,
traceId: completion.traceId,
requestId: completion.invocationId,
provider: start.provider,
model: start.model,
policyRevision: start.policyRevision,
requestDigest: start.requestDigest,
deadlineAtMs: start.deadlineAtMs,
inputBytes: start.inputBytes,
maxOutputTokens: start.maxOutputTokens,
outputBytes: completion.outputBytes,
usage: completion.usage,
errorCode: completion.errorCode,
occurredAtMs: completion.completedAtMs,
},
mutation,
completion.outcome === 'succeeded'
? mutation.stepRun.outputRef ?? undefined
: undefined,
);
if (
canonical.completion.completionDigest !== completion.completionDigest ||
digest(value.commandDigest, 'command digest') !== canonical.commandDigest
) {
invalid('completion command is not canonical');
}
return canonical;
}
@@ -0,0 +1,159 @@
import type { StepRunRecord, StepRunMutation } from '@qinglong/runtime-core/step-run';
import type { ModelUsage } from '../../model-gateway/model';
export const MODEL_INVOCATION_START_SCHEMA =
'qinglong/model-invocation-start@v1' as const;
export const MODEL_INVOCATION_START_COMMAND_SCHEMA =
'qinglong/model-invocation-start-command@v1' as const;
export const MODEL_INVOCATION_COMPLETION_SCHEMA =
'qinglong/model-invocation-completion@v1' as const;
export const MODEL_INVOCATION_COMPLETION_COMMAND_SCHEMA =
'qinglong/model-invocation-completion-command@v1' as const;
export const MAX_MODEL_INVOCATION_RECORD_JSON_BYTES = 24 * 1024;
export const MAX_MODEL_INVOCATION_RECOVERY_PAGE_SIZE = 128;
export const MODEL_INVOCATION_OUTCOMES = [
'succeeded',
'failed',
'timed_out',
'outcome_unknown',
] as const;
export const MODEL_INVOCATION_MUTATION_PHASES = [
'start',
'completion',
'resolution',
] as const;
export type ModelInvocationOutcome = (typeof MODEL_INVOCATION_OUTCOMES)[number];
export type ModelInvocationMutationPhase =
(typeof MODEL_INVOCATION_MUTATION_PHASES)[number];
export interface ModelInvocationStartRecord {
readonly schema: typeof MODEL_INVOCATION_START_SCHEMA;
readonly invocationId: string;
readonly projectId: string;
readonly runId: string;
readonly stepRunId: string;
readonly traceId: string;
readonly provider: string;
readonly model: string;
readonly policyRevision: string;
readonly requestDigest: string;
readonly inputBytes: number;
readonly maxOutputTokens: number;
readonly deadlineAtMs: number;
readonly startedStepRunVersion: number;
readonly stepRunMutationId: string;
readonly stepRunMutationDigest: string;
readonly startedStepRunDigest: string;
readonly runEventId: string;
readonly admittedAtMs: number;
readonly startDigest: string;
}
export interface ModelInvocationStartCommand {
readonly schema: typeof MODEL_INVOCATION_START_COMMAND_SCHEMA;
readonly start: Readonly<ModelInvocationStartRecord>;
readonly stepRunMutation: Readonly<StepRunMutation>;
readonly commandDigest: string;
}
export interface ModelInvocationCompletionRecord {
readonly schema: typeof MODEL_INVOCATION_COMPLETION_SCHEMA;
readonly invocationId: string;
readonly projectId: string;
readonly runId: string;
readonly stepRunId: string;
readonly traceId: string;
readonly startDigest: string;
readonly outcome: ModelInvocationOutcome;
readonly outputBytes: number;
readonly usage: Readonly<ModelUsage> | null;
readonly errorCode: string | null;
readonly completedStepRunVersion: number;
readonly stepRunMutationId: string;
readonly stepRunMutationDigest: string;
readonly completedStepRunDigest: string;
readonly runEventId: string;
readonly completedAtMs: number;
readonly completionDigest: string;
}
export interface ModelInvocationCompletionCommand {
readonly schema: typeof MODEL_INVOCATION_COMPLETION_COMMAND_SCHEMA;
readonly start: Readonly<ModelInvocationStartRecord>;
readonly completion: Readonly<ModelInvocationCompletionRecord>;
readonly stepRunMutation: Readonly<StepRunMutation>;
readonly commandDigest: string;
}
export interface CommitModelInvocationResult<T> {
readonly status: 'created' | 'existing';
readonly record: Readonly<T>;
}
export interface ModelInvocationAuthoritySnapshot {
readonly projectId: string;
readonly runId: string;
readonly runVersion: number;
readonly runEventSequence: number;
readonly stepRun: Readonly<StepRunRecord>;
}
export interface ModelInvocationRecoveryPage {
readonly observedAtMs: number;
readonly candidates: readonly Readonly<ModelInvocationStartRecord>[];
readonly hasMore: boolean;
}
export interface ModelInvocationRepository {
findStart(
invocationId: string,
): Promise<Readonly<ModelInvocationStartRecord> | null>;
findCompletion(
invocationId: string,
): Promise<Readonly<ModelInvocationCompletionRecord> | null>;
readAuthority(
identity: Readonly<{
projectId: string;
runId: string;
stepRunId: string;
}>,
): Promise<Readonly<ModelInvocationAuthoritySnapshot> | null>;
listIncomplete(limit: number): Promise<Readonly<ModelInvocationRecoveryPage>>;
admit(
command: ModelInvocationStartCommand,
): Promise<Readonly<CommitModelInvocationResult<ModelInvocationStartRecord>>>;
complete(
command: ModelInvocationCompletionCommand,
): Promise<
Readonly<CommitModelInvocationResult<ModelInvocationCompletionRecord>>
>;
}
export class InvalidModelInvocationError extends TypeError {
readonly code = 'MODEL_INVOCATION_INVALID';
constructor(message: string) {
super(`Model invocation is invalid: ${message}`);
this.name = 'InvalidModelInvocationError';
}
}
export class ModelInvocationConflictError extends Error {
readonly code = 'MODEL_INVOCATION_CONFLICT';
constructor() {
super('Model invocation conflicts with durable state');
this.name = 'ModelInvocationConflictError';
}
}
export class ModelInvocationRepositoryUnavailableError extends Error {
readonly code = 'MODEL_INVOCATION_REPOSITORY_UNAVAILABLE';
constructor(options?: ErrorOptions) {
super('Model invocation repository is unavailable', options);
this.name = 'ModelInvocationRepositoryUnavailableError';
}
}
@@ -0,0 +1,244 @@
import { normalizeStepRunMutation, type StepRunMutation } from '@qinglong/runtime-core/step-run';
import type { ModelInvocationAuditRecord } from '../../model-gateway/model';
import {
MODEL_INVOCATION_START_COMMAND_SCHEMA,
MODEL_INVOCATION_START_SCHEMA,
type ModelInvocationStartCommand,
type ModelInvocationStartRecord,
} from './contracts';
import {
START_COMMAND_DIGEST_DOMAIN,
START_DIGEST_DOMAIN,
assertJsonBudget,
createModelInvocationMutationIdentity,
dataRecord,
digest,
exactKeys,
hash,
identifier,
integer,
invalid,
requestDigest,
} from './common';
import { normalizeAdmissionAudit } from './audit';
function startWithoutDigest(
value: Readonly<ModelInvocationStartRecord>,
): Omit<ModelInvocationStartRecord, 'startDigest'> {
const { startDigest: _startDigest, ...unsigned } = value;
return unsigned;
}
export function normalizeModelInvocationStartRecord(
value: ModelInvocationStartRecord,
): Readonly<ModelInvocationStartRecord> {
const candidate = dataRecord(value, 'start record');
exactKeys(
candidate,
[
'admittedAtMs',
'deadlineAtMs',
'inputBytes',
'invocationId',
'maxOutputTokens',
'model',
'policyRevision',
'projectId',
'provider',
'requestDigest',
'runEventId',
'runId',
'schema',
'startDigest',
'startedStepRunDigest',
'startedStepRunVersion',
'stepRunId',
'stepRunMutationDigest',
'stepRunMutationId',
'traceId',
],
'start record',
);
if (value.schema !== MODEL_INVOCATION_START_SCHEMA) {
invalid('start schema is invalid');
}
const normalized = Object.freeze({
schema: MODEL_INVOCATION_START_SCHEMA,
invocationId: identifier(value.invocationId, 'invocation id'),
projectId: identifier(value.projectId, 'project id'),
runId: identifier(value.runId, 'Run id'),
stepRunId: identifier(value.stepRunId, 'StepRun id'),
traceId: identifier(value.traceId, 'trace id'),
provider: identifier(value.provider, 'provider'),
model: identifier(value.model, 'model'),
policyRevision: identifier(value.policyRevision, 'policy revision'),
requestDigest: requestDigest(value.requestDigest),
inputBytes: integer(value.inputBytes, 1, 256 * 1024, 'input bytes'),
maxOutputTokens: integer(
value.maxOutputTokens,
1,
32_768,
'max output tokens',
),
deadlineAtMs: integer(
value.deadlineAtMs,
0,
Number.MAX_SAFE_INTEGER,
'deadline',
),
startedStepRunVersion: integer(
value.startedStepRunVersion,
2,
2_147_483_647,
'started StepRun version',
),
stepRunMutationId: identifier(
value.stepRunMutationId,
'StepRun mutation id',
),
stepRunMutationDigest: digest(
value.stepRunMutationDigest,
'StepRun mutation digest',
),
startedStepRunDigest: digest(
value.startedStepRunDigest,
'started StepRun digest',
),
runEventId: identifier(value.runEventId, 'RunEvent id'),
admittedAtMs: integer(
value.admittedAtMs,
0,
Number.MAX_SAFE_INTEGER,
'admitted time',
),
startDigest: digest(value.startDigest, 'start digest'),
});
const identity = createModelInvocationMutationIdentity(
normalized.invocationId,
'start',
);
if (
normalized.deadlineAtMs <= normalized.admittedAtMs ||
normalized.stepRunMutationId !== identity.mutationId ||
normalized.runEventId !== identity.eventId ||
hash(START_DIGEST_DOMAIN, startWithoutDigest(normalized)) !==
normalized.startDigest
) {
invalid('start time or digest is invalid');
}
assertJsonBudget(normalized, 'start record');
return normalized;
}
function assertStartMutation(
mutation: Readonly<StepRunMutation>,
audit: Readonly<ModelInvocationAuditRecord>,
): void {
const identity = createModelInvocationMutationIdentity(
audit.requestId,
'start',
);
if (
mutation.previousStatus !== 'ready' ||
mutation.mutationId !== identity.mutationId ||
mutation.stepRun.kind !== 'model' ||
mutation.stepRun.status !== 'running' ||
mutation.stepRun.runId !== audit.runId ||
mutation.stepRun.id !== audit.stepRunId ||
mutation.stepRun.updatedAtMs !== audit.occurredAtMs ||
mutation.event.id !== identity.eventId ||
mutation.event.dedupeKey !== identity.dedupeKey ||
mutation.event.type !== 'step.running'
) {
invalid('start StepRun mutation is not exact');
}
}
export function createModelInvocationStartCommand(
auditValue: ModelInvocationAuditRecord,
mutationValue: StepRunMutation,
): Readonly<ModelInvocationStartCommand> {
const audit = normalizeAdmissionAudit(auditValue);
const mutation = normalizeStepRunMutation(mutationValue);
assertStartMutation(mutation, audit);
const unsigned = Object.freeze({
schema: MODEL_INVOCATION_START_SCHEMA,
invocationId: audit.requestId,
projectId: audit.projectId,
runId: audit.runId,
stepRunId: audit.stepRunId,
traceId: audit.traceId,
provider: audit.provider,
model: audit.model,
policyRevision: audit.policyRevision,
requestDigest: audit.requestDigest,
inputBytes: audit.inputBytes,
maxOutputTokens: audit.maxOutputTokens,
deadlineAtMs: audit.deadlineAtMs,
startedStepRunVersion: mutation.stepRun.version,
stepRunMutationId: mutation.mutationId,
stepRunMutationDigest: mutation.mutationDigest,
startedStepRunDigest: mutation.stepRun.stepRunDigest,
runEventId: mutation.event.id,
admittedAtMs: audit.occurredAtMs,
});
const start = normalizeModelInvocationStartRecord({
...unsigned,
startDigest: hash(START_DIGEST_DOMAIN, unsigned),
});
const commandUnsigned = Object.freeze({
schema: MODEL_INVOCATION_START_COMMAND_SCHEMA,
start,
stepRunMutation: mutation,
});
return Object.freeze({
...commandUnsigned,
commandDigest: hash(START_COMMAND_DIGEST_DOMAIN, commandUnsigned),
});
}
export function normalizeModelInvocationStartCommand(
value: ModelInvocationStartCommand,
): Readonly<ModelInvocationStartCommand> {
const candidate = dataRecord(value, 'start command');
exactKeys(
candidate,
['commandDigest', 'schema', 'start', 'stepRunMutation'],
'start command',
);
if (value.schema !== MODEL_INVOCATION_START_COMMAND_SCHEMA) {
invalid('start command schema is invalid');
}
const start = normalizeModelInvocationStartRecord(value.start);
const mutation = normalizeStepRunMutation(value.stepRunMutation);
const canonical = createModelInvocationStartCommand(
{
phase: 'admitted',
projectId: start.projectId,
runId: start.runId,
stepRunId: start.stepRunId,
traceId: start.traceId,
requestId: start.invocationId,
provider: start.provider,
model: start.model,
policyRevision: start.policyRevision,
requestDigest: start.requestDigest,
deadlineAtMs: start.deadlineAtMs,
inputBytes: start.inputBytes,
maxOutputTokens: start.maxOutputTokens,
outputBytes: 0,
usage: null,
errorCode: null,
occurredAtMs: start.admittedAtMs,
},
mutation,
);
if (
canonical.start.startDigest !== start.startDigest ||
digest(value.commandDigest, 'command digest') !== canonical.commandDigest
) {
invalid('start command is not canonical');
}
return canonical;
}
@@ -0,0 +1,34 @@
export {
MAX_MODEL_INVOCATION_RECORD_JSON_BYTES,
MAX_MODEL_INVOCATION_RECOVERY_PAGE_SIZE,
MODEL_INVOCATION_COMPLETION_COMMAND_SCHEMA,
MODEL_INVOCATION_COMPLETION_SCHEMA,
MODEL_INVOCATION_MUTATION_PHASES,
MODEL_INVOCATION_OUTCOMES,
MODEL_INVOCATION_START_COMMAND_SCHEMA,
MODEL_INVOCATION_START_SCHEMA,
InvalidModelInvocationError,
ModelInvocationConflictError,
ModelInvocationRepositoryUnavailableError,
type CommitModelInvocationResult,
type ModelInvocationAuthoritySnapshot,
type ModelInvocationCompletionCommand,
type ModelInvocationCompletionRecord,
type ModelInvocationMutationPhase,
type ModelInvocationOutcome,
type ModelInvocationRecoveryPage,
type ModelInvocationRepository,
type ModelInvocationStartCommand,
type ModelInvocationStartRecord,
} from './model-invocation/contracts';
export { createModelInvocationMutationIdentity } from './model-invocation/common';
export {
createModelInvocationStartCommand,
normalizeModelInvocationStartCommand,
normalizeModelInvocationStartRecord,
} from './model-invocation/startProtocol';
export {
createModelInvocationCompletionCommand,
normalizeModelInvocationCompletionCommand,
normalizeModelInvocationCompletionRecord,
} from './model-invocation/completionProtocol';
@@ -0,0 +1,584 @@
import { Buffer } from 'node:buffer';
import { createHash } from 'node:crypto';
// Model Invocation owns durable ambiguity resolution alongside its transaction contract.
import {
normalizeStepRunMutation,
transitionStepRunMutation,
type StepRunMutation,
type StepRunStatus,
} from '@qinglong/runtime-core/step-run';
import {
MAX_MODEL_INVOCATION_RECORD_JSON_BYTES,
ModelInvocationConflictError,
ModelInvocationRepositoryUnavailableError,
createModelInvocationMutationIdentity,
normalizeModelInvocationCompletionRecord,
type CommitModelInvocationResult,
type ModelInvocationCompletionRecord,
type ModelInvocationRepository,
} from './modelInvocation';
export const MODEL_INVOCATION_RESOLUTION_SCHEMA =
'qinglong/model-invocation-resolution@v1' as const;
export const MODEL_INVOCATION_RESOLUTION_COMMAND_SCHEMA =
'qinglong/model-invocation-resolution-command@v1' as const;
export const MODEL_INVOCATION_RESOLUTION_DECISIONS = [
'retry',
'fail',
'cancel',
] as const;
export type ModelInvocationResolutionDecision =
(typeof MODEL_INVOCATION_RESOLUTION_DECISIONS)[number];
export interface ModelInvocationResolutionRecord {
readonly schema: typeof MODEL_INVOCATION_RESOLUTION_SCHEMA;
readonly resolutionId: string;
readonly invocationId: string;
readonly projectId: string;
readonly runId: string;
readonly stepRunId: string;
readonly traceId: string;
readonly completionDigest: string;
readonly decision: ModelInvocationResolutionDecision;
readonly resolvedByUserId: string;
readonly resolvedStepRunVersion: number;
readonly stepRunMutationId: string;
readonly stepRunMutationDigest: string;
readonly resolvedStepRunDigest: string;
readonly runEventId: string;
readonly resolvedAtMs: number;
readonly resolutionDigest: string;
}
export interface ModelInvocationResolutionCommand {
readonly schema: typeof MODEL_INVOCATION_RESOLUTION_COMMAND_SCHEMA;
readonly completion: Readonly<ModelInvocationCompletionRecord>;
readonly resolution: Readonly<ModelInvocationResolutionRecord>;
readonly stepRunMutation: Readonly<StepRunMutation>;
readonly commandDigest: string;
}
export interface ModelInvocationResolutionRepository
extends ModelInvocationRepository {
findResolution(
invocationId: string,
): Promise<Readonly<ModelInvocationResolutionRecord> | null>;
resolve(
command: ModelInvocationResolutionCommand,
): Promise<
Readonly<CommitModelInvocationResult<ModelInvocationResolutionRecord>>
>;
}
export class InvalidModelInvocationResolutionError extends TypeError {
readonly code = 'MODEL_INVOCATION_RESOLUTION_INVALID';
constructor(message: string) {
super(`Model invocation resolution is invalid: ${message}`);
this.name = 'InvalidModelInvocationResolutionError';
}
}
const IDENTITY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
const DIGEST_PATTERN = /^[0-9a-f]{64}$/;
const RESOLUTION_DIGEST_DOMAIN = Buffer.from(
'qinglong/model-invocation-resolution-digest@v1\0',
'utf8',
);
const RESOLUTION_COMMAND_DIGEST_DOMAIN = Buffer.from(
'qinglong/model-invocation-resolution-command-digest@v1\0',
'utf8',
);
function invalid(message: string): never {
throw new InvalidModelInvocationResolutionError(message);
}
function dataRecord(value: unknown, label: string): Record<string, unknown> {
if (
!value ||
typeof value !== 'object' ||
Array.isArray(value) ||
Object.getPrototypeOf(value) !== Object.prototype
) {
return invalid(`${label} must be a plain object`);
}
return value as Record<string, unknown>;
}
function exactKeys(
value: object,
expected: readonly string[],
label: string,
): void {
const actual = Object.keys(value).sort();
const canonical = [...expected].sort();
if (
actual.length !== canonical.length ||
actual.some((key, index) => key !== canonical[index])
) {
invalid(`${label} shape is invalid`);
}
}
function identifier(value: unknown, label: string): string {
if (typeof value !== 'string' || !IDENTITY_PATTERN.test(value)) {
return invalid(`${label} is invalid`);
}
return value;
}
function digest(value: unknown, label: string): string {
if (typeof value !== 'string' || !DIGEST_PATTERN.test(value)) {
return invalid(`${label} is invalid`);
}
return value;
}
function integer(
value: unknown,
minimum: number,
maximum: number,
label: string,
): number {
if (
!Number.isSafeInteger(value) ||
(value as number) < minimum ||
(value as number) > maximum
) {
return invalid(`${label} is invalid`);
}
return value as number;
}
function hash(domain: Uint8Array, value: unknown): string {
return createHash('sha256')
.update(domain)
.update(JSON.stringify(value))
.digest('hex');
}
function withoutDigest(
value: Readonly<ModelInvocationResolutionRecord>,
): Omit<ModelInvocationResolutionRecord, 'resolutionDigest'> {
const { resolutionDigest: _resolutionDigest, ...unsigned } = value;
return unsigned;
}
export function resolutionTransition(
decision: ModelInvocationResolutionDecision,
): Readonly<{
to: StepRunStatus;
resultCode?: string;
errorSummary?: string;
}> {
if (decision === 'retry') return Object.freeze({ to: 'ready' });
if (decision === 'fail') {
return Object.freeze({
to: 'failed',
resultCode: 'model_outcome_rejected',
errorSummary: 'Unknown model outcome rejected by operator',
});
}
if (decision === 'cancel') {
return Object.freeze({
to: 'cancelled',
resultCode: 'model_outcome_cancelled',
});
}
return invalid('decision is invalid');
}
export function normalizeModelInvocationResolutionRecord(
value: ModelInvocationResolutionRecord,
): Readonly<ModelInvocationResolutionRecord> {
const candidate = dataRecord(value, 'resolution record');
exactKeys(
candidate,
[
'completionDigest',
'decision',
'invocationId',
'projectId',
'resolutionDigest',
'resolutionId',
'resolvedAtMs',
'resolvedByUserId',
'resolvedStepRunDigest',
'resolvedStepRunVersion',
'runEventId',
'runId',
'schema',
'stepRunId',
'stepRunMutationDigest',
'stepRunMutationId',
'traceId',
],
'resolution record',
);
if (
value.schema !== MODEL_INVOCATION_RESOLUTION_SCHEMA ||
!MODEL_INVOCATION_RESOLUTION_DECISIONS.includes(value.decision)
) {
invalid('resolution schema or decision is invalid');
}
const normalized = Object.freeze({
schema: MODEL_INVOCATION_RESOLUTION_SCHEMA,
resolutionId: identifier(value.resolutionId, 'resolution id'),
invocationId: identifier(value.invocationId, 'invocation id'),
projectId: identifier(value.projectId, 'project id'),
runId: identifier(value.runId, 'Run id'),
stepRunId: identifier(value.stepRunId, 'StepRun id'),
traceId: identifier(value.traceId, 'trace id'),
completionDigest: digest(value.completionDigest, 'completion digest'),
decision: value.decision,
resolvedByUserId: identifier(value.resolvedByUserId, 'resolving user id'),
resolvedStepRunVersion: integer(
value.resolvedStepRunVersion,
4,
2_147_483_647,
'resolved StepRun version',
),
stepRunMutationId: identifier(
value.stepRunMutationId,
'StepRun mutation id',
),
stepRunMutationDigest: digest(
value.stepRunMutationDigest,
'StepRun mutation digest',
),
resolvedStepRunDigest: digest(
value.resolvedStepRunDigest,
'resolved StepRun digest',
),
runEventId: identifier(value.runEventId, 'RunEvent id'),
resolvedAtMs: integer(
value.resolvedAtMs,
0,
Number.MAX_SAFE_INTEGER,
'resolved time',
),
resolutionDigest: digest(value.resolutionDigest, 'resolution digest'),
});
const identity = createModelInvocationMutationIdentity(
normalized.invocationId,
'resolution',
);
if (
normalized.resolutionId !== identity.dedupeKey ||
normalized.stepRunMutationId !== identity.mutationId ||
normalized.runEventId !== identity.eventId ||
hash(RESOLUTION_DIGEST_DOMAIN, withoutDigest(normalized)) !==
normalized.resolutionDigest
) {
invalid('resolution identity or digest is invalid');
}
if (
Buffer.byteLength(JSON.stringify(normalized), 'utf8') >
MAX_MODEL_INVOCATION_RECORD_JSON_BYTES
) {
invalid('resolution record exceeds its JSON budget');
}
return normalized;
}
export function createModelInvocationResolutionCommand(
completionValue: ModelInvocationCompletionRecord,
decision: ModelInvocationResolutionDecision,
resolvedByUserIdValue: string,
mutationValue: StepRunMutation,
): Readonly<ModelInvocationResolutionCommand> {
const completion = normalizeModelInvocationCompletionRecord(completionValue);
const transition = resolutionTransition(decision);
const resolvedByUserId = identifier(
resolvedByUserIdValue,
'resolving user id',
);
const mutation = normalizeStepRunMutation(mutationValue);
const identity = createModelInvocationMutationIdentity(
completion.invocationId,
'resolution',
);
if (
completion.outcome !== 'outcome_unknown' ||
mutation.previousStatus !== 'lost' ||
mutation.expectedStepRunVersion !== completion.completedStepRunVersion ||
mutation.expectedStepRunDigest !== completion.completedStepRunDigest ||
mutation.mutationId !== identity.mutationId ||
mutation.runId !== completion.runId ||
mutation.stepRun.id !== completion.stepRunId ||
mutation.stepRun.runId !== completion.runId ||
mutation.stepRun.kind !== 'model' ||
mutation.stepRun.status !== transition.to ||
mutation.event.id !== identity.eventId ||
mutation.event.dedupeKey !== identity.dedupeKey ||
mutation.event.type !== `step.${transition.to}` ||
mutation.event.actorType !== 'user' ||
mutation.event.actorId !== resolvedByUserId ||
mutation.stepRun.updatedAtMs < completion.completedAtMs ||
(transition.to === 'ready'
? mutation.stepRun.outputRef !== null ||
mutation.stepRun.resultCode !== null ||
mutation.stepRun.errorSummary !== null
: mutation.stepRun.resultCode !== transition.resultCode ||
mutation.stepRun.errorSummary !==
(transition.errorSummary === undefined
? null
: transition.errorSummary))
) {
invalid('resolution completion or StepRun mutation is not exact');
}
const unsigned = Object.freeze({
schema: MODEL_INVOCATION_RESOLUTION_SCHEMA,
resolutionId: identity.dedupeKey,
invocationId: completion.invocationId,
projectId: completion.projectId,
runId: completion.runId,
stepRunId: completion.stepRunId,
traceId: completion.traceId,
completionDigest: completion.completionDigest,
decision,
resolvedByUserId,
resolvedStepRunVersion: mutation.stepRun.version,
stepRunMutationId: mutation.mutationId,
stepRunMutationDigest: mutation.mutationDigest,
resolvedStepRunDigest: mutation.stepRun.stepRunDigest,
runEventId: mutation.event.id,
resolvedAtMs: mutation.stepRun.updatedAtMs,
});
const resolution = normalizeModelInvocationResolutionRecord({
...unsigned,
resolutionDigest: hash(RESOLUTION_DIGEST_DOMAIN, unsigned),
});
const commandUnsigned = Object.freeze({
schema: MODEL_INVOCATION_RESOLUTION_COMMAND_SCHEMA,
completion,
resolution,
stepRunMutation: mutation,
});
return Object.freeze({
...commandUnsigned,
commandDigest: hash(RESOLUTION_COMMAND_DIGEST_DOMAIN, commandUnsigned),
});
}
export function normalizeModelInvocationResolutionCommand(
value: ModelInvocationResolutionCommand,
): Readonly<ModelInvocationResolutionCommand> {
const candidate = dataRecord(value, 'resolution command');
exactKeys(
candidate,
['commandDigest', 'completion', 'resolution', 'schema', 'stepRunMutation'],
'resolution command',
);
if (value.schema !== MODEL_INVOCATION_RESOLUTION_COMMAND_SCHEMA) {
invalid('resolution command schema is invalid');
}
const completion = normalizeModelInvocationCompletionRecord(value.completion);
const resolution = normalizeModelInvocationResolutionRecord(value.resolution);
const mutation = normalizeStepRunMutation(value.stepRunMutation);
const canonical = createModelInvocationResolutionCommand(
completion,
resolution.decision,
resolution.resolvedByUserId,
mutation,
);
if (
canonical.resolution.resolutionDigest !== resolution.resolutionDigest ||
digest(value.commandDigest, 'command digest') !== canonical.commandDigest
) {
invalid('resolution command is not canonical');
}
return canonical;
}
export function assertResolutionMatchesDecision(
resolutionValue: ModelInvocationResolutionRecord,
completionValue: ModelInvocationCompletionRecord,
decision: ModelInvocationResolutionDecision,
resolvedByUserId: string,
): Readonly<ModelInvocationResolutionRecord> {
const resolution = normalizeModelInvocationResolutionRecord(resolutionValue);
const completion = normalizeModelInvocationCompletionRecord(completionValue);
if (
resolution.invocationId !== completion.invocationId ||
resolution.projectId !== completion.projectId ||
resolution.runId !== completion.runId ||
resolution.stepRunId !== completion.stepRunId ||
resolution.traceId !== completion.traceId ||
resolution.completionDigest !== completion.completionDigest ||
resolution.decision !== decision ||
resolution.resolvedByUserId !== resolvedByUserId
) {
throw new ModelInvocationConflictError();
}
return resolution;
}
export interface ResolveModelInvocationOptions {
readonly invocationId: string;
readonly decision: ModelInvocationResolutionDecision;
readonly resolvedByUserId: string;
readonly resolvedAtMs: number;
}
const MAX_RESOLUTION_COORDINATOR_ATTEMPTS = 3;
export class DurableModelInvocationResolutionCoordinator {
constructor(
private readonly repository: ModelInvocationResolutionRepository,
) {
if (
!repository ||
typeof repository.findCompletion !== 'function' ||
typeof repository.findResolution !== 'function' ||
typeof repository.readAuthority !== 'function' ||
typeof repository.resolve !== 'function'
) {
throw new ModelInvocationRepositoryUnavailableError();
}
}
async resolve(
optionsValue: ResolveModelInvocationOptions,
): Promise<
Readonly<CommitModelInvocationResult<ModelInvocationResolutionRecord>>
> {
const options = dataRecord(optionsValue, 'resolution options');
exactKeys(
options,
['decision', 'invocationId', 'resolvedAtMs', 'resolvedByUserId'],
'resolution options',
);
const invocationId = identifier(optionsValue.invocationId, 'invocation id');
const decision = optionsValue.decision;
resolutionTransition(decision);
const resolvedByUserId = identifier(
optionsValue.resolvedByUserId,
'resolving user id',
);
const resolvedAtMs = integer(
optionsValue.resolvedAtMs,
0,
Number.MAX_SAFE_INTEGER,
'resolved time',
);
const completion = await this.repository.findCompletion(invocationId);
if (!completion || completion.outcome !== 'outcome_unknown') {
throw new ModelInvocationConflictError();
}
const existing = await this.repository.findResolution(invocationId);
if (existing) {
return Object.freeze({
status: 'existing',
record: assertResolutionMatchesDecision(
existing,
completion,
decision,
resolvedByUserId,
),
});
}
for (
let attempt = 0;
attempt < MAX_RESOLUTION_COORDINATOR_ATTEMPTS;
attempt += 1
) {
const authority = await this.repository.readAuthority({
projectId: completion.projectId,
runId: completion.runId,
stepRunId: completion.stepRunId,
});
if (
!authority ||
authority.stepRun.kind !== 'model' ||
authority.stepRun.status !== 'lost' ||
authority.stepRun.version !== completion.completedStepRunVersion ||
authority.stepRun.stepRunDigest !== completion.completedStepRunDigest
) {
throw new ModelInvocationConflictError();
}
const transition = resolutionTransition(decision);
const identity = createModelInvocationMutationIdentity(
invocationId,
'resolution',
);
const command = createModelInvocationResolutionCommand(
completion,
decision,
resolvedByUserId,
transitionStepRunMutation(
authority.stepRun,
{
expectedVersion: authority.stepRun.version,
expectedDigest: authority.stepRun.stepRunDigest,
mutationId: identity.mutationId,
to: transition.to,
atMs: resolvedAtMs,
...(transition.resultCode === undefined
? {}
: { resultCode: transition.resultCode }),
...(transition.errorSummary === undefined
? {}
: { errorSummary: transition.errorSummary }),
},
{
expectedRunVersion: authority.runVersion,
expectedRunEventSequence: authority.runEventSequence,
eventId: identity.eventId,
dedupeKey: identity.dedupeKey,
actor: { type: 'user', id: resolvedByUserId },
},
),
);
try {
return await this.repository.resolve(command);
} catch (error) {
const stored = await this.#resolutionAfterFailure(
completion,
decision,
resolvedByUserId,
error,
);
if (stored) {
return Object.freeze({ status: 'existing', record: stored });
}
if (
!(error instanceof ModelInvocationConflictError) ||
attempt + 1 >= MAX_RESOLUTION_COORDINATOR_ATTEMPTS
) {
throw error;
}
}
}
throw new ModelInvocationConflictError();
}
async #resolutionAfterFailure(
completion: Readonly<ModelInvocationCompletionRecord>,
decision: ModelInvocationResolutionDecision,
resolvedByUserId: string,
original: unknown,
): Promise<Readonly<ModelInvocationResolutionRecord> | null> {
try {
const stored = await this.repository.findResolution(
completion.invocationId,
);
return stored
? assertResolutionMatchesDecision(
stored,
completion,
decision,
resolvedByUserId,
)
: null;
} catch {
throw original;
}
}
}
@@ -0,0 +1,258 @@
import type { PostgresPool } from '@qinglong/runtime-core';
import {
normalizeModelInvocationPriceQuote,
type ModelInvocationPriceQuote,
} from '../../pricing/pricing';
import {
ModelInvocationProjectQuotaExceededError,
createModelInvocationQuotaReservation,
normalizeModelInvocationQuotaAdmission,
type ModelInvocationQuotaAdmission,
type ModelInvocationQuotaReservation,
} from '../../usage/usageQuota';
import {
ModelInvocationConflictError,
normalizeModelInvocationStartCommand,
type CommitModelInvocationResult,
type ModelInvocationStartCommand,
type ModelInvocationStartRecord,
} from '../modelInvocation';
import type { Row } from './authority';
import { integer, unavailable } from './authority';
import { parsePriceQuote, parseQuotaReservation, parseStart } from './codec';
import {
applyMutation,
assertCurrent,
insertPriceQuote,
insertQuotaReservation,
insertStart,
quotaWindowUsage,
} from './mutations';
import { priceQuoteRows, quotaReservationRows, startRows } from './queries';
import { runPostgresModelInvocationTransaction } from './transaction';
export async function admitOperation(
pool: PostgresPool,
commandValue: ModelInvocationStartCommand,
): Promise<Readonly<CommitModelInvocationResult<ModelInvocationStartRecord>>> {
const command = normalizeModelInvocationStartCommand(commandValue);
const start = command.start;
return runPostgresModelInvocationTransaction(pool, async (client) => {
const existing = await startRows(
client,
`start.invocation_id = $1 OR
start.mutation_id = $2 OR start.run_event_id = $3`,
[start.invocationId, start.stepRunMutationId, start.runEventId],
);
if (existing.length > 1) throw new ModelInvocationConflictError();
if (existing[0]) {
const stored = parseStart(existing[0]);
if (JSON.stringify(stored) !== JSON.stringify(start)) {
throw new ModelInvocationConflictError();
}
return Object.freeze({ status: 'existing' as const, record: stored });
}
await assertCurrent(client, command.stepRunMutation, start.projectId);
await applyMutation(client, command.stepRunMutation);
await insertStart(client, start);
return Object.freeze({ status: 'created' as const, record: start });
});
}
export async function admitWithQuotaOperation(
pool: PostgresPool,
commandValue: ModelInvocationStartCommand,
admissionValue: ModelInvocationQuotaAdmission,
): Promise<Readonly<CommitModelInvocationResult<ModelInvocationStartRecord>>> {
const command = normalizeModelInvocationStartCommand(commandValue);
const admission = normalizeModelInvocationQuotaAdmission(admissionValue);
const start = command.start;
if (
admission.invocationId !== start.invocationId ||
admission.projectId !== start.projectId ||
admission.modelPolicyRevision !== start.policyRevision
) {
throw new ModelInvocationConflictError();
}
return runPostgresModelInvocationTransaction(pool, async (client) => {
const existing = await startRows(
client,
`start.invocation_id = $1 OR
start.mutation_id = $2 OR start.run_event_id = $3`,
[start.invocationId, start.stepRunMutationId, start.runEventId],
);
if (existing.length > 1) throw new ModelInvocationConflictError();
if (existing[0]) {
const stored = parseStart(existing[0]);
const reservations = await quotaReservationRows(
client,
'reservation.invocation_id = $1',
[start.invocationId],
);
if (
JSON.stringify(stored) !== JSON.stringify(start) ||
reservations.length !== 1 ||
parseQuotaReservation(reservations[0]!).admissionDigest !==
admission.admissionDigest
) {
throw new ModelInvocationConflictError();
}
return Object.freeze({ status: 'existing' as const, record: stored });
}
const observation = await client.query<Row>(
`SELECT floor(extract(epoch FROM statement_timestamp()) * 1000)::bigint
AS "observedAtMs"`,
);
const observedRow = observation.rows[0];
if (observation.rows.length !== 1 || !observedRow) throw unavailable();
const reservation = createModelInvocationQuotaReservation(
admission,
integer(observedRow, 'observedAtMs'),
);
await client.query(
`SELECT pg_advisory_xact_lock(hashtextextended($1, 0))`,
[
JSON.stringify([
start.projectId,
reservation.windowStartMs,
reservation.windowMs,
]),
],
);
const usage = await quotaWindowUsage(
client,
start.projectId,
reservation.windowStartMs,
reservation.windowMs,
);
if (
usage.invocationCount + 1 > reservation.maxInvocations ||
usage.effectiveTokens + reservation.reservedTokens >
reservation.maxTokens ||
(reservation.maxCostMicros !== null &&
(usage.unknownCostInvocations !== 0 ||
usage.effectiveCostMicros + reservation.reservedCostMicros! >
reservation.maxCostMicros))
) {
throw new ModelInvocationProjectQuotaExceededError();
}
await assertCurrent(client, command.stepRunMutation, start.projectId);
await applyMutation(client, command.stepRunMutation);
await insertStart(client, start);
await insertQuotaReservation(client, reservation);
return Object.freeze({ status: 'created' as const, record: start });
});
}
export async function admitWithPricingOperation(
pool: PostgresPool,
commandValue: ModelInvocationStartCommand,
quoteValue: ModelInvocationPriceQuote,
admissionValue?: ModelInvocationQuotaAdmission,
): Promise<Readonly<CommitModelInvocationResult<ModelInvocationStartRecord>>> {
const command = normalizeModelInvocationStartCommand(commandValue);
const quote = normalizeModelInvocationPriceQuote(quoteValue);
const admission =
admissionValue === undefined
? undefined
: normalizeModelInvocationQuotaAdmission(admissionValue);
const start = command.start;
if (
quote.invocationId !== start.invocationId ||
quote.projectId !== start.projectId ||
quote.modelPolicyRevision !== start.policyRevision ||
quote.provider !== start.provider ||
quote.model !== start.model ||
quote.maxOutputTokens !== start.maxOutputTokens ||
(admission !== undefined &&
(admission.invocationId !== start.invocationId ||
admission.projectId !== start.projectId ||
admission.modelPolicyRevision !== start.policyRevision ||
(admission.maxCostMicros !== null &&
admission.reservedCostMicros !== quote.reservedCostMicros)))
) {
throw new ModelInvocationConflictError();
}
return runPostgresModelInvocationTransaction(pool, async (client) => {
const existing = await startRows(
client,
`start.invocation_id = $1 OR
start.mutation_id = $2 OR start.run_event_id = $3`,
[start.invocationId, start.stepRunMutationId, start.runEventId],
);
if (existing.length > 1) throw new ModelInvocationConflictError();
if (existing[0]) {
const [quotes, reservations] = await Promise.all([
priceQuoteRows(client, 'quote.invocation_id = $1', [
start.invocationId,
]),
quotaReservationRows(client, 'reservation.invocation_id = $1', [
start.invocationId,
]),
]);
const stored = parseStart(existing[0]);
if (
JSON.stringify(stored) !== JSON.stringify(start) ||
quotes.length !== 1 ||
JSON.stringify(parsePriceQuote(quotes[0]!)) !== JSON.stringify(quote) ||
reservations.length !== (admission ? 1 : 0) ||
(admission !== undefined &&
parseQuotaReservation(reservations[0]!).admissionDigest !==
admission.admissionDigest)
) {
throw new ModelInvocationConflictError();
}
return Object.freeze({ status: 'existing' as const, record: stored });
}
let reservation: Readonly<ModelInvocationQuotaReservation> | undefined;
if (admission) {
const observation = await client.query<Row>(
`SELECT floor(extract(epoch FROM statement_timestamp()) * 1000)::bigint
AS "observedAtMs"`,
);
const observedRow = observation.rows[0];
if (observation.rows.length !== 1 || !observedRow) {
throw unavailable();
}
reservation = createModelInvocationQuotaReservation(
admission,
integer(observedRow, 'observedAtMs'),
);
await client.query(
`SELECT pg_advisory_xact_lock(hashtextextended($1, 0))`,
[
JSON.stringify([
start.projectId,
reservation.windowStartMs,
reservation.windowMs,
]),
],
);
const usage = await quotaWindowUsage(
client,
start.projectId,
reservation.windowStartMs,
reservation.windowMs,
);
if (
usage.invocationCount + 1 > reservation.maxInvocations ||
usage.effectiveTokens + reservation.reservedTokens >
reservation.maxTokens ||
(reservation.maxCostMicros !== null &&
(usage.unknownCostInvocations !== 0 ||
usage.effectiveCostMicros + reservation.reservedCostMicros! >
reservation.maxCostMicros))
) {
throw new ModelInvocationProjectQuotaExceededError();
}
}
await assertCurrent(client, command.stepRunMutation, start.projectId);
await applyMutation(client, command.stepRunMutation);
await insertStart(client, start);
await insertPriceQuote(client, quote);
if (reservation) await insertQuotaReservation(client, reservation);
return Object.freeze({ status: 'created' as const, record: start });
});
}
@@ -0,0 +1,118 @@
import type { PostgresQueryable } from '@qinglong/runtime-core';
import {
PluginPackagePromptOutputArtifactConflictError,
PluginPackagePromptOutputArtifactUnavailableError,
} from '../../prompt-output/pluginPackagePromptOutputArtifact';
import { ModelInvocationUsageSummaryLimitExceededError } from '../../usage/usageLedger';
import { ModelInvocationProjectQuotaExceededError } from '../../usage/usageQuota';
import {
MAX_MODEL_INVOCATION_RECOVERY_PAGE_SIZE,
ModelInvocationConflictError,
ModelInvocationRepositoryUnavailableError,
} from '../modelInvocation';
export type Row = Record<string, unknown>;
export type Queryable = Pick<PostgresQueryable, 'query'>;
export const IDENTIFIER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
export const TERMINAL_RUN_STATUSES = new Set([
'succeeded',
'failed',
'cancelled',
'timed_out',
]);
export const RETRYABLE_SQL_STATES = new Set(['40001', '40P01']);
export const MAX_TRANSACTION_ATTEMPTS = 3;
export function unavailable(
cause?: unknown,
): ModelInvocationRepositoryUnavailableError {
return new ModelInvocationRepositoryUnavailableError({
cause: cause instanceof Error ? cause : undefined,
});
}
export function identifier(value: unknown): string {
if (typeof value !== 'string' || !IDENTIFIER_PATTERN.test(value)) {
throw unavailable();
}
return value;
}
export function text(row: Row, key: string): string {
const value = row[key];
if (typeof value !== 'string') throw unavailable();
return value;
}
export function integer(row: Row, key: string): number {
const value = row[key];
if (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0) {
return value;
}
if (typeof value === 'string' && /^(0|[1-9]\d*)$/.test(value)) {
const parsed = Number(value);
if (Number.isSafeInteger(parsed)) return parsed;
}
throw unavailable();
}
export function nullableInteger(row: Row, key: string): number | null {
return row[key] === null ? null : integer(row, key);
}
export function recoveryLimit(value: number): number {
if (
!Number.isSafeInteger(value) ||
value < 1 ||
value > MAX_MODEL_INVOCATION_RECOVERY_PAGE_SIZE
) {
throw unavailable();
}
return value;
}
export function jsonObject(row: Row, key: string): Record<string, unknown> {
const value = row[key];
if (
value &&
typeof value === 'object' &&
!Array.isArray(value) &&
Object.getPrototypeOf(value) === Object.prototype
) {
return value as Record<string, unknown>;
}
if (typeof value === 'string') {
try {
return JSON.parse(value) as Record<string, unknown>;
} catch {
throw unavailable();
}
}
throw unavailable();
}
export function sqlState(error: unknown): string {
if (!error || typeof error !== 'object') return '';
const value = (error as { code?: unknown }).code;
return typeof value === 'string' ? value : '';
}
export function mapStorageError(error: unknown): Error {
if (
error instanceof ModelInvocationConflictError ||
error instanceof ModelInvocationRepositoryUnavailableError ||
error instanceof ModelInvocationUsageSummaryLimitExceededError ||
error instanceof ModelInvocationProjectQuotaExceededError ||
error instanceof PluginPackagePromptOutputArtifactConflictError ||
error instanceof PluginPackagePromptOutputArtifactUnavailableError
) {
return error;
}
const state = sqlState(error);
if (state === '23503' || state === '23505' || state === '23514') {
return new ModelInvocationConflictError();
}
return unavailable(error);
}
@@ -0,0 +1,367 @@
import {
normalizeStepRunRecord,
type StepRunRecord,
} from '@qinglong/runtime-core/step-run';
import {
normalizeModelInvocationPriceQuote,
normalizeModelInvocationPriceSettlement,
type ModelInvocationPriceQuote,
type ModelInvocationPriceSettlement,
} from '../../pricing/pricing';
import {
normalizeModelInvocationUsageLedgerRecord,
type ModelInvocationUsageLedgerRecord,
} from '../../usage/usageLedger';
import {
normalizeModelInvocationQuotaReservation,
normalizeModelInvocationQuotaSettlement,
type ModelInvocationQuotaReservation,
type ModelInvocationQuotaSettlement,
} from '../../usage/usageQuota';
import {
normalizeModelInvocationCompletionRecord,
normalizeModelInvocationStartRecord,
type ModelInvocationAuthoritySnapshot,
type ModelInvocationCompletionRecord,
type ModelInvocationStartRecord,
} from '../modelInvocation';
import {
normalizeModelInvocationResolutionRecord,
type ModelInvocationResolutionRecord,
} from '../modelInvocationResolution';
import type { Row } from './authority';
import {
integer,
jsonObject,
nullableInteger,
text,
unavailable,
} from './authority';
export function parseStart(row: Row): Readonly<ModelInvocationStartRecord> {
let start: Readonly<ModelInvocationStartRecord>;
try {
start = normalizeModelInvocationStartRecord(
jsonObject(row, 'recordJson') as unknown as ModelInvocationStartRecord,
);
} catch (error) {
throw unavailable(error);
}
if (
start.invocationId !== text(row, 'invocationId') ||
start.projectId !== text(row, 'projectId') ||
start.runId !== text(row, 'runId') ||
start.stepRunId !== text(row, 'stepRunId') ||
start.traceId !== text(row, 'traceId') ||
start.provider !== text(row, 'provider') ||
start.model !== text(row, 'model') ||
start.policyRevision !== text(row, 'policyRevision') ||
start.requestDigest !== text(row, 'requestDigest') ||
start.inputBytes !== integer(row, 'inputBytes') ||
start.maxOutputTokens !== integer(row, 'maxOutputTokens') ||
start.deadlineAtMs !== integer(row, 'deadlineAtMs') ||
start.admittedAtMs !== integer(row, 'admittedAtMs') ||
start.stepRunMutationId !== text(row, 'mutationId') ||
start.stepRunMutationDigest !== text(row, 'mutationDigest') ||
start.startedStepRunDigest !== text(row, 'stepRunDigest') ||
start.startedStepRunVersion !== integer(row, 'stepRunVersion') ||
start.runEventId !== text(row, 'runEventId') ||
start.startDigest !== text(row, 'startDigest')
) {
throw unavailable();
}
return start;
}
export function parseCompletion(
row: Row,
): Readonly<ModelInvocationCompletionRecord> {
let completion: Readonly<ModelInvocationCompletionRecord>;
try {
completion = normalizeModelInvocationCompletionRecord(
jsonObject(
row,
'recordJson',
) as unknown as ModelInvocationCompletionRecord,
);
} catch (error) {
throw unavailable(error);
}
if (
completion.invocationId !== text(row, 'invocationId') ||
completion.projectId !== text(row, 'projectId') ||
completion.runId !== text(row, 'runId') ||
completion.stepRunId !== text(row, 'stepRunId') ||
completion.traceId !== text(row, 'traceId') ||
completion.startDigest !== text(row, 'startDigest') ||
completion.outcome !== text(row, 'outcome') ||
completion.outputBytes !== integer(row, 'outputBytes') ||
completion.errorCode !== row.errorCode ||
completion.completedAtMs !== integer(row, 'completedAtMs') ||
completion.stepRunMutationId !== text(row, 'mutationId') ||
completion.stepRunMutationDigest !== text(row, 'mutationDigest') ||
completion.completedStepRunDigest !== text(row, 'stepRunDigest') ||
completion.completedStepRunVersion !== integer(row, 'stepRunVersion') ||
completion.runEventId !== text(row, 'runEventId') ||
completion.completionDigest !== text(row, 'completionDigest')
) {
throw unavailable();
}
return completion;
}
export function parseUsage(
row: Row,
): Readonly<ModelInvocationUsageLedgerRecord> {
let usage: Readonly<ModelInvocationUsageLedgerRecord>;
try {
usage = normalizeModelInvocationUsageLedgerRecord(
jsonObject(
row,
'recordJson',
) as unknown as ModelInvocationUsageLedgerRecord,
);
} catch (error) {
throw unavailable(error);
}
if (
usage.invocationId !== text(row, 'invocationId') ||
usage.projectId !== text(row, 'projectId') ||
usage.runId !== text(row, 'runId') ||
usage.stepRunId !== text(row, 'stepRunId') ||
usage.traceId !== text(row, 'traceId') ||
usage.provider !== text(row, 'provider') ||
usage.model !== text(row, 'model') ||
usage.policyRevision !== text(row, 'policyRevision') ||
usage.completionDigest !== text(row, 'completionDigest') ||
usage.outcome !== text(row, 'outcome') ||
usage.settledAtMs !== integer(row, 'settledAtMs') ||
usage.inputBytes !== integer(row, 'inputBytes') ||
usage.outputBytes !== integer(row, 'outputBytes') ||
usage.inputTokens !== integer(row, 'inputTokens') ||
usage.outputTokens !== integer(row, 'outputTokens') ||
usage.totalTokens !== integer(row, 'totalTokens') ||
usage.costMicros !== nullableInteger(row, 'costMicros') ||
usage.ledgerDigest !== text(row, 'ledgerDigest')
) {
throw unavailable();
}
return usage;
}
export function parseQuotaReservation(
row: Row,
): Readonly<ModelInvocationQuotaReservation> {
try {
return normalizeModelInvocationQuotaReservation(
jsonObject(
row,
'recordJson',
) as unknown as ModelInvocationQuotaReservation,
);
} catch (error) {
throw unavailable(error);
}
}
export function parseQuotaSettlement(
row: Row,
reservation: Readonly<ModelInvocationQuotaReservation>,
completion: Readonly<ModelInvocationCompletionRecord>,
): Readonly<ModelInvocationQuotaSettlement> {
try {
return normalizeModelInvocationQuotaSettlement(
jsonObject(
row,
'recordJson',
) as unknown as ModelInvocationQuotaSettlement,
reservation,
completion,
);
} catch (error) {
throw unavailable(error);
}
}
export function parsePriceQuote(row: Row): Readonly<ModelInvocationPriceQuote> {
try {
return normalizeModelInvocationPriceQuote(
jsonObject(row, 'recordJson') as unknown as ModelInvocationPriceQuote,
);
} catch (error) {
throw unavailable(error);
}
}
export function parsePriceSettlement(
row: Row,
quote: Readonly<ModelInvocationPriceQuote>,
completion: Readonly<ModelInvocationCompletionRecord>,
): Readonly<ModelInvocationPriceSettlement> {
try {
return normalizeModelInvocationPriceSettlement(
jsonObject(
row,
'recordJson',
) as unknown as ModelInvocationPriceSettlement,
quote,
completion,
);
} catch (error) {
throw unavailable(error);
}
}
export function parseResolution(
row: Row,
): Readonly<ModelInvocationResolutionRecord> {
let resolution: Readonly<ModelInvocationResolutionRecord>;
try {
resolution = normalizeModelInvocationResolutionRecord(
jsonObject(
row,
'recordJson',
) as unknown as ModelInvocationResolutionRecord,
);
} catch (error) {
throw unavailable(error);
}
if (
resolution.resolutionId !== text(row, 'resolutionId') ||
resolution.invocationId !== text(row, 'invocationId') ||
resolution.projectId !== text(row, 'projectId') ||
resolution.runId !== text(row, 'runId') ||
resolution.stepRunId !== text(row, 'stepRunId') ||
resolution.traceId !== text(row, 'traceId') ||
resolution.completionDigest !== text(row, 'completionDigest') ||
resolution.decision !== text(row, 'decision') ||
resolution.resolvedByUserId !== text(row, 'resolvedByUserId') ||
resolution.resolvedAtMs !== integer(row, 'resolvedAtMs') ||
resolution.stepRunMutationId !== text(row, 'mutationId') ||
resolution.stepRunMutationDigest !== text(row, 'mutationDigest') ||
resolution.resolvedStepRunDigest !== text(row, 'stepRunDigest') ||
resolution.resolvedStepRunVersion !== integer(row, 'stepRunVersion') ||
resolution.runEventId !== text(row, 'runEventId') ||
resolution.resolutionDigest !== text(row, 'resolutionDigest')
) {
throw unavailable();
}
return resolution;
}
export function parseAuthority(
row: Row,
): Readonly<ModelInvocationAuthoritySnapshot> {
let stepRun: Readonly<StepRunRecord>;
try {
stepRun = normalizeStepRunRecord(
jsonObject(row, 'stepRunJson') as unknown as StepRunRecord,
);
} catch (error) {
throw unavailable(error);
}
if (
stepRun.id !== text(row, 'stepRunId') ||
stepRun.runId !== text(row, 'runId') ||
stepRun.kind !== 'model' ||
stepRun.status !== text(row, 'stepStatus') ||
stepRun.version !== integer(row, 'stepVersion') ||
stepRun.stepRunDigest !== text(row, 'stepDigest')
) {
throw unavailable();
}
return Object.freeze({
projectId: text(row, 'projectId'),
runId: text(row, 'runId'),
runVersion: integer(row, 'runVersion'),
runEventSequence: integer(row, 'runEventSequence'),
stepRun,
});
}
export const START_SELECT = `
start.invocation_id AS "invocationId",
start.project_id AS "projectId",
start.run_id AS "runId",
start.step_run_id AS "stepRunId",
start.trace_id AS "traceId",
start.provider,
start.model,
start.policy_revision AS "policyRevision",
start.request_digest AS "requestDigest",
start.input_bytes AS "inputBytes",
start.max_output_tokens AS "maxOutputTokens",
start.deadline_at_ms AS "deadlineAtMs",
start.admitted_at_ms AS "admittedAtMs",
start.mutation_id AS "mutationId",
start.mutation_digest AS "mutationDigest",
start.run_event_id AS "runEventId",
start.start_digest AS "startDigest",
start.record_json AS "recordJson",
mutation.step_run_digest AS "stepRunDigest",
mutation.step_run_json->>'version' AS "stepRunVersion"
`;
export const COMPLETION_SELECT = `
completion.invocation_id AS "invocationId",
completion.project_id AS "projectId",
completion.run_id AS "runId",
completion.step_run_id AS "stepRunId",
completion.trace_id AS "traceId",
completion.start_digest AS "startDigest",
completion.outcome,
completion.output_bytes AS "outputBytes",
completion.error_code AS "errorCode",
completion.completed_at_ms AS "completedAtMs",
completion.mutation_id AS "mutationId",
completion.mutation_digest AS "mutationDigest",
completion.run_event_id AS "runEventId",
completion.completion_digest AS "completionDigest",
completion.record_json AS "recordJson",
mutation.step_run_digest AS "stepRunDigest",
mutation.step_run_json->>'version' AS "stepRunVersion"
`;
export const USAGE_SELECT = `
usage.invocation_id AS "invocationId",
usage.project_id AS "projectId",
usage.run_id AS "runId",
usage.step_run_id AS "stepRunId",
usage.trace_id AS "traceId",
usage.provider,
usage.model,
usage.policy_revision AS "policyRevision",
usage.completion_digest AS "completionDigest",
usage.outcome,
usage.settled_at_ms AS "settledAtMs",
usage.input_bytes AS "inputBytes",
usage.output_bytes AS "outputBytes",
usage.input_tokens AS "inputTokens",
usage.output_tokens AS "outputTokens",
usage.total_tokens AS "totalTokens",
usage.cost_micros AS "costMicros",
usage.ledger_digest AS "ledgerDigest",
usage.record_json AS "recordJson"
`;
export const RESOLUTION_SELECT = `
resolution.resolution_id AS "resolutionId",
resolution.invocation_id AS "invocationId",
resolution.project_id AS "projectId",
resolution.run_id AS "runId",
resolution.step_run_id AS "stepRunId",
resolution.trace_id AS "traceId",
resolution.completion_digest AS "completionDigest",
resolution.decision,
resolution.resolved_by_user_id AS "resolvedByUserId",
resolution.resolved_at_ms AS "resolvedAtMs",
resolution.mutation_id AS "mutationId",
resolution.mutation_digest AS "mutationDigest",
resolution.run_event_id AS "runEventId",
resolution.resolution_digest AS "resolutionDigest",
resolution.record_json AS "recordJson",
mutation.step_run_digest AS "stepRunDigest",
mutation.step_run_json->>'version' AS "stepRunVersion"
`;
@@ -0,0 +1,459 @@
import type { PostgresPool } from '@qinglong/runtime-core';
import { createModelInvocationPriceSettlement } from '../../pricing/pricing';
import { type PluginPackagePromptOutputArtifact } from '../../prompt-output/pluginPackagePromptOutputArtifact';
import {
assertPluginPackagePromptOutputCompletionBinding,
type CommitPluginPackagePromptOutputResult,
} from '../../prompt-output/pluginPackagePromptOutputCompletion';
import {
putPostgresPluginPackagePromptOutputArtifactInTransaction,
readPostgresPluginPackagePromptOutputArtifactInTransaction,
} from '../../prompt-output/storage/postgresPluginPackagePromptOutputArtifactRepository';
import { createModelInvocationUsageLedgerRecord } from '../../usage/usageLedger';
import { createModelInvocationQuotaSettlement } from '../../usage/usageQuota';
import {
ModelInvocationConflictError,
normalizeModelInvocationCompletionCommand,
type CommitModelInvocationResult,
type ModelInvocationCompletionCommand,
type ModelInvocationCompletionRecord,
} from '../modelInvocation';
import {
parseCompletion,
parsePriceQuote,
parsePriceSettlement,
parseQuotaReservation,
parseQuotaSettlement,
parseStart,
parseUsage,
} from './codec';
import {
applyMutation,
assertCurrent,
insertCompletion,
insertPriceSettlement,
insertQuotaSettlement,
insertUsage,
} from './mutations';
import {
completionRows,
priceQuoteRows,
priceSettlementRows,
quotaReservationRows,
quotaSettlementRows,
startRows,
usageRows,
} from './queries';
import { runPostgresModelInvocationTransaction } from './transaction';
export async function completeOperation(
pool: PostgresPool,
commandValue: ModelInvocationCompletionCommand,
): Promise<
Readonly<CommitModelInvocationResult<ModelInvocationCompletionRecord>>
> {
const command = normalizeModelInvocationCompletionCommand(commandValue);
const completion = command.completion;
const expectedUsage = createModelInvocationUsageLedgerRecord(
command.start,
completion,
);
return runPostgresModelInvocationTransaction(pool, async (client) => {
const existing = await completionRows(
client,
`completion.invocation_id = $1 OR
completion.mutation_id = $2 OR completion.run_event_id = $3`,
[
completion.invocationId,
completion.stepRunMutationId,
completion.runEventId,
],
);
if (existing.length > 1) throw new ModelInvocationConflictError();
if (existing[0]) {
const stored = parseCompletion(existing[0]);
if (JSON.stringify(stored) !== JSON.stringify(completion)) {
throw new ModelInvocationConflictError();
}
const usage = await usageRows(client, 'usage.invocation_id = $1', [
completion.invocationId,
]);
if (
usage.length !== (expectedUsage ? 1 : 0) ||
(expectedUsage &&
JSON.stringify(parseUsage(usage[0]!)) !==
JSON.stringify(expectedUsage))
) {
throw new ModelInvocationConflictError();
}
return Object.freeze({ status: 'existing' as const, record: stored });
}
const starts = await startRows(client, 'start.invocation_id = $1', [
completion.invocationId,
]);
if (
starts.length !== 1 ||
JSON.stringify(parseStart(starts[0]!)) !== JSON.stringify(command.start)
) {
throw new ModelInvocationConflictError();
}
await assertCurrent(client, command.stepRunMutation, completion.projectId);
await applyMutation(client, command.stepRunMutation);
await insertCompletion(client, completion);
if (expectedUsage) await insertUsage(client, expectedUsage);
return Object.freeze({
status: 'created' as const,
record: completion,
});
});
}
export async function completeWithQuotaOperation(
pool: PostgresPool,
commandValue: ModelInvocationCompletionCommand,
): Promise<
Readonly<CommitModelInvocationResult<ModelInvocationCompletionRecord>>
> {
const command = normalizeModelInvocationCompletionCommand(commandValue);
const completion = command.completion;
const expectedUsage = createModelInvocationUsageLedgerRecord(
command.start,
completion,
);
return runPostgresModelInvocationTransaction(pool, async (client) => {
const reservationRows = await quotaReservationRows(
client,
'reservation.invocation_id = $1',
[completion.invocationId],
);
if (reservationRows.length !== 1) {
throw new ModelInvocationConflictError();
}
const reservation = parseQuotaReservation(reservationRows[0]!);
const expectedSettlement = createModelInvocationQuotaSettlement(
reservation,
completion,
);
const existing = await completionRows(
client,
`completion.invocation_id = $1 OR
completion.mutation_id = $2 OR completion.run_event_id = $3`,
[
completion.invocationId,
completion.stepRunMutationId,
completion.runEventId,
],
);
if (existing.length > 1) throw new ModelInvocationConflictError();
if (existing[0]) {
const stored = parseCompletion(existing[0]);
const usage = await usageRows(client, 'usage.invocation_id = $1', [
completion.invocationId,
]);
const settlements = await quotaSettlementRows(
client,
'settlement.invocation_id = $1',
[completion.invocationId],
);
if (
JSON.stringify(stored) !== JSON.stringify(completion) ||
usage.length !== (expectedUsage ? 1 : 0) ||
(expectedUsage &&
JSON.stringify(parseUsage(usage[0]!)) !==
JSON.stringify(expectedUsage)) ||
settlements.length !== 1 ||
JSON.stringify(
parseQuotaSettlement(settlements[0]!, reservation, completion),
) !== JSON.stringify(expectedSettlement)
) {
throw new ModelInvocationConflictError();
}
return Object.freeze({ status: 'existing' as const, record: stored });
}
const starts = await startRows(client, 'start.invocation_id = $1', [
completion.invocationId,
]);
if (
starts.length !== 1 ||
JSON.stringify(parseStart(starts[0]!)) !== JSON.stringify(command.start)
) {
throw new ModelInvocationConflictError();
}
await assertCurrent(client, command.stepRunMutation, completion.projectId);
await applyMutation(client, command.stepRunMutation);
await insertCompletion(client, completion);
if (expectedUsage) await insertUsage(client, expectedUsage);
await insertQuotaSettlement(client, expectedSettlement);
return Object.freeze({
status: 'created' as const,
record: completion,
});
});
}
export async function completeWithPricingOperation(
pool: PostgresPool,
commandValue: ModelInvocationCompletionCommand,
): Promise<
Readonly<CommitModelInvocationResult<ModelInvocationCompletionRecord>>
> {
const command = normalizeModelInvocationCompletionCommand(commandValue);
const completion = command.completion;
const expectedUsage = createModelInvocationUsageLedgerRecord(
command.start,
completion,
);
return runPostgresModelInvocationTransaction(pool, async (client) => {
const quoteRows = await priceQuoteRows(client, 'quote.invocation_id = $1', [
completion.invocationId,
]);
if (quoteRows.length !== 1) {
throw new ModelInvocationConflictError();
}
const quote = parsePriceQuote(quoteRows[0]!);
if (
quote.invocationId !== command.start.invocationId ||
quote.projectId !== command.start.projectId ||
quote.modelPolicyRevision !== command.start.policyRevision ||
quote.provider !== command.start.provider ||
quote.model !== command.start.model
) {
throw new ModelInvocationConflictError();
}
const expectedPriceSettlement = createModelInvocationPriceSettlement(
quote,
completion,
);
const reservationRows = await quotaReservationRows(
client,
'reservation.invocation_id = $1',
[completion.invocationId],
);
if (reservationRows.length > 1) {
throw new ModelInvocationConflictError();
}
const reservation = reservationRows[0]
? parseQuotaReservation(reservationRows[0])
: null;
const expectedQuotaSettlement = reservation
? createModelInvocationQuotaSettlement(reservation, completion)
: null;
const existing = await completionRows(
client,
`completion.invocation_id = $1 OR
completion.mutation_id = $2 OR completion.run_event_id = $3`,
[
completion.invocationId,
completion.stepRunMutationId,
completion.runEventId,
],
);
if (existing.length > 1) throw new ModelInvocationConflictError();
if (existing[0]) {
const [usage, priceSettlements, quotaSettlements] = await Promise.all([
usageRows(client, 'usage.invocation_id = $1', [
completion.invocationId,
]),
priceSettlementRows(client, 'settlement.invocation_id = $1', [
completion.invocationId,
]),
quotaSettlementRows(client, 'settlement.invocation_id = $1', [
completion.invocationId,
]),
]);
const stored = parseCompletion(existing[0]);
if (
JSON.stringify(stored) !== JSON.stringify(completion) ||
usage.length !== (expectedUsage ? 1 : 0) ||
(expectedUsage &&
JSON.stringify(parseUsage(usage[0]!)) !==
JSON.stringify(expectedUsage)) ||
priceSettlements.length !== (expectedPriceSettlement ? 1 : 0) ||
(expectedPriceSettlement &&
JSON.stringify(
parsePriceSettlement(priceSettlements[0]!, quote, completion),
) !== JSON.stringify(expectedPriceSettlement)) ||
quotaSettlements.length !== (expectedQuotaSettlement ? 1 : 0) ||
(expectedQuotaSettlement &&
JSON.stringify(
parseQuotaSettlement(
quotaSettlements[0]!,
reservation!,
completion,
),
) !== JSON.stringify(expectedQuotaSettlement))
) {
throw new ModelInvocationConflictError();
}
return Object.freeze({ status: 'existing' as const, record: stored });
}
const starts = await startRows(client, 'start.invocation_id = $1', [
completion.invocationId,
]);
if (
starts.length !== 1 ||
JSON.stringify(parseStart(starts[0]!)) !== JSON.stringify(command.start)
) {
throw new ModelInvocationConflictError();
}
await assertCurrent(client, command.stepRunMutation, completion.projectId);
await applyMutation(client, command.stepRunMutation);
await insertCompletion(client, completion);
if (expectedUsage) await insertUsage(client, expectedUsage);
if (expectedPriceSettlement) {
await insertPriceSettlement(client, expectedPriceSettlement);
}
if (expectedQuotaSettlement) {
await insertQuotaSettlement(client, expectedQuotaSettlement);
}
return Object.freeze({
status: 'created' as const,
record: completion,
});
});
}
export async function completeWithPromptOutputArtifactOperation(
pool: PostgresPool,
commandValue: ModelInvocationCompletionCommand,
artifactValue: PluginPackagePromptOutputArtifact,
): Promise<Readonly<CommitPluginPackagePromptOutputResult>> {
const command = normalizeModelInvocationCompletionCommand(commandValue);
const completion = command.completion;
const binding = assertPluginPackagePromptOutputCompletionBinding(
command,
artifactValue,
);
const expectedUsage = createModelInvocationUsageLedgerRecord(
command.start,
completion,
);
return runPostgresModelInvocationTransaction(pool, async (client) => {
const quoteRows = await priceQuoteRows(client, 'quote.invocation_id = $1', [
completion.invocationId,
]);
if (quoteRows.length > 1) throw new ModelInvocationConflictError();
const quote = quoteRows[0] ? parsePriceQuote(quoteRows[0]) : null;
if (
quote &&
(quote.invocationId !== command.start.invocationId ||
quote.projectId !== command.start.projectId ||
quote.modelPolicyRevision !== command.start.policyRevision ||
quote.provider !== command.start.provider ||
quote.model !== command.start.model)
) {
throw new ModelInvocationConflictError();
}
const expectedPriceSettlement = quote
? createModelInvocationPriceSettlement(quote, completion)
: null;
const reservationRows = await quotaReservationRows(
client,
'reservation.invocation_id = $1',
[completion.invocationId],
);
if (reservationRows.length > 1) {
throw new ModelInvocationConflictError();
}
const reservation = reservationRows[0]
? parseQuotaReservation(reservationRows[0])
: null;
const expectedQuotaSettlement = reservation
? createModelInvocationQuotaSettlement(reservation, completion)
: null;
const existing = await completionRows(
client,
`completion.invocation_id = $1 OR
completion.mutation_id = $2 OR completion.run_event_id = $3`,
[
completion.invocationId,
completion.stepRunMutationId,
completion.runEventId,
],
);
if (existing.length > 1) throw new ModelInvocationConflictError();
if (existing[0]) {
const [storedArtifact, usage, priceSettlements, quotaSettlements] =
await Promise.all([
readPostgresPluginPackagePromptOutputArtifactInTransaction(
client,
binding.artifact.artifactId,
),
usageRows(client, 'usage.invocation_id = $1', [
completion.invocationId,
]),
priceSettlementRows(client, 'settlement.invocation_id = $1', [
completion.invocationId,
]),
quotaSettlementRows(client, 'settlement.invocation_id = $1', [
completion.invocationId,
]),
]);
const stored = parseCompletion(existing[0]);
if (
JSON.stringify(stored) !== JSON.stringify(completion) ||
!storedArtifact ||
JSON.stringify(storedArtifact) !== JSON.stringify(binding.artifact) ||
usage.length !== (expectedUsage ? 1 : 0) ||
(expectedUsage &&
JSON.stringify(parseUsage(usage[0]!)) !==
JSON.stringify(expectedUsage)) ||
priceSettlements.length !== (expectedPriceSettlement ? 1 : 0) ||
(expectedPriceSettlement &&
JSON.stringify(
parsePriceSettlement(priceSettlements[0]!, quote!, completion),
) !== JSON.stringify(expectedPriceSettlement)) ||
quotaSettlements.length !== (expectedQuotaSettlement ? 1 : 0) ||
(expectedQuotaSettlement &&
JSON.stringify(
parseQuotaSettlement(
quotaSettlements[0]!,
reservation!,
completion,
),
) !== JSON.stringify(expectedQuotaSettlement))
) {
throw new ModelInvocationConflictError();
}
return Object.freeze({
status: 'existing' as const,
record: stored,
artifact: storedArtifact,
reference: binding.reference,
});
}
const starts = await startRows(client, 'start.invocation_id = $1', [
completion.invocationId,
]);
if (
starts.length !== 1 ||
JSON.stringify(parseStart(starts[0]!)) !== JSON.stringify(command.start)
) {
throw new ModelInvocationConflictError();
}
await assertCurrent(client, command.stepRunMutation, completion.projectId);
const artifact = (
await putPostgresPluginPackagePromptOutputArtifactInTransaction(
client,
binding.artifact,
)
).artifact;
await applyMutation(client, command.stepRunMutation);
await insertCompletion(client, completion);
if (expectedUsage) await insertUsage(client, expectedUsage);
if (expectedPriceSettlement) {
await insertPriceSettlement(client, expectedPriceSettlement);
}
if (expectedQuotaSettlement) {
await insertQuotaSettlement(client, expectedQuotaSettlement);
}
return Object.freeze({
status: 'created' as const,
record: completion,
artifact,
reference: binding.reference,
});
});
}
@@ -0,0 +1,490 @@
import type { PostgresClient } from '@qinglong/runtime-core';
import { type StepRunMutation } from '@qinglong/runtime-core/step-run';
import { POSTGRES_MODEL_INVOCATION_SCHEMA } from '../../migration/modelInvocationMigration';
import {
type ModelInvocationPriceQuote,
type ModelInvocationPriceSettlement,
} from '../../pricing/pricing';
import { type ModelInvocationUsageLedgerRecord } from '../../usage/usageLedger';
import {
type ModelInvocationQuotaReservation,
type ModelInvocationQuotaSettlement,
type ModelInvocationQuotaWindowUsage,
} from '../../usage/usageQuota';
import {
ModelInvocationConflictError,
type ModelInvocationCompletionRecord,
type ModelInvocationStartRecord,
} from '../modelInvocation';
import { type ModelInvocationResolutionRecord } from '../modelInvocationResolution';
import type { Queryable, Row } from './authority';
import { TERMINAL_RUN_STATUSES, integer, text, unavailable } from './authority';
export async function updateStepRun(
client: PostgresClient,
mutation: Readonly<StepRunMutation>,
): Promise<void> {
const step = mutation.stepRun;
const result = await client.query(
`UPDATE "ql3"."step_runs"
SET status = $1, version = $2, attempt_count = $3, output_ref = $4,
approval_request_id = $5, ready_at_ms = $6, started_at_ms = $7,
finished_at_ms = $8, result_code = $9, error_summary = $10,
updated_at_ms = $11, last_mutation_id = $12,
step_run_digest = $13, step_run_json = $14::jsonb
WHERE id = $15 AND run_id = $16 AND version = $17
AND step_run_digest = $18 AND status = $19`,
[
step.status,
step.version,
step.attemptCount,
step.outputRef,
step.approvalRequestId,
step.readyAtMs,
step.startedAtMs,
step.finishedAtMs,
step.resultCode,
step.errorSummary,
step.updatedAtMs,
step.lastMutationId,
step.stepRunDigest,
JSON.stringify(step),
step.id,
step.runId,
mutation.expectedStepRunVersion,
mutation.expectedStepRunDigest,
mutation.previousStatus,
],
);
if (result.rowCount !== 1) throw new ModelInvocationConflictError();
}
export async function updateRun(
client: PostgresClient,
mutation: Readonly<StepRunMutation>,
): Promise<void> {
const result = await client.query(
`UPDATE "ql3"."runs"
SET version = version + 1, event_sequence = event_sequence + 1
WHERE id = $1 AND version = $2 AND event_sequence = $3`,
[
mutation.runId,
mutation.expectedRunVersion,
mutation.expectedRunEventSequence,
],
);
if (result.rowCount !== 1) throw new ModelInvocationConflictError();
}
export async function insertRunEvent(
client: PostgresClient,
mutation: Readonly<StepRunMutation>,
): Promise<void> {
const event = mutation.event;
await client.query(
`INSERT INTO "ql3"."run_events" (
id, run_id, sequence, type, dedupe_key, actor_type, actor_id,
attempt_id, step_run_id, payload, created_at_ms
) VALUES ($1, $2, $3, $4, $5, $6, $7, NULL, $8, $9::jsonb, $10)`,
[
event.id,
event.runId,
event.sequence,
event.type,
event.dedupeKey,
event.actorType,
event.actorId ?? null,
mutation.stepRun.id,
JSON.stringify(event.payload),
event.createdAtMs,
],
);
}
export async function insertMutation(
client: PostgresClient,
mutation: Readonly<StepRunMutation>,
): Promise<void> {
await client.query(
`INSERT INTO "ql3"."step_run_mutations" (
mutation_id, mutation_digest, run_id, step_run_id,
step_run_digest, event_id, event_sequence, run_version,
step_run_json, committed_at_ms
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9::jsonb,
floor(extract(epoch FROM transaction_timestamp()) * 1000)::bigint
)`,
[
mutation.mutationId,
mutation.mutationDigest,
mutation.runId,
mutation.stepRun.id,
mutation.stepRun.stepRunDigest,
mutation.event.id,
mutation.event.sequence,
mutation.expectedRunVersion + 1,
JSON.stringify(mutation.stepRun),
],
);
}
export async function applyMutation(
client: PostgresClient,
mutation: Readonly<StepRunMutation>,
): Promise<void> {
await updateStepRun(client, mutation);
await updateRun(client, mutation);
await insertRunEvent(client, mutation);
await insertMutation(client, mutation);
}
export async function insertStart(
client: PostgresClient,
start: Readonly<ModelInvocationStartRecord>,
): Promise<void> {
await client.query(
`INSERT INTO "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_starts" (
invocation_id, project_id, run_id, step_run_id, trace_id,
provider, model, policy_revision, request_digest, input_bytes,
max_output_tokens, deadline_at_ms, admitted_at_ms, mutation_id,
mutation_digest, run_event_id, start_digest, record_json
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13,
$14, $15, $16, $17, $18::jsonb
)`,
[
start.invocationId,
start.projectId,
start.runId,
start.stepRunId,
start.traceId,
start.provider,
start.model,
start.policyRevision,
start.requestDigest,
start.inputBytes,
start.maxOutputTokens,
start.deadlineAtMs,
start.admittedAtMs,
start.stepRunMutationId,
start.stepRunMutationDigest,
start.runEventId,
start.startDigest,
JSON.stringify(start),
],
);
}
export async function insertCompletion(
client: PostgresClient,
completion: Readonly<ModelInvocationCompletionRecord>,
): Promise<void> {
await client.query(
`INSERT INTO "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_completions" (
invocation_id, project_id, run_id, step_run_id, trace_id,
start_digest, outcome, output_bytes, error_code, completed_at_ms,
mutation_id, mutation_digest, run_event_id, completion_digest,
record_json
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13,
$14, $15::jsonb
)`,
[
completion.invocationId,
completion.projectId,
completion.runId,
completion.stepRunId,
completion.traceId,
completion.startDigest,
completion.outcome,
completion.outputBytes,
completion.errorCode,
completion.completedAtMs,
completion.stepRunMutationId,
completion.stepRunMutationDigest,
completion.runEventId,
completion.completionDigest,
JSON.stringify(completion),
],
);
}
export async function insertUsage(
client: PostgresClient,
usage: Readonly<ModelInvocationUsageLedgerRecord>,
): Promise<void> {
await client.query(
`INSERT INTO "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_usage_ledger" (
invocation_id, project_id, run_id, step_run_id, trace_id,
provider, model, policy_revision, completion_digest, outcome,
settled_at_ms, input_bytes, output_bytes, input_tokens,
output_tokens, total_tokens, cost_micros, ledger_digest, record_json
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13,
$14, $15, $16, $17, $18, $19::jsonb
)`,
[
usage.invocationId,
usage.projectId,
usage.runId,
usage.stepRunId,
usage.traceId,
usage.provider,
usage.model,
usage.policyRevision,
usage.completionDigest,
usage.outcome,
usage.settledAtMs,
usage.inputBytes,
usage.outputBytes,
usage.inputTokens,
usage.outputTokens,
usage.totalTokens,
usage.costMicros,
usage.ledgerDigest,
JSON.stringify(usage),
],
);
}
export async function insertQuotaReservation(
client: PostgresClient,
reservation: Readonly<ModelInvocationQuotaReservation>,
): Promise<void> {
await client.query(
`INSERT INTO "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_quota_reservations" (
invocation_id, project_id, model_policy_revision,
quota_policy_revision, window_ms, window_start_ms, window_end_ms,
max_invocations, max_tokens, max_cost_micros, reserved_tokens,
reserved_cost_micros, reserved_at_ms, admission_digest,
reservation_digest, record_json
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13,
$14, $15, $16::jsonb
)`,
[
reservation.invocationId,
reservation.projectId,
reservation.modelPolicyRevision,
reservation.quotaPolicyRevision,
reservation.windowMs,
reservation.windowStartMs,
reservation.windowEndMs,
reservation.maxInvocations,
reservation.maxTokens,
reservation.maxCostMicros,
reservation.reservedTokens,
reservation.reservedCostMicros,
reservation.reservedAtMs,
reservation.admissionDigest,
reservation.reservationDigest,
JSON.stringify(reservation),
],
);
}
export async function insertQuotaSettlement(
client: PostgresClient,
settlement: Readonly<ModelInvocationQuotaSettlement>,
): Promise<void> {
await client.query(
`INSERT INTO "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_quota_settlements" (
invocation_id, project_id, reservation_digest, completion_digest,
effective_tokens, effective_cost_micros,
retained_token_reservation, retained_cost_reservation,
settled_at_ms, settlement_digest, record_json
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11::jsonb
)`,
[
settlement.invocationId,
settlement.projectId,
settlement.reservationDigest,
settlement.completionDigest,
settlement.effectiveTokens,
settlement.effectiveCostMicros,
settlement.retainedTokenReservation,
settlement.retainedCostReservation,
settlement.settledAtMs,
settlement.settlementDigest,
JSON.stringify(settlement),
],
);
}
export async function insertPriceQuote(
client: PostgresClient,
quote: Readonly<ModelInvocationPriceQuote>,
): Promise<void> {
await client.query(
`INSERT INTO "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_price_quotes" (
invocation_id, project_id, model_policy_revision, provider, model,
price_revision, currency, input_micros_per_million_tokens,
output_micros_per_million_tokens, max_total_tokens, max_output_tokens,
reserved_cost_micros, catalog_digest, quote_digest, record_json
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14,
$15::jsonb
)`,
[
quote.invocationId,
quote.projectId,
quote.modelPolicyRevision,
quote.provider,
quote.model,
quote.priceRevision,
quote.currency,
quote.inputMicrosPerMillionTokens,
quote.outputMicrosPerMillionTokens,
quote.maxTotalTokens,
quote.maxOutputTokens,
quote.reservedCostMicros,
quote.catalogDigest,
quote.quoteDigest,
JSON.stringify(quote),
],
);
}
export async function insertPriceSettlement(
client: PostgresClient,
settlement: Readonly<ModelInvocationPriceSettlement>,
): Promise<void> {
await client.query(
`INSERT INTO "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_price_settlements" (
invocation_id, project_id, quote_digest, completion_digest, currency,
input_tokens, output_tokens, cost_micros, settled_at_ms,
settlement_digest, record_json
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11::jsonb
)`,
[
settlement.invocationId,
settlement.projectId,
settlement.quoteDigest,
settlement.completionDigest,
settlement.currency,
settlement.inputTokens,
settlement.outputTokens,
settlement.costMicros,
settlement.settledAtMs,
settlement.settlementDigest,
JSON.stringify(settlement),
],
);
}
export async function quotaWindowUsage(
queryable: Queryable,
projectId: string,
windowStartMs: number,
windowMs: number,
): Promise<Readonly<ModelInvocationQuotaWindowUsage>> {
const result = await queryable.query<Row>(
`SELECT
COUNT(*)::text AS "invocationCount",
COALESCE(SUM(COALESCE(
settlement.effective_tokens, reservation.reserved_tokens
)), 0)::text AS "effectiveTokens",
COALESCE(SUM(COALESCE(
settlement.effective_cost_micros,
reservation.reserved_cost_micros,
0
)), 0)::text AS "effectiveCostMicros",
COUNT(*) FILTER (
WHERE settlement.effective_cost_micros IS NULL
AND reservation.reserved_cost_micros IS NULL
)::text AS "unknownCostInvocations"
FROM "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_quota_reservations"
AS reservation
LEFT JOIN "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_quota_settlements"
AS settlement ON settlement.invocation_id = reservation.invocation_id
WHERE reservation.project_id = $1
AND reservation.window_start_ms = $2
AND reservation.window_ms = $3`,
[projectId, windowStartMs, windowMs],
);
const row = result.rows[0];
if (result.rows.length !== 1 || !row) throw unavailable();
return Object.freeze({
projectId,
windowStartMs,
windowEndMs: windowStartMs + windowMs,
invocationCount: integer(row, 'invocationCount'),
effectiveTokens: integer(row, 'effectiveTokens'),
effectiveCostMicros: integer(row, 'effectiveCostMicros'),
unknownCostInvocations: integer(row, 'unknownCostInvocations'),
});
}
export async function insertResolution(
client: PostgresClient,
resolution: Readonly<ModelInvocationResolutionRecord>,
): Promise<void> {
await client.query(
`INSERT INTO "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_resolutions" (
resolution_id, invocation_id, project_id, run_id, step_run_id,
trace_id, completion_digest, decision, resolved_by_user_id,
resolved_at_ms, mutation_id, mutation_digest, run_event_id,
resolution_digest, record_json
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13,
$14, $15::jsonb
)`,
[
resolution.resolutionId,
resolution.invocationId,
resolution.projectId,
resolution.runId,
resolution.stepRunId,
resolution.traceId,
resolution.completionDigest,
resolution.decision,
resolution.resolvedByUserId,
resolution.resolvedAtMs,
resolution.stepRunMutationId,
resolution.stepRunMutationDigest,
resolution.runEventId,
resolution.resolutionDigest,
JSON.stringify(resolution),
],
);
}
export async function assertCurrent(
client: PostgresClient,
mutation: Readonly<StepRunMutation>,
projectId: string,
): Promise<void> {
const result = await client.query<Row>(
`SELECT
step.kind AS "stepKind", step.status AS "stepStatus",
step.version AS "stepVersion", step.step_run_digest AS "stepDigest",
run.project_id AS "projectId", run.status AS "runStatus",
run.version AS "runVersion",
run.event_sequence AS "runEventSequence"
FROM "ql3"."step_runs" AS step
JOIN "ql3"."runs" AS run ON run.id = step.run_id
WHERE step.id = $1 AND step.run_id = $2
LIMIT 2
FOR UPDATE OF step, run`,
[mutation.stepRun.id, mutation.runId],
);
const row = result.rows[0];
if (
result.rows.length !== 1 ||
!row ||
text(row, 'stepKind') !== 'model' ||
text(row, 'stepStatus') !== mutation.previousStatus ||
integer(row, 'stepVersion') !== mutation.expectedStepRunVersion ||
text(row, 'stepDigest') !== mutation.expectedStepRunDigest ||
text(row, 'projectId') !== projectId ||
integer(row, 'runVersion') !== mutation.expectedRunVersion ||
integer(row, 'runEventSequence') !== mutation.expectedRunEventSequence ||
TERMINAL_RUN_STATUSES.has(text(row, 'runStatus'))
) {
throw new ModelInvocationConflictError();
}
}
@@ -0,0 +1,146 @@
import { POSTGRES_MODEL_INVOCATION_SCHEMA } from '../../migration/modelInvocationMigration';
import type { Queryable, Row } from './authority';
import {
COMPLETION_SELECT,
RESOLUTION_SELECT,
START_SELECT,
USAGE_SELECT,
} from './codec';
export async function startRows(
queryable: Queryable,
where: string,
values: readonly unknown[],
): Promise<readonly Row[]> {
const result = await queryable.query<Row>(
`SELECT ${START_SELECT}
FROM "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_starts" AS start
JOIN "ql3"."step_run_mutations" AS mutation
ON mutation.mutation_id = start.mutation_id
JOIN "ql3"."run_events" AS event ON event.id = start.run_event_id
WHERE ${where}
LIMIT 2`,
values,
);
return result.rows;
}
export async function completionRows(
queryable: Queryable,
where: string,
values: readonly unknown[],
): Promise<readonly Row[]> {
const result = await queryable.query<Row>(
`SELECT ${COMPLETION_SELECT}
FROM "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_completions" AS completion
JOIN "ql3"."step_run_mutations" AS mutation
ON mutation.mutation_id = completion.mutation_id
JOIN "ql3"."run_events" AS event ON event.id = completion.run_event_id
WHERE ${where}
LIMIT 2`,
values,
);
return result.rows;
}
export async function usageRows(
queryable: Queryable,
where: string,
values: readonly unknown[],
limit = 2,
): Promise<readonly Row[]> {
const result = await queryable.query<Row>(
`SELECT ${USAGE_SELECT}
FROM "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_usage_ledger" AS usage
WHERE ${where}
ORDER BY usage.settled_at_ms, usage.invocation_id
LIMIT $${values.length + 1}`,
[...values, limit],
);
return result.rows;
}
export async function quotaReservationRows(
queryable: Queryable,
where: string,
values: readonly unknown[],
limit = 2,
): Promise<readonly Row[]> {
const result = await queryable.query<Row>(
`SELECT reservation.record_json AS "recordJson"
FROM "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_quota_reservations"
AS reservation
WHERE ${where}
ORDER BY reservation.window_start_ms, reservation.invocation_id
LIMIT $${values.length + 1}`,
[...values, limit],
);
return result.rows;
}
export async function quotaSettlementRows(
queryable: Queryable,
where: string,
values: readonly unknown[],
): Promise<readonly Row[]> {
const result = await queryable.query<Row>(
`SELECT settlement.record_json AS "recordJson"
FROM "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_quota_settlements"
AS settlement
WHERE ${where}
LIMIT 2`,
values,
);
return result.rows;
}
export async function priceQuoteRows(
queryable: Queryable,
where: string,
values: readonly unknown[],
): Promise<readonly Row[]> {
const result = await queryable.query<Row>(
`SELECT quote.record_json AS "recordJson"
FROM "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_price_quotes"
AS quote
WHERE ${where}
LIMIT 2`,
values,
);
return result.rows;
}
export async function priceSettlementRows(
queryable: Queryable,
where: string,
values: readonly unknown[],
): Promise<readonly Row[]> {
const result = await queryable.query<Row>(
`SELECT settlement.record_json AS "recordJson"
FROM "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_price_settlements"
AS settlement
WHERE ${where}
LIMIT 2`,
values,
);
return result.rows;
}
export async function resolutionRows(
queryable: Queryable,
where: string,
values: readonly unknown[],
): Promise<readonly Row[]> {
const result = await queryable.query<Row>(
`SELECT ${RESOLUTION_SELECT}
FROM "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_resolutions" AS resolution
JOIN "ql3"."step_run_mutations" AS mutation
ON mutation.mutation_id = resolution.mutation_id
JOIN "ql3"."run_events" AS event ON event.id = resolution.run_event_id
WHERE ${where}
LIMIT 2`,
values,
);
return result.rows;
}
@@ -0,0 +1,366 @@
import type { PostgresPool } from '@qinglong/runtime-core';
import { POSTGRES_MODEL_INVOCATION_SCHEMA } from '../../migration/modelInvocationMigration';
import {
type ModelInvocationPriceQuote,
type ModelInvocationPriceSettlement,
} from '../../pricing/pricing';
import { type PluginPackagePromptOutputArtifact } from '../../prompt-output/pluginPackagePromptOutputArtifact';
import type { PluginPackagePromptOutputArtifactTombstone } from '../../prompt-output/pluginPackagePromptOutputRetention';
import { readPostgresPluginPackagePromptOutputArtifactInTransaction } from '../../prompt-output/storage/postgresPluginPackagePromptOutputArtifactRepository';
import { readPostgresPluginPackagePromptOutputArtifactTombstoneInTransaction } from '../../prompt-output/storage/postgresPluginPackagePromptOutputRetentionRepository';
import {
MAX_MODEL_INVOCATION_USAGE_SUMMARY_ROWS,
ModelInvocationUsageSummaryLimitExceededError,
normalizeModelInvocationUsageLedgerQuery,
normalizeModelInvocationUsageLedgerSummaryQuery,
type ModelInvocationUsageLedgerPage,
type ModelInvocationUsageLedgerQuery,
type ModelInvocationUsageLedgerRecord,
type ModelInvocationUsageLedgerSummary,
type ModelInvocationUsageLedgerSummaryQuery,
} from '../../usage/usageLedger';
import {
type ModelInvocationQuotaReservation,
type ModelInvocationQuotaSettlement,
type ModelInvocationQuotaWindowUsage,
} from '../../usage/usageQuota';
import {
type ModelInvocationCompletionRecord,
type ModelInvocationStartRecord,
} from '../modelInvocation';
import type { Row } from './authority';
import { identifier, integer, mapStorageError, unavailable } from './authority';
import {
parseCompletion,
parsePriceQuote,
parsePriceSettlement,
parseQuotaReservation,
parseQuotaSettlement,
parseStart,
parseUsage,
} from './codec';
import { quotaWindowUsage } from './mutations';
import {
completionRows,
priceQuoteRows,
priceSettlementRows,
quotaReservationRows,
quotaSettlementRows,
startRows,
usageRows,
} from './queries';
export async function findStartOperation(
pool: PostgresPool,
invocationIdValue: string,
): Promise<Readonly<ModelInvocationStartRecord> | null> {
const invocationId = identifier(invocationIdValue);
try {
const rows = await startRows(pool, 'start.invocation_id = $1', [
invocationId,
]);
if (rows.length > 1) throw unavailable();
return rows[0] ? parseStart(rows[0]) : null;
} catch (error) {
throw mapStorageError(error);
}
}
export async function findCompletionOperation(
pool: PostgresPool,
invocationIdValue: string,
): Promise<Readonly<ModelInvocationCompletionRecord> | null> {
const invocationId = identifier(invocationIdValue);
try {
const rows = await completionRows(pool, 'completion.invocation_id = $1', [
invocationId,
]);
if (rows.length > 1) throw unavailable();
return rows[0] ? parseCompletion(rows[0]) : null;
} catch (error) {
throw mapStorageError(error);
}
}
export async function findPromptOutputArtifactOperation(
pool: PostgresPool,
artifactIdValue: string,
): Promise<Readonly<PluginPackagePromptOutputArtifact> | null> {
const artifactId = identifier(artifactIdValue);
try {
return await readPostgresPluginPackagePromptOutputArtifactInTransaction(
pool,
artifactId,
);
} catch (error) {
throw mapStorageError(error);
}
}
export async function findPromptOutputArtifactTombstoneOperation(
pool: PostgresPool,
artifactIdValue: string,
): Promise<Readonly<PluginPackagePromptOutputArtifactTombstone> | null> {
const artifactId = identifier(artifactIdValue);
try {
return await readPostgresPluginPackagePromptOutputArtifactTombstoneInTransaction(
pool,
artifactId,
);
} catch (error) {
throw mapStorageError(error);
}
}
export async function findUsageOperation(
pool: PostgresPool,
invocationIdValue: string,
): Promise<Readonly<ModelInvocationUsageLedgerRecord> | null> {
const invocationId = identifier(invocationIdValue);
try {
const rows = await usageRows(pool, 'usage.invocation_id = $1', [
invocationId,
]);
if (rows.length > 1) throw unavailable();
return rows[0] ? parseUsage(rows[0]) : null;
} catch (error) {
throw mapStorageError(error);
}
}
export async function findPriceQuoteOperation(
pool: PostgresPool,
invocationIdValue: string,
): Promise<Readonly<ModelInvocationPriceQuote> | null> {
const invocationId = identifier(invocationIdValue);
try {
const rows = await priceQuoteRows(pool, 'quote.invocation_id = $1', [
invocationId,
]);
if (rows.length > 1) throw unavailable();
return rows[0] ? parsePriceQuote(rows[0]) : null;
} catch (error) {
throw mapStorageError(error);
}
}
export async function findPriceSettlementOperation(
pool: PostgresPool,
invocationIdValue: string,
): Promise<Readonly<ModelInvocationPriceSettlement> | null> {
const invocationId = identifier(invocationIdValue);
try {
const [quotes, completions, settlements] = await Promise.all([
priceQuoteRows(pool, 'quote.invocation_id = $1', [invocationId]),
completionRows(pool, 'completion.invocation_id = $1', [invocationId]),
priceSettlementRows(pool, 'settlement.invocation_id = $1', [
invocationId,
]),
]);
if (quotes.length > 1 || completions.length > 1 || settlements.length > 1) {
throw unavailable();
}
if (!settlements[0]) return null;
if (!quotes[0] || !completions[0]) throw unavailable();
return parsePriceSettlement(
settlements[0],
parsePriceQuote(quotes[0]),
parseCompletion(completions[0]),
);
} catch (error) {
throw mapStorageError(error);
}
}
export async function findQuotaReservationOperation(
pool: PostgresPool,
invocationIdValue: string,
): Promise<Readonly<ModelInvocationQuotaReservation> | null> {
const invocationId = identifier(invocationIdValue);
try {
const rows = await quotaReservationRows(
pool,
'reservation.invocation_id = $1',
[invocationId],
);
if (rows.length > 1) throw unavailable();
return rows[0] ? parseQuotaReservation(rows[0]) : null;
} catch (error) {
throw mapStorageError(error);
}
}
export async function findQuotaSettlementOperation(
pool: PostgresPool,
invocationIdValue: string,
): Promise<Readonly<ModelInvocationQuotaSettlement> | null> {
const invocationId = identifier(invocationIdValue);
try {
const [reservations, completions, settlements] = await Promise.all([
quotaReservationRows(pool, 'reservation.invocation_id = $1', [
invocationId,
]),
completionRows(pool, 'completion.invocation_id = $1', [invocationId]),
quotaSettlementRows(pool, 'settlement.invocation_id = $1', [
invocationId,
]),
]);
if (
reservations.length > 1 ||
completions.length > 1 ||
settlements.length > 1
) {
throw unavailable();
}
if (!settlements[0]) return null;
if (!reservations[0] || !completions[0]) throw unavailable();
return parseQuotaSettlement(
settlements[0],
parseQuotaReservation(reservations[0]),
parseCompletion(completions[0]),
);
} catch (error) {
throw mapStorageError(error);
}
}
export async function readQuotaWindowUsageOperation(
pool: PostgresPool,
projectIdValue: string,
atMsValue?: number,
): Promise<Readonly<ModelInvocationQuotaWindowUsage> | null> {
const projectId = identifier(projectIdValue);
if (
atMsValue !== undefined &&
(!Number.isSafeInteger(atMsValue) || atMsValue < 0)
) {
throw unavailable();
}
try {
let atMs = atMsValue;
if (atMs === undefined) {
const observation = await pool.query<Row>(
`SELECT floor(extract(epoch FROM statement_timestamp()) * 1000)::bigint
AS "atMs"`,
);
const row = observation.rows[0];
if (observation.rows.length !== 1 || !row) throw unavailable();
atMs = integer(row, 'atMs');
}
const result = await pool.query<Row>(
`SELECT reservation.record_json AS "recordJson"
FROM "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_quota_reservations"
AS reservation
WHERE reservation.project_id = $1
AND reservation.window_start_ms <= $2
AND reservation.window_end_ms > $3
ORDER BY reservation.reserved_at_ms DESC,
reservation.invocation_id DESC
LIMIT 1`,
[projectId, atMs, atMs],
);
const row = result.rows[0];
if (!row) return null;
const reservation = parseQuotaReservation(row);
return quotaWindowUsage(
pool,
projectId,
reservation.windowStartMs,
reservation.windowMs,
);
} catch (error) {
throw mapStorageError(error);
}
}
export async function listProjectUsageOperation(
pool: PostgresPool,
queryValue: ModelInvocationUsageLedgerQuery,
): Promise<Readonly<ModelInvocationUsageLedgerPage>> {
const query = normalizeModelInvocationUsageLedgerQuery(queryValue);
const cursor = query.after;
try {
const rows = await usageRows(
pool,
`usage.project_id = $1
AND usage.settled_at_ms >= $2 AND usage.settled_at_ms < $3
${
cursor
? `AND (
usage.settled_at_ms > $4 OR
(usage.settled_at_ms = $5 AND usage.invocation_id > $6)
)`
: ''
}`,
[
query.projectId,
query.fromMsInclusive,
query.toMsExclusive,
...(cursor
? [cursor.settledAtMs, cursor.settledAtMs, cursor.invocationId]
: []),
],
query.limit + 1,
);
return Object.freeze({
records: Object.freeze(
rows.slice(0, query.limit).map((row) => parseUsage(row)),
),
hasMore: rows.length > query.limit,
});
} catch (error) {
throw mapStorageError(error);
}
}
export async function summarizeProjectUsageOperation(
pool: PostgresPool,
queryValue: ModelInvocationUsageLedgerSummaryQuery,
): Promise<Readonly<ModelInvocationUsageLedgerSummary>> {
const query = normalizeModelInvocationUsageLedgerSummaryQuery(queryValue);
try {
const result = await pool.query<Row>(
`SELECT
COUNT(*)::text AS "invocationCount",
COALESCE(SUM(input_tokens), 0)::text AS "inputTokens",
COALESCE(SUM(output_tokens), 0)::text AS "outputTokens",
COALESCE(SUM(total_tokens), 0)::text AS "totalTokens",
COALESCE(SUM(cost_micros), 0)::text AS "knownCostMicros",
COUNT(*) FILTER (WHERE cost_micros IS NULL)::text
AS "unknownCostInvocations"
FROM (
SELECT input_tokens, output_tokens, total_tokens, cost_micros
FROM "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_usage_ledger"
WHERE project_id = $1
AND settled_at_ms >= $2 AND settled_at_ms < $3
ORDER BY settled_at_ms, invocation_id
LIMIT $4
) AS bounded_usage`,
[
query.projectId,
query.fromMsInclusive,
query.toMsExclusive,
MAX_MODEL_INVOCATION_USAGE_SUMMARY_ROWS + 1,
],
);
const row = result.rows[0];
if (result.rows.length !== 1 || !row) throw unavailable();
if (
integer(row, 'invocationCount') > MAX_MODEL_INVOCATION_USAGE_SUMMARY_ROWS
) {
throw new ModelInvocationUsageSummaryLimitExceededError();
}
return Object.freeze({
invocationCount: integer(row, 'invocationCount'),
inputTokens: integer(row, 'inputTokens'),
outputTokens: integer(row, 'outputTokens'),
totalTokens: integer(row, 'totalTokens'),
knownCostMicros: integer(row, 'knownCostMicros'),
unknownCostInvocations: integer(row, 'unknownCostInvocations'),
});
} catch (error) {
throw mapStorageError(error);
}
}
@@ -0,0 +1,183 @@
import type { PostgresPool } from '@qinglong/runtime-core';
import { POSTGRES_MODEL_INVOCATION_SCHEMA } from '../../migration/modelInvocationMigration';
import {
ModelInvocationConflictError,
type CommitModelInvocationResult,
type ModelInvocationAuthoritySnapshot,
type ModelInvocationRecoveryPage,
} from '../modelInvocation';
import {
normalizeModelInvocationResolutionCommand,
type ModelInvocationResolutionCommand,
type ModelInvocationResolutionRecord,
} from '../modelInvocationResolution';
import type { Row } from './authority';
import {
TERMINAL_RUN_STATUSES,
identifier,
integer,
mapStorageError,
recoveryLimit,
text,
unavailable,
} from './authority';
import {
START_SELECT,
parseAuthority,
parseCompletion,
parseResolution,
parseStart,
} from './codec';
import { applyMutation, assertCurrent, insertResolution } from './mutations';
import { completionRows, resolutionRows } from './queries';
import { runPostgresModelInvocationTransaction } from './transaction';
export async function findResolutionOperation(
pool: PostgresPool,
invocationIdValue: string,
): Promise<Readonly<ModelInvocationResolutionRecord> | null> {
const invocationId = identifier(invocationIdValue);
try {
const rows = await resolutionRows(pool, 'resolution.invocation_id = $1', [
invocationId,
]);
if (rows.length > 1) throw unavailable();
return rows[0] ? parseResolution(rows[0]) : null;
} catch (error) {
throw mapStorageError(error);
}
}
export async function readAuthorityOperation(
pool: PostgresPool,
identity: Readonly<{
projectId: string;
runId: string;
stepRunId: string;
}>,
): Promise<Readonly<ModelInvocationAuthoritySnapshot> | null> {
const projectId = identifier(identity?.projectId);
const runId = identifier(identity?.runId);
const stepRunId = identifier(identity?.stepRunId);
try {
const result = await pool.query<Row>(
`SELECT
run.project_id AS "projectId", run.id AS "runId",
run.status AS "runStatus", run.version AS "runVersion",
run.event_sequence AS "runEventSequence",
step.id AS "stepRunId", step.status AS "stepStatus",
step.version AS "stepVersion",
step.step_run_digest AS "stepDigest",
step.step_run_json AS "stepRunJson"
FROM "ql3"."runs" AS run
JOIN "ql3"."step_runs" AS step ON step.run_id = run.id
WHERE run.project_id = $1 AND run.id = $2 AND step.id = $3
LIMIT 2`,
[projectId, runId, stepRunId],
);
if (result.rows.length > 1) throw unavailable();
const row = result.rows[0];
if (!row || TERMINAL_RUN_STATUSES.has(text(row, 'runStatus'))) {
return null;
}
return parseAuthority(row);
} catch (error) {
throw mapStorageError(error);
}
}
export async function listIncompleteOperation(
pool: PostgresPool,
limitValue: number,
): Promise<Readonly<ModelInvocationRecoveryPage>> {
const limit = recoveryLimit(limitValue);
try {
const observation = await pool.query<Row>(
`SELECT floor(extract(epoch FROM statement_timestamp()) * 1000)::bigint
AS "observedAtMs"`,
);
const observedRow = observation.rows[0];
if (observation.rows.length !== 1 || !observedRow) {
throw unavailable();
}
const observedAtMs = integer(observedRow, 'observedAtMs');
const result = await pool.query<Row>(
`SELECT ${START_SELECT}
FROM "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_starts" AS start
JOIN "ql3"."step_run_mutations" AS mutation
ON mutation.mutation_id = start.mutation_id
JOIN "ql3"."run_events" AS event ON event.id = start.run_event_id
LEFT JOIN "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_invocation_completions" AS completion
ON completion.invocation_id = start.invocation_id
WHERE completion.invocation_id IS NULL
AND start.deadline_at_ms <= $1
ORDER BY start.deadline_at_ms, start.invocation_id
LIMIT $2`,
[observedAtMs, limit + 1],
);
const hasMore = result.rows.length > limit;
return Object.freeze({
observedAtMs,
candidates: Object.freeze(
result.rows.slice(0, limit).map((row) => parseStart(row)),
),
hasMore,
});
} catch (error) {
throw mapStorageError(error);
}
}
export async function resolveOperation(
pool: PostgresPool,
commandValue: ModelInvocationResolutionCommand,
): Promise<
Readonly<CommitModelInvocationResult<ModelInvocationResolutionRecord>>
> {
const command = normalizeModelInvocationResolutionCommand(commandValue);
const resolution = command.resolution;
return runPostgresModelInvocationTransaction(pool, async (client) => {
const existing = await resolutionRows(
client,
`resolution.invocation_id = $1 OR
resolution.resolution_id = $2 OR
resolution.mutation_id = $3 OR resolution.run_event_id = $4`,
[
resolution.invocationId,
resolution.resolutionId,
resolution.stepRunMutationId,
resolution.runEventId,
],
);
if (existing.length > 1) throw new ModelInvocationConflictError();
if (existing[0]) {
const stored = parseResolution(existing[0]);
if (JSON.stringify(stored) !== JSON.stringify(resolution)) {
throw new ModelInvocationConflictError();
}
return Object.freeze({ status: 'existing' as const, record: stored });
}
const completions = await completionRows(
client,
'completion.invocation_id = $1',
[resolution.invocationId],
);
if (
completions.length !== 1 ||
JSON.stringify(parseCompletion(completions[0]!)) !==
JSON.stringify(command.completion) ||
command.completion.outcome !== 'outcome_unknown'
) {
throw new ModelInvocationConflictError();
}
await assertCurrent(client, command.stepRunMutation, resolution.projectId);
await applyMutation(client, command.stepRunMutation);
await insertResolution(client, resolution);
return Object.freeze({
status: 'created' as const,
record: resolution,
});
});
}
@@ -0,0 +1,269 @@
import type { PostgresPool } from '@qinglong/runtime-core';
import {
type ModelInvocationPriceQuote,
type ModelInvocationPriceSettlement,
type PricingAwareModelInvocationRepository,
} from '../../pricing/pricing';
import { type PluginPackagePromptOutputArtifact } from '../../prompt-output/pluginPackagePromptOutputArtifact';
import {
type CommitPluginPackagePromptOutputResult,
type PluginPackagePromptOutputCompletionRepository,
} from '../../prompt-output/pluginPackagePromptOutputCompletion';
import type { PluginPackagePromptOutputArtifactTombstone } from '../../prompt-output/pluginPackagePromptOutputRetention';
import {
type ModelInvocationUsageLedgerPage,
type ModelInvocationUsageLedgerQuery,
type ModelInvocationUsageLedgerRecord,
type ModelInvocationUsageLedgerRepository,
type ModelInvocationUsageLedgerSummary,
type ModelInvocationUsageLedgerSummaryQuery,
} from '../../usage/usageLedger';
import {
type ModelInvocationQuotaAdmission,
type ModelInvocationQuotaReservation,
type ModelInvocationQuotaSettlement,
type ModelInvocationQuotaWindowUsage,
type QuotaAwareModelInvocationRepository,
} from '../../usage/usageQuota';
import {
type CommitModelInvocationResult,
type ModelInvocationAuthoritySnapshot,
type ModelInvocationCompletionCommand,
type ModelInvocationCompletionRecord,
type ModelInvocationRecoveryPage,
type ModelInvocationRepository,
type ModelInvocationStartCommand,
type ModelInvocationStartRecord,
} from '../modelInvocation';
import {
type ModelInvocationResolutionCommand,
type ModelInvocationResolutionRecord,
type ModelInvocationResolutionRepository,
} from '../modelInvocationResolution';
import {
admitOperation,
admitWithPricingOperation,
admitWithQuotaOperation,
} from './admissionOperations';
import { unavailable } from './authority';
import {
completeOperation,
completeWithPricingOperation,
completeWithPromptOutputArtifactOperation,
completeWithQuotaOperation,
} from './completionOperations';
import {
findCompletionOperation,
findPriceQuoteOperation,
findPriceSettlementOperation,
findPromptOutputArtifactOperation,
findPromptOutputArtifactTombstoneOperation,
findQuotaReservationOperation,
findQuotaSettlementOperation,
findStartOperation,
findUsageOperation,
listProjectUsageOperation,
readQuotaWindowUsageOperation,
summarizeProjectUsageOperation,
} from './readOperations';
import {
findResolutionOperation,
listIncompleteOperation,
readAuthorityOperation,
resolveOperation,
} from './recoveryResolutionOperations';
export class PostgresModelInvocationRepository
implements
ModelInvocationRepository,
ModelInvocationResolutionRepository,
ModelInvocationUsageLedgerRepository,
QuotaAwareModelInvocationRepository,
PricingAwareModelInvocationRepository,
PluginPackagePromptOutputCompletionRepository
{
constructor(private readonly pool: PostgresPool) {
if (
!pool ||
typeof pool.query !== 'function' ||
typeof pool.connect !== 'function'
) {
throw unavailable();
}
}
async findStart(
invocationIdValue: string,
): Promise<Readonly<ModelInvocationStartRecord> | null> {
return findStartOperation(this.pool, invocationIdValue);
}
async findCompletion(
invocationIdValue: string,
): Promise<Readonly<ModelInvocationCompletionRecord> | null> {
return findCompletionOperation(this.pool, invocationIdValue);
}
async findPromptOutputArtifact(
artifactIdValue: string,
): Promise<Readonly<PluginPackagePromptOutputArtifact> | null> {
return findPromptOutputArtifactOperation(this.pool, artifactIdValue);
}
async findPromptOutputArtifactTombstone(
artifactIdValue: string,
): Promise<Readonly<PluginPackagePromptOutputArtifactTombstone> | null> {
return findPromptOutputArtifactTombstoneOperation(
this.pool,
artifactIdValue,
);
}
async findUsage(
invocationIdValue: string,
): Promise<Readonly<ModelInvocationUsageLedgerRecord> | null> {
return findUsageOperation(this.pool, invocationIdValue);
}
async findPriceQuote(
invocationIdValue: string,
): Promise<Readonly<ModelInvocationPriceQuote> | null> {
return findPriceQuoteOperation(this.pool, invocationIdValue);
}
async findPriceSettlement(
invocationIdValue: string,
): Promise<Readonly<ModelInvocationPriceSettlement> | null> {
return findPriceSettlementOperation(this.pool, invocationIdValue);
}
async findQuotaReservation(
invocationIdValue: string,
): Promise<Readonly<ModelInvocationQuotaReservation> | null> {
return findQuotaReservationOperation(this.pool, invocationIdValue);
}
async findQuotaSettlement(
invocationIdValue: string,
): Promise<Readonly<ModelInvocationQuotaSettlement> | null> {
return findQuotaSettlementOperation(this.pool, invocationIdValue);
}
async readQuotaWindowUsage(
projectIdValue: string,
atMsValue?: number,
): Promise<Readonly<ModelInvocationQuotaWindowUsage> | null> {
return readQuotaWindowUsageOperation(this.pool, projectIdValue, atMsValue);
}
async listProjectUsage(
queryValue: ModelInvocationUsageLedgerQuery,
): Promise<Readonly<ModelInvocationUsageLedgerPage>> {
return listProjectUsageOperation(this.pool, queryValue);
}
async summarizeProjectUsage(
queryValue: ModelInvocationUsageLedgerSummaryQuery,
): Promise<Readonly<ModelInvocationUsageLedgerSummary>> {
return summarizeProjectUsageOperation(this.pool, queryValue);
}
async findResolution(
invocationIdValue: string,
): Promise<Readonly<ModelInvocationResolutionRecord> | null> {
return findResolutionOperation(this.pool, invocationIdValue);
}
async readAuthority(
identity: Readonly<{
projectId: string;
runId: string;
stepRunId: string;
}>,
): Promise<Readonly<ModelInvocationAuthoritySnapshot> | null> {
return readAuthorityOperation(this.pool, identity);
}
async listIncomplete(
limitValue: number,
): Promise<Readonly<ModelInvocationRecoveryPage>> {
return listIncompleteOperation(this.pool, limitValue);
}
async admit(
commandValue: ModelInvocationStartCommand,
): Promise<
Readonly<CommitModelInvocationResult<ModelInvocationStartRecord>>
> {
return admitOperation(this.pool, commandValue);
}
async admitWithQuota(
commandValue: ModelInvocationStartCommand,
admissionValue: ModelInvocationQuotaAdmission,
): Promise<
Readonly<CommitModelInvocationResult<ModelInvocationStartRecord>>
> {
return admitWithQuotaOperation(this.pool, commandValue, admissionValue);
}
async admitWithPricing(
commandValue: ModelInvocationStartCommand,
quoteValue: ModelInvocationPriceQuote,
admissionValue?: ModelInvocationQuotaAdmission,
): Promise<
Readonly<CommitModelInvocationResult<ModelInvocationStartRecord>>
> {
return admitWithPricingOperation(
this.pool,
commandValue,
quoteValue,
admissionValue,
);
}
async complete(
commandValue: ModelInvocationCompletionCommand,
): Promise<
Readonly<CommitModelInvocationResult<ModelInvocationCompletionRecord>>
> {
return completeOperation(this.pool, commandValue);
}
async completeWithQuota(
commandValue: ModelInvocationCompletionCommand,
): Promise<
Readonly<CommitModelInvocationResult<ModelInvocationCompletionRecord>>
> {
return completeWithQuotaOperation(this.pool, commandValue);
}
async completeWithPricing(
commandValue: ModelInvocationCompletionCommand,
): Promise<
Readonly<CommitModelInvocationResult<ModelInvocationCompletionRecord>>
> {
return completeWithPricingOperation(this.pool, commandValue);
}
async completeWithPromptOutputArtifact(
commandValue: ModelInvocationCompletionCommand,
artifactValue: PluginPackagePromptOutputArtifact,
): Promise<Readonly<CommitPluginPackagePromptOutputResult>> {
return completeWithPromptOutputArtifactOperation(
this.pool,
commandValue,
artifactValue,
);
}
async resolve(
commandValue: ModelInvocationResolutionCommand,
): Promise<
Readonly<CommitModelInvocationResult<ModelInvocationResolutionRecord>>
> {
return resolveOperation(this.pool, commandValue);
}
}
@@ -0,0 +1,64 @@
import type { PostgresClient, PostgresPool } from '@qinglong/runtime-core';
import {
MAX_TRANSACTION_ATTEMPTS,
RETRYABLE_SQL_STATES,
mapStorageError,
sqlState,
unavailable,
} from './authority';
export async function begin(client: PostgresClient): Promise<void> {
await client.query('BEGIN ISOLATION LEVEL SERIALIZABLE');
await client.query(`SELECT set_config('statement_timeout', $1, true)`, [
'5s',
]);
await client.query(`SELECT set_config('lock_timeout', $1, true)`, ['2s']);
await client.query(
`SELECT set_config('idle_in_transaction_session_timeout', $1, true)`,
['5s'],
);
}
export async function rollback(client: PostgresClient): Promise<void> {
try {
await client.query('ROLLBACK');
} catch {
// Preserve the original transaction failure.
}
}
export async function runPostgresModelInvocationTransaction<T>(
pool: PostgresPool,
work: (client: PostgresClient) => Promise<T>,
): Promise<T> {
for (let attempt = 0; attempt < MAX_TRANSACTION_ATTEMPTS; attempt += 1) {
let client: PostgresClient;
try {
client = await pool.connect();
} catch (error) {
throw unavailable(error);
}
let began = false;
try {
await begin(client);
began = true;
const result = await work(client);
await client.query('COMMIT');
began = false;
return result;
} catch (error) {
if (began) await rollback(client);
if (
RETRYABLE_SQL_STATES.has(sqlState(error)) &&
attempt + 1 < MAX_TRANSACTION_ATTEMPTS
) {
continue;
}
throw mapStorageError(error);
} finally {
client.release();
}
}
throw unavailable();
}
@@ -0,0 +1 @@
export { PostgresModelInvocationRepository } from './postgres-model-invocation-repository/repository';
@@ -0,0 +1,699 @@
import type { DatabaseSync } from 'node:sqlite';
import { parseSecretRef } from '@qinglong/runtime-core/secret-reference';
import {
ModelProviderCredentialAdministrationAuthorizationFenceConflictError,
ModelProviderCredentialAdministrationMutationConflictError,
normalizeAuthorizedModelProviderCredentialInspection,
normalizeAuthorizedModelProviderCredentialTransitionMutation,
type AuthorizedModelProviderCredentialInspection,
type AuthorizedModelProviderCredentialTransitionMutation,
type ModelProviderCredentialAdministrationInspectionRepository,
type ModelProviderCredentialAdministrationRepository,
} from './modelProviderCredentialAdministration';
import {
InvalidModelProviderCredentialTransitionError,
ModelProviderCredentialCatalogUnavailableError,
ModelProviderCredentialTransitionConflictError,
createModelProviderCredentialTransition,
modelProviderCredentialBindingForTransition,
normalizeModelProviderCredentialTransition,
normalizeModelProviderCredentialTransitionCommand,
type CommitModelProviderCredentialTransitionResult,
type ModelProviderCredentialTransition,
type ModelProviderCredentialTransitionCommand,
} from './modelProviderCredentialCatalog';
import type { LocalModelInvocationOperationAuthority } from '../model-invocation/localModelInvocationRepository';
import {
MODEL_PROVIDER_CREDENTIAL_AUDIT_SCHEMA,
MODEL_PROVIDER_CREDENTIAL_OPERATIONS,
digestModelProviderCredentialBinding,
normalizeModelProviderCredentialBinding,
type ModelProviderCredentialAuditRecord,
type ModelProviderCredentialAuditSink,
type ModelProviderCredentialBinding,
type ModelProviderCredentialBindingLookup,
type ModelProviderCredentialBindingSource,
} from './providerCredential';
type Row = Record<string, unknown>;
export type LocalModelProviderCredentialAuthorizationInput =
| Readonly<{
kind: 'mutation';
value: Readonly<AuthorizedModelProviderCredentialTransitionMutation>;
replay: boolean;
}>
| Readonly<{
kind: 'inspection';
value: Readonly<AuthorizedModelProviderCredentialInspection>;
replay: false;
}>;
export interface LocalModelProviderCredentialAuthorizationGuard {
confirm(input: LocalModelProviderCredentialAuthorizationInput): void;
}
export interface LocalModelProviderCredentialRepositoryOptions {
readonly now?: () => number;
readonly authorization?: LocalModelProviderCredentialAuthorizationGuard;
}
class PrivateLocalAuthority implements LocalModelInvocationOperationAuthority {
readonly client: DatabaseSync;
#tail: Promise<void> = Promise.resolve();
#pending = 0;
constructor(client: DatabaseSync) {
this.client = client;
}
enqueue<T>(
work: () => Promise<T>,
rejection: (reason: 'closed' | 'busy') => Error,
): Promise<T> {
if (!this.client.isOpen) return Promise.reject(rejection('closed'));
if (this.#pending >= 64) return Promise.reject(rejection('busy'));
this.#pending += 1;
const result = this.#tail.then(work, work);
this.#tail = result.then(
() => undefined,
() => undefined,
);
return result.finally(() => {
this.#pending -= 1;
});
}
}
function isAuthority(
value: LocalModelInvocationOperationAuthority | DatabaseSync,
): value is LocalModelInvocationOperationAuthority {
return (
!!value &&
typeof value === 'object' &&
'client' in value &&
'enqueue' in value &&
typeof value.enqueue === 'function'
);
}
function canonical(value: unknown): string {
return JSON.stringify(value);
}
function text(row: Row, key: string): string {
const value = row[key];
if (typeof value !== 'string') {
throw new ModelProviderCredentialCatalogUnavailableError();
}
return value;
}
function integer(row: Row, key: string): number {
const value = row[key];
if (!Number.isSafeInteger(value) || (value as number) < 0) {
throw new ModelProviderCredentialCatalogUnavailableError();
}
return value as number;
}
function nullableText(row: Row, key: string): string | null {
return row[key] === null ? null : text(row, key);
}
function sqliteConstraint(error: unknown): boolean {
if (!error || typeof error !== 'object') return false;
const code = (error as { code?: unknown }).code;
const errcode = (error as { errcode?: unknown }).errcode;
return (
(typeof code === 'string' && code.includes('SQLITE_CONSTRAINT')) ||
(typeof errcode === 'number' && (errcode & 0xff) === 19)
);
}
function mapStorageError(error: unknown): Error {
if (
error instanceof InvalidModelProviderCredentialTransitionError ||
error instanceof ModelProviderCredentialTransitionConflictError ||
error instanceof ModelProviderCredentialCatalogUnavailableError ||
error instanceof
ModelProviderCredentialAdministrationAuthorizationFenceConflictError ||
error instanceof ModelProviderCredentialAdministrationMutationConflictError
) {
return error;
}
if (sqliteConstraint(error)) {
return new ModelProviderCredentialTransitionConflictError();
}
return new ModelProviderCredentialCatalogUnavailableError({
cause: error instanceof Error ? error : undefined,
});
}
function identifier(value: unknown, label: string): string {
if (
typeof value !== 'string' ||
!/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/.test(value)
) {
throw new InvalidModelProviderCredentialTransitionError(
`${label} is invalid`,
);
}
return value;
}
function normalizeUseAudit(
value: Readonly<ModelProviderCredentialAuditRecord>,
): Readonly<ModelProviderCredentialAuditRecord> {
if (
!value ||
typeof value !== 'object' ||
Array.isArray(value) ||
Object.keys(value).sort().join('\0') !==
[
'bindingDigest',
'bindingRevision',
'occurredAtMs',
'operation',
'projectId',
'provider',
'requestId',
'schema',
]
.sort()
.join('\0') ||
value.schema !== MODEL_PROVIDER_CREDENTIAL_AUDIT_SCHEMA ||
!MODEL_PROVIDER_CREDENTIAL_OPERATIONS.includes(value.operation) ||
typeof value.bindingDigest !== 'string' ||
!/^sha256:[0-9a-f]{64}$/.test(value.bindingDigest) ||
!Number.isSafeInteger(value.occurredAtMs) ||
value.occurredAtMs < 0
) {
throw new ModelProviderCredentialCatalogUnavailableError();
}
return Object.freeze({
schema: MODEL_PROVIDER_CREDENTIAL_AUDIT_SCHEMA,
operation: value.operation,
projectId: identifier(value.projectId, 'projectId'),
provider: identifier(value.provider, 'provider'),
requestId: identifier(value.requestId, 'requestId'),
bindingRevision: identifier(value.bindingRevision, 'bindingRevision'),
bindingDigest: value.bindingDigest,
occurredAtMs: value.occurredAtMs,
});
}
function sameUseAudit(
left: Readonly<ModelProviderCredentialAuditRecord>,
right: Readonly<ModelProviderCredentialAuditRecord>,
): boolean {
return (
canonical({ ...left, occurredAtMs: right.occurredAtMs }) ===
canonical(right)
);
}
export class LocalModelProviderCredentialRepository
implements
ModelProviderCredentialAdministrationRepository,
ModelProviderCredentialAdministrationInspectionRepository,
ModelProviderCredentialBindingSource,
ModelProviderCredentialAuditSink
{
readonly #authority: LocalModelInvocationOperationAuthority;
readonly #now: () => number;
readonly #authorization:
| LocalModelProviderCredentialAuthorizationGuard
| undefined;
constructor(
authority: LocalModelInvocationOperationAuthority | DatabaseSync,
options: LocalModelProviderCredentialRepositoryOptions = {},
) {
if (
!options ||
typeof options !== 'object' ||
Array.isArray(options) ||
Object.keys(options).some(
(key) => key !== 'authorization' && key !== 'now',
) ||
(options.now !== undefined && typeof options.now !== 'function') ||
(options.authorization !== undefined &&
typeof options.authorization.confirm !== 'function')
) {
throw new TypeError(
'Local model provider credential repository options are invalid',
);
}
this.#authority = isAuthority(authority)
? authority
: new PrivateLocalAuthority(authority);
this.#now = options.now ?? Date.now;
this.#authorization = options.authorization;
}
#enqueue<T>(work: () => T | Promise<T>): Promise<T> {
return this.#authority.enqueue(
async () => {
try {
return await work();
} catch (error) {
throw mapStorageError(error);
}
},
() => new ModelProviderCredentialCatalogUnavailableError(),
);
}
#rowByMutation(mutationId: string): Row | undefined {
return this.#authority.client
.prepare(
`SELECT command_json AS "commandJson",
transition_json AS "transitionJson"
FROM "ModelInvocationProviderCredentialTransitions"
WHERE mutation_id = ?`,
)
.get(mutationId) as Row | undefined;
}
#currentRow(projectId: string, provider: string): Row | undefined {
return this.#authority.client
.prepare(
`SELECT transition_json AS "transitionJson"
FROM "ModelInvocationProviderCredentialTransitions"
WHERE project_id = ? AND provider = ?
ORDER BY generation DESC LIMIT 1`,
)
.get(projectId, provider) as Row | undefined;
}
#transition(row: Row): Readonly<ModelProviderCredentialTransition> {
try {
return normalizeModelProviderCredentialTransition(
JSON.parse(
text(row, 'transitionJson'),
) as ModelProviderCredentialTransition,
);
} catch (error) {
if (error instanceof ModelProviderCredentialCatalogUnavailableError) {
throw error;
}
throw new ModelProviderCredentialCatalogUnavailableError();
}
}
#current(
projectId: string,
provider: string,
): Readonly<ModelProviderCredentialTransition> | null {
const row = this.#currentRow(projectId, provider);
if (!row) return null;
const transition = this.#transition(row);
if (
transition.projectId !== projectId ||
transition.provider !== provider
) {
throw new ModelProviderCredentialCatalogUnavailableError();
}
return transition;
}
#existing(
command: Readonly<ModelProviderCredentialTransitionCommand>,
): Readonly<CommitModelProviderCredentialTransitionResult> | null {
const row = this.#rowByMutation(command.mutationId);
if (!row) return null;
let storedCommand: Readonly<ModelProviderCredentialTransitionCommand>;
try {
storedCommand = normalizeModelProviderCredentialTransitionCommand(
JSON.parse(
text(row, 'commandJson'),
) as ModelProviderCredentialTransitionCommand,
);
} catch {
throw new ModelProviderCredentialCatalogUnavailableError();
}
if (canonical(storedCommand) !== canonical(command)) {
throw new ModelProviderCredentialTransitionConflictError();
}
return Object.freeze({
status: 'existing' as const,
transition: this.#transition(row),
});
}
#assertSecretExists(binding: Readonly<ModelProviderCredentialBinding>): void {
let reference;
try {
reference = parseSecretRef(binding.secretRef);
} catch {
throw new ModelProviderCredentialTransitionConflictError();
}
const row =
reference.version === undefined
? this.#authority.client
.prepare(
`SELECT version FROM "QingLong3LocalSecretEnvelopes"
WHERE project_id = ? AND secret_name = ?
ORDER BY version DESC LIMIT 1`,
)
.get(reference.projectId, reference.name)
: this.#authority.client
.prepare(
`SELECT version FROM "QingLong3LocalSecretEnvelopes"
WHERE project_id = ? AND secret_name = ? AND version = ?`,
)
.get(reference.projectId, reference.name, reference.version);
if (!row) throw new ModelProviderCredentialTransitionConflictError();
}
#binding(
transition: Readonly<ModelProviderCredentialTransition>,
): Readonly<ModelProviderCredentialBinding> | null {
if (transition.action === 'revoke') {
return modelProviderCredentialBindingForTransition(transition, null);
}
const row = this.#authority.client
.prepare(
`SELECT binding_digest AS "bindingDigest",
binding_json AS "bindingJson"
FROM "ModelInvocationProviderCredentialBindings"
WHERE project_id = ? AND provider = ? AND revision = ?`,
)
.get(
transition.projectId,
transition.provider,
transition.activeBindingRevision,
) as Row | undefined;
if (!row) throw new ModelProviderCredentialCatalogUnavailableError();
let binding: Readonly<ModelProviderCredentialBinding>;
try {
binding = normalizeModelProviderCredentialBinding(
JSON.parse(text(row, 'bindingJson')) as ModelProviderCredentialBinding,
);
} catch {
throw new ModelProviderCredentialCatalogUnavailableError();
}
if (
digestModelProviderCredentialBinding(binding).slice(7) !==
text(row, 'bindingDigest')
) {
throw new ModelProviderCredentialCatalogUnavailableError();
}
return modelProviderCredentialBindingForTransition(transition, binding);
}
#insertBinding(binding: Readonly<ModelProviderCredentialBinding>): void {
const digest = digestModelProviderCredentialBinding(binding).slice(7);
const existing = this.#authority.client
.prepare(
`SELECT binding_digest AS "bindingDigest",
binding_json AS "bindingJson"
FROM "ModelInvocationProviderCredentialBindings"
WHERE project_id = ? AND provider = ? AND revision = ?`,
)
.get(binding.projectId, binding.provider, binding.revision) as
| Row
| undefined;
if (existing) {
if (
text(existing, 'bindingDigest') !== digest ||
text(existing, 'bindingJson') !== canonical(binding)
) {
throw new ModelProviderCredentialTransitionConflictError();
}
return;
}
this.#authority.client
.prepare(
`INSERT INTO "ModelInvocationProviderCredentialBindings" (
project_id, provider, revision, binding_digest, binding_json
) VALUES (?, ?, ?, ?, ?)`,
)
.run(
binding.projectId,
binding.provider,
binding.revision,
digest,
canonical(binding),
);
}
#insertTransition(
command: Readonly<ModelProviderCredentialTransitionCommand>,
transition: Readonly<ModelProviderCredentialTransition>,
): void {
this.#authority.client
.prepare(
`INSERT INTO "ModelInvocationProviderCredentialTransitions" (
mutation_id, project_id, provider, generation, action,
active_binding_revision, active_binding_digest,
previous_transition_digest, changed_by_type, changed_by_id,
changed_at_ms, command_digest, transition_digest,
command_json, transition_json
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(
transition.mutationId,
transition.projectId,
transition.provider,
transition.generation,
transition.action,
transition.activeBindingRevision,
transition.activeBindingDigest,
transition.previousTransitionDigest,
transition.changedBy.type,
transition.changedBy.id,
transition.changedAtMs,
transition.commandDigest,
transition.transitionDigest,
canonical(command),
canonical(transition),
);
}
#commit(
command: Readonly<ModelProviderCredentialTransitionCommand>,
authorized?: Readonly<AuthorizedModelProviderCredentialTransitionMutation>,
): Readonly<CommitModelProviderCredentialTransitionResult> {
let began = false;
try {
this.#authority.client.exec('BEGIN IMMEDIATE');
began = true;
const existing = this.#existing(command);
if (existing) {
if (authorized) {
if (!this.#authorization) {
throw new ModelProviderCredentialAdministrationAuthorizationFenceConflictError();
}
this.#authorization.confirm(
Object.freeze({
kind: 'mutation',
value: authorized,
replay: true,
}),
);
}
this.#authority.client.exec('COMMIT');
began = false;
return existing;
}
if (authorized) {
if (!this.#authorization) {
throw new ModelProviderCredentialAdministrationAuthorizationFenceConflictError();
}
this.#authorization.confirm(
Object.freeze({ kind: 'mutation', value: authorized, replay: false }),
);
}
const previous = this.#current(command.projectId, command.provider);
const changedAtMs = this.#now();
if (!Number.isSafeInteger(changedAtMs) || changedAtMs < 0) {
throw new ModelProviderCredentialCatalogUnavailableError();
}
const transition = createModelProviderCredentialTransition(
command,
previous,
changedAtMs,
);
if (command.binding) {
this.#assertSecretExists(command.binding);
this.#insertBinding(command.binding);
}
this.#insertTransition(command, transition);
this.#authority.client.exec('COMMIT');
began = false;
return Object.freeze({ status: 'created' as const, transition });
} finally {
if (began && this.#authority.client.isTransaction) {
try {
this.#authority.client.exec('ROLLBACK');
} catch {
// Preserve the original fail-closed error.
}
}
}
}
findCurrentTransition(projectIdValue: string, providerValue: string) {
const projectId = identifier(projectIdValue, 'projectId');
const provider = identifier(providerValue, 'provider');
return this.#enqueue(() => this.#current(projectId, provider));
}
commit(commandValue: Readonly<ModelProviderCredentialTransitionCommand>) {
const command =
normalizeModelProviderCredentialTransitionCommand(commandValue);
return this.#enqueue(() => this.#commit(command));
}
commitAuthorized(
mutationValue: AuthorizedModelProviderCredentialTransitionMutation,
) {
const mutation =
normalizeAuthorizedModelProviderCredentialTransitionMutation(
mutationValue,
);
return this.#enqueue(() => this.#commit(mutation.command, mutation));
}
inspectAuthorized(
inspectionValue: AuthorizedModelProviderCredentialInspection,
): Promise<Readonly<ModelProviderCredentialTransition> | null> {
const inspection =
normalizeAuthorizedModelProviderCredentialInspection(inspectionValue);
return this.#enqueue(() => {
if (!this.#authorization) {
throw new ModelProviderCredentialAdministrationAuthorizationFenceConflictError();
}
let began = false;
try {
this.#authority.client.exec('BEGIN IMMEDIATE');
began = true;
const transition = this.#current(
inspection.projectId,
inspection.provider,
);
this.#authorization.confirm(
Object.freeze({
kind: 'inspection',
value: inspection,
replay: false,
}),
);
this.#authority.client.exec('COMMIT');
began = false;
return transition;
} finally {
if (began && this.#authority.client.isTransaction) {
try {
this.#authority.client.exec('ROLLBACK');
} catch {
// Preserve the original fail-closed error.
}
}
}
});
}
resolveModelProviderCredentialBinding(
lookupValue: Readonly<ModelProviderCredentialBindingLookup>,
): Promise<Readonly<ModelProviderCredentialBinding> | null> {
if (
!lookupValue ||
typeof lookupValue !== 'object' ||
Array.isArray(lookupValue) ||
Object.keys(lookupValue).sort().join('\0') !== 'projectId\0provider'
) {
return Promise.reject(
new ModelProviderCredentialCatalogUnavailableError(),
);
}
const projectId = identifier(lookupValue.projectId, 'projectId');
const provider = identifier(lookupValue.provider, 'provider');
return this.#enqueue(() => {
const transition = this.#current(projectId, provider);
return transition ? this.#binding(transition) : null;
});
}
record(
recordValue: Readonly<ModelProviderCredentialAuditRecord>,
): Promise<void> {
const record = normalizeUseAudit(recordValue);
return this.#enqueue(() => {
let began = false;
try {
this.#authority.client.exec('BEGIN IMMEDIATE');
began = true;
const transition = this.#current(record.projectId, record.provider);
if (
!transition ||
transition.action !== 'bind' ||
transition.activeBindingRevision !== record.bindingRevision ||
`sha256:${transition.activeBindingDigest}` !== record.bindingDigest
) {
throw new ModelProviderCredentialTransitionConflictError();
}
const existing = this.#authority.client
.prepare(
`SELECT audit_json AS "auditJson"
FROM "ModelInvocationProviderCredentialAudits"
WHERE operation = ? AND project_id = ? AND provider = ?
AND request_id = ?`,
)
.get(
record.operation,
record.projectId,
record.provider,
record.requestId,
) as Row | undefined;
if (existing) {
let stored: Readonly<ModelProviderCredentialAuditRecord>;
try {
stored = normalizeUseAudit(
JSON.parse(
text(existing, 'auditJson'),
) as ModelProviderCredentialAuditRecord,
);
} catch {
throw new ModelProviderCredentialCatalogUnavailableError();
}
if (!sameUseAudit(stored, record)) {
throw new ModelProviderCredentialTransitionConflictError();
}
} else {
this.#authority.client
.prepare(
`INSERT INTO "ModelInvocationProviderCredentialAudits" (
operation, project_id, provider, request_id,
binding_revision, binding_digest, occurred_at_ms, audit_json
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(
record.operation,
record.projectId,
record.provider,
record.requestId,
record.bindingRevision,
record.bindingDigest,
record.occurredAtMs,
canonical(record),
);
}
this.#authority.client.exec('COMMIT');
began = false;
} finally {
if (began && this.#authority.client.isTransaction) {
try {
this.#authority.client.exec('ROLLBACK');
} catch {
// Preserve the original fail-closed error.
}
}
}
});
}
}
@@ -0,0 +1,187 @@
import { createHash } from 'node:crypto';
import type {
SecurityPolicyFence,
SecuritySubject,
} from '@qinglong/runtime-core/security';
export const MODEL_PROVIDER_CREDENTIAL_TEST_ALLOWLIST_SCHEMA =
'qinglong/model-provider-credential-test-allowlist@v1' as const;
export const MODEL_PROVIDER_CREDENTIAL_TEST_PLAN_SCHEMA =
'qinglong/model-provider-credential-test-plan@v1' as const;
export const MODEL_PROVIDER_CREDENTIAL_TEST_EXECUTION_SCHEMA =
'qinglong/model-provider-credential-test-execution@v1' as const;
export const MODEL_PROVIDER_CREDENTIAL_TEST_RESULT_SCHEMA =
'qinglong/model-provider-credential-test-result@v1' as const;
export const MAX_MODEL_PROVIDER_CREDENTIAL_TEST_ALLOWLIST_ENTRIES = 16;
export const MAX_MODEL_PROVIDER_CREDENTIAL_TEST_DEADLINE_MS = 15_000;
export const MAX_MODEL_PROVIDER_CREDENTIAL_TEST_RESPONSE_BYTES = 256 * 1_024;
export const MAX_MODEL_PROVIDER_CREDENTIAL_TEST_MODELS = 256;
export const MAX_MODEL_PROVIDER_CREDENTIAL_TEST_PLAN_LIFETIME_MS =
5 * 60 * 1_000;
export interface ModelProviderCredentialTestEndpointInput {
readonly provider: string;
readonly adapter: 'openai-compatible';
readonly baseUrl: string;
readonly revision: string;
readonly deadlineMs: number;
readonly maxResponseBytes: number;
readonly maxModels: number;
readonly maxCostMicrousd: 0;
readonly retryLimit: 0;
}
export interface ModelProviderCredentialTestEndpoint
extends ModelProviderCredentialTestEndpointInput {
readonly configDigest: string;
}
export interface ModelProviderCredentialTestAllowlist {
readonly schema: typeof MODEL_PROVIDER_CREDENTIAL_TEST_ALLOWLIST_SCHEMA;
readonly revision: string;
readonly providers: readonly Readonly<ModelProviderCredentialTestEndpoint>[];
readonly catalogDigest: string;
}
export interface ModelProviderCredentialTestPlan {
readonly schema: typeof MODEL_PROVIDER_CREDENTIAL_TEST_PLAN_SCHEMA;
readonly testId: string;
readonly requestId: string;
readonly projectId: string;
readonly provider: string;
readonly endpoint: Readonly<ModelProviderCredentialTestEndpoint>;
readonly requestedBy: Readonly<{ type: 'user'; id: string }>;
readonly fence: Readonly<{
projectVersion: number;
bindingVersion: number;
}>;
readonly plannedAtMs: number;
readonly expiresAtMs: number;
readonly planDigest: string;
}
export interface CreateModelProviderCredentialTestPlanInput {
readonly testId: string;
readonly requestId: string;
readonly projectId: string;
readonly provider: string;
readonly endpoint: Readonly<ModelProviderCredentialTestEndpoint>;
readonly requestedBy: SecuritySubject;
readonly fence: SecurityPolicyFence;
readonly plannedAtMs: number;
readonly expiresAtMs: number;
}
export interface ModelProviderCredentialTestExecution {
readonly schema: typeof MODEL_PROVIDER_CREDENTIAL_TEST_EXECUTION_SCHEMA;
readonly executionId: string;
readonly testId: string;
readonly planDigest: string;
readonly startedAtMs: number;
readonly executionDigest: string;
}
export type ModelProviderCredentialTestOutcome = 'reachable' | 'unreachable';
export interface ModelProviderCredentialTestResult {
readonly schema: typeof MODEL_PROVIDER_CREDENTIAL_TEST_RESULT_SCHEMA;
readonly executionId: string;
readonly testId: string;
readonly planDigest: string;
readonly outcome: ModelProviderCredentialTestOutcome;
readonly modelCount: number | null;
readonly durationMs: number;
readonly completedAtMs: number;
readonly resultDigest: string;
}
export class InvalidModelProviderCredentialTestConnectionError extends TypeError {
readonly code = 'MODEL_PROVIDER_CREDENTIAL_TEST_CONNECTION_INVALID';
constructor() {
super('Model provider credential test connection value is invalid');
this.name = 'InvalidModelProviderCredentialTestConnectionError';
}
}
const UUID_V4_PATTERN =
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
const IDENTIFIER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
const REQUEST_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
const DIGEST_PATTERN = /^[0-9a-f]{64}$/;
export const MODEL_PROVIDER_CREDENTIAL_TEST_CONTROL_PATTERN =
/[\u0000-\u001f\u007f]/;
export function invalid(): never {
throw new InvalidModelProviderCredentialTestConnectionError();
}
export function exact(
value: unknown,
keys: readonly string[],
): Record<string, unknown> {
if (!value || typeof value !== 'object' || Array.isArray(value)) invalid();
const candidate = value as Record<string, unknown>;
const actual = Reflect.ownKeys(candidate);
const expected = [...keys].sort();
if (
actual.some((key) => typeof key !== 'string') ||
actual.length !== expected.length ||
actual
.map(String)
.sort()
.some((key, index) => key !== expected[index])
) {
invalid();
}
const descriptors = Object.getOwnPropertyDescriptors(candidate);
if (
Object.values(descriptors).some(
({ get, set, enumerable }) =>
get !== undefined || set !== undefined || enumerable !== true,
)
) {
invalid();
}
return candidate;
}
export function identifier(value: unknown, request = false): string {
const pattern = request ? REQUEST_ID_PATTERN : IDENTIFIER_PATTERN;
if (typeof value !== 'string' || !pattern.test(value)) invalid();
return value;
}
export function uuid(value: unknown): string {
if (typeof value !== 'string' || !UUID_V4_PATTERN.test(value)) invalid();
return value;
}
export function digest(value: unknown): string {
if (typeof value !== 'string' || !DIGEST_PATTERN.test(value)) invalid();
return value;
}
export function integer(
value: unknown,
minimum: number,
maximum: number,
): number {
if (
!Number.isSafeInteger(value) ||
(value as number) < minimum ||
(value as number) > maximum
) {
invalid();
}
return value as number;
}
export function sha256(domain: string, value: unknown): string {
return createHash('sha256')
.update(domain, 'utf8')
.update(JSON.stringify(value), 'utf8')
.digest('hex');
}
@@ -0,0 +1,239 @@
import {
MAX_MODEL_PROVIDER_CREDENTIAL_TEST_ALLOWLIST_ENTRIES,
MAX_MODEL_PROVIDER_CREDENTIAL_TEST_DEADLINE_MS,
MAX_MODEL_PROVIDER_CREDENTIAL_TEST_MODELS,
MAX_MODEL_PROVIDER_CREDENTIAL_TEST_RESPONSE_BYTES,
MODEL_PROVIDER_CREDENTIAL_TEST_ALLOWLIST_SCHEMA,
MODEL_PROVIDER_CREDENTIAL_TEST_CONTROL_PATTERN,
digest,
exact,
identifier,
integer,
invalid,
sha256,
type ModelProviderCredentialTestAllowlist,
type ModelProviderCredentialTestEndpoint,
type ModelProviderCredentialTestEndpointInput,
} from './contractProtocol';
const ENDPOINT_DIGEST_DOMAIN =
'qinglong/model-provider-credential-test-endpoint-digest@v1\0';
const ALLOWLIST_DIGEST_DOMAIN =
'qinglong/model-provider-credential-test-allowlist-digest@v1\0';
function canonicalBaseUrl(value: unknown): string {
if (
typeof value !== 'string' ||
value.length < 1 ||
Buffer.byteLength(value, 'utf8') > 1_024 ||
MODEL_PROVIDER_CREDENTIAL_TEST_CONTROL_PATTERN.test(value)
) {
invalid();
}
let url: URL;
try {
url = new URL(value);
} catch {
return invalid();
}
if (
url.protocol !== 'https:' ||
url.username !== '' ||
url.password !== '' ||
url.search !== '' ||
url.hash !== '' ||
url.hostname.length < 1 ||
url.pathname.length < 1 ||
!url.pathname.endsWith('/') ||
url.toString() !== value
) {
invalid();
}
return value;
}
function endpointSemantic(
endpoint: Readonly<ModelProviderCredentialTestEndpointInput>,
): Readonly<ModelProviderCredentialTestEndpointInput> {
return Object.freeze({
provider: endpoint.provider,
adapter: endpoint.adapter,
baseUrl: endpoint.baseUrl,
revision: endpoint.revision,
deadlineMs: endpoint.deadlineMs,
maxResponseBytes: endpoint.maxResponseBytes,
maxModels: endpoint.maxModels,
maxCostMicrousd: 0,
retryLimit: 0,
});
}
export function createModelProviderCredentialTestEndpoint(
value: ModelProviderCredentialTestEndpointInput,
): Readonly<ModelProviderCredentialTestEndpoint> {
exact(value, [
'adapter',
'baseUrl',
'deadlineMs',
'maxCostMicrousd',
'maxModels',
'maxResponseBytes',
'provider',
'retryLimit',
'revision',
]);
const semantic = endpointSemantic({
provider: identifier(value.provider),
adapter: value.adapter === 'openai-compatible' ? value.adapter : invalid(),
baseUrl: canonicalBaseUrl(value.baseUrl),
revision: identifier(value.revision),
deadlineMs: integer(
value.deadlineMs,
1_000,
MAX_MODEL_PROVIDER_CREDENTIAL_TEST_DEADLINE_MS,
),
maxResponseBytes: integer(
value.maxResponseBytes,
1_024,
MAX_MODEL_PROVIDER_CREDENTIAL_TEST_RESPONSE_BYTES,
),
maxModels: integer(
value.maxModels,
1,
MAX_MODEL_PROVIDER_CREDENTIAL_TEST_MODELS,
),
maxCostMicrousd:
value.maxCostMicrousd === 0 ? value.maxCostMicrousd : invalid(),
retryLimit: value.retryLimit === 0 ? value.retryLimit : invalid(),
});
return Object.freeze({
...semantic,
configDigest: sha256(ENDPOINT_DIGEST_DOMAIN, semantic),
});
}
export function normalizeModelProviderCredentialTestEndpoint(
value: ModelProviderCredentialTestEndpoint,
): Readonly<ModelProviderCredentialTestEndpoint> {
exact(value, [
'adapter',
'baseUrl',
'configDigest',
'deadlineMs',
'maxCostMicrousd',
'maxModels',
'maxResponseBytes',
'provider',
'retryLimit',
'revision',
]);
const normalized = createModelProviderCredentialTestEndpoint({
provider: value.provider,
adapter: value.adapter,
baseUrl: value.baseUrl,
revision: value.revision,
deadlineMs: value.deadlineMs,
maxResponseBytes: value.maxResponseBytes,
maxModels: value.maxModels,
maxCostMicrousd: value.maxCostMicrousd,
retryLimit: value.retryLimit,
});
if (digest(value.configDigest) !== normalized.configDigest) invalid();
return normalized;
}
function allowlistSemantic(
revision: string,
providers: readonly Readonly<ModelProviderCredentialTestEndpoint>[],
) {
return Object.freeze({
schema: MODEL_PROVIDER_CREDENTIAL_TEST_ALLOWLIST_SCHEMA,
revision,
providers,
});
}
export function createModelProviderCredentialTestAllowlist(
value: Readonly<{
revision: string;
providers: readonly ModelProviderCredentialTestEndpointInput[];
}>,
): Readonly<ModelProviderCredentialTestAllowlist> {
exact(value, ['providers', 'revision']);
if (
!Array.isArray(value.providers) ||
value.providers.length < 1 ||
value.providers.length >
MAX_MODEL_PROVIDER_CREDENTIAL_TEST_ALLOWLIST_ENTRIES
) {
invalid();
}
const providers = Object.freeze(
value.providers
.map(createModelProviderCredentialTestEndpoint)
.sort((left, right) => left.provider.localeCompare(right.provider)),
);
if (
providers.some(
(provider, index) =>
index > 0 && providers[index - 1]?.provider === provider.provider,
)
) {
invalid();
}
const semantic = allowlistSemantic(identifier(value.revision), providers);
return Object.freeze({
...semantic,
catalogDigest: sha256(ALLOWLIST_DIGEST_DOMAIN, semantic),
});
}
export function normalizeModelProviderCredentialTestAllowlist(
value: ModelProviderCredentialTestAllowlist,
): Readonly<ModelProviderCredentialTestAllowlist> {
exact(value, ['catalogDigest', 'providers', 'revision', 'schema']);
if (
value.schema !== MODEL_PROVIDER_CREDENTIAL_TEST_ALLOWLIST_SCHEMA ||
!Array.isArray(value.providers) ||
value.providers.length < 1 ||
value.providers.length >
MAX_MODEL_PROVIDER_CREDENTIAL_TEST_ALLOWLIST_ENTRIES
) {
invalid();
}
const providers = Object.freeze(
value.providers.map(normalizeModelProviderCredentialTestEndpoint),
);
if (
providers.some(
(provider, index) =>
(index > 0 &&
providers[index - 1]!.provider.localeCompare(provider.provider) >=
0) ||
provider.configDigest !== value.providers[index]?.configDigest,
)
) {
invalid();
}
const semantic = allowlistSemantic(identifier(value.revision), providers);
if (
digest(value.catalogDigest) !== sha256(ALLOWLIST_DIGEST_DOMAIN, semantic)
) {
invalid();
}
return Object.freeze({ ...semantic, catalogDigest: value.catalogDigest });
}
export function resolveModelProviderCredentialTestEndpoint(
allowlistValue: ModelProviderCredentialTestAllowlist,
providerValue: string,
): Readonly<ModelProviderCredentialTestEndpoint> {
const allowlist =
normalizeModelProviderCredentialTestAllowlist(allowlistValue);
const provider = identifier(providerValue);
const endpoint = allowlist.providers.find(
(candidate) => candidate.provider === provider,
);
if (!endpoint) invalid();
return endpoint;
}
@@ -0,0 +1,175 @@
import {
MAX_MODEL_PROVIDER_CREDENTIAL_TEST_DEADLINE_MS,
MAX_MODEL_PROVIDER_CREDENTIAL_TEST_MODELS,
MODEL_PROVIDER_CREDENTIAL_TEST_EXECUTION_SCHEMA,
MODEL_PROVIDER_CREDENTIAL_TEST_RESULT_SCHEMA,
digest,
exact,
integer,
invalid,
sha256,
uuid,
type ModelProviderCredentialTestExecution,
type ModelProviderCredentialTestOutcome,
type ModelProviderCredentialTestResult,
} from './contractProtocol';
const EXECUTION_DIGEST_DOMAIN =
'qinglong/model-provider-credential-test-execution-digest@v1\0';
const RESULT_DIGEST_DOMAIN =
'qinglong/model-provider-credential-test-result-digest@v1\0';
function executionSemantic(
value: Omit<ModelProviderCredentialTestExecution, 'executionDigest'>,
) {
return Object.freeze({
schema: MODEL_PROVIDER_CREDENTIAL_TEST_EXECUTION_SCHEMA,
executionId: value.executionId,
testId: value.testId,
planDigest: value.planDigest,
startedAtMs: value.startedAtMs,
});
}
export function createModelProviderCredentialTestExecution(
value: Readonly<{
executionId: string;
testId: string;
planDigest: string;
startedAtMs: number;
}>,
): Readonly<ModelProviderCredentialTestExecution> {
exact(value, ['executionId', 'planDigest', 'startedAtMs', 'testId']);
const semantic = executionSemantic({
schema: MODEL_PROVIDER_CREDENTIAL_TEST_EXECUTION_SCHEMA,
executionId: uuid(value.executionId),
testId: uuid(value.testId),
planDigest: digest(value.planDigest),
startedAtMs: integer(value.startedAtMs, 0, Number.MAX_SAFE_INTEGER),
});
return Object.freeze({
...semantic,
executionDigest: sha256(EXECUTION_DIGEST_DOMAIN, semantic),
});
}
export function normalizeModelProviderCredentialTestExecution(
value: ModelProviderCredentialTestExecution,
): Readonly<ModelProviderCredentialTestExecution> {
exact(value, [
'executionDigest',
'executionId',
'planDigest',
'schema',
'startedAtMs',
'testId',
]);
if (value.schema !== MODEL_PROVIDER_CREDENTIAL_TEST_EXECUTION_SCHEMA) {
invalid();
}
const normalized = createModelProviderCredentialTestExecution({
executionId: value.executionId,
testId: value.testId,
planDigest: value.planDigest,
startedAtMs: value.startedAtMs,
});
if (digest(value.executionDigest) !== normalized.executionDigest) invalid();
return normalized;
}
function resultSemantic(
value: Omit<ModelProviderCredentialTestResult, 'resultDigest'>,
) {
return Object.freeze({
schema: MODEL_PROVIDER_CREDENTIAL_TEST_RESULT_SCHEMA,
executionId: value.executionId,
testId: value.testId,
planDigest: value.planDigest,
outcome: value.outcome,
modelCount: value.modelCount,
durationMs: value.durationMs,
completedAtMs: value.completedAtMs,
});
}
export function createModelProviderCredentialTestResult(
value: Readonly<{
executionId: string;
testId: string;
planDigest: string;
outcome: ModelProviderCredentialTestOutcome;
modelCount: number | null;
durationMs: number;
completedAtMs: number;
}>,
): Readonly<ModelProviderCredentialTestResult> {
exact(value, [
'completedAtMs',
'durationMs',
'executionId',
'modelCount',
'outcome',
'planDigest',
'testId',
]);
const outcome =
value.outcome === 'reachable' || value.outcome === 'unreachable'
? value.outcome
: invalid();
const modelCount =
value.modelCount === null
? null
: integer(value.modelCount, 0, MAX_MODEL_PROVIDER_CREDENTIAL_TEST_MODELS);
if (
(outcome === 'reachable' && modelCount === null) ||
(outcome === 'unreachable' && modelCount !== null)
) {
invalid();
}
const semantic = resultSemantic({
schema: MODEL_PROVIDER_CREDENTIAL_TEST_RESULT_SCHEMA,
executionId: uuid(value.executionId),
testId: uuid(value.testId),
planDigest: digest(value.planDigest),
outcome,
modelCount,
durationMs: integer(
value.durationMs,
0,
MAX_MODEL_PROVIDER_CREDENTIAL_TEST_DEADLINE_MS,
),
completedAtMs: integer(value.completedAtMs, 0, Number.MAX_SAFE_INTEGER),
});
return Object.freeze({
...semantic,
resultDigest: sha256(RESULT_DIGEST_DOMAIN, semantic),
});
}
export function normalizeModelProviderCredentialTestResult(
value: ModelProviderCredentialTestResult,
): Readonly<ModelProviderCredentialTestResult> {
exact(value, [
'completedAtMs',
'durationMs',
'executionId',
'modelCount',
'outcome',
'planDigest',
'resultDigest',
'schema',
'testId',
]);
if (value.schema !== MODEL_PROVIDER_CREDENTIAL_TEST_RESULT_SCHEMA) invalid();
const normalized = createModelProviderCredentialTestResult({
executionId: value.executionId,
testId: value.testId,
planDigest: value.planDigest,
outcome: value.outcome,
modelCount: value.modelCount,
durationMs: value.durationMs,
completedAtMs: value.completedAtMs,
});
if (digest(value.resultDigest) !== normalized.resultDigest) invalid();
return normalized;
}
@@ -0,0 +1,142 @@
import type {
SecurityPolicyFence,
SecuritySubject,
} from '@qinglong/runtime-core/security';
import {
MAX_MODEL_PROVIDER_CREDENTIAL_TEST_PLAN_LIFETIME_MS,
MODEL_PROVIDER_CREDENTIAL_TEST_CONTROL_PATTERN,
MODEL_PROVIDER_CREDENTIAL_TEST_PLAN_SCHEMA,
digest,
exact,
identifier,
integer,
invalid,
sha256,
uuid,
type CreateModelProviderCredentialTestPlanInput,
type ModelProviderCredentialTestPlan,
} from './contractProtocol';
import { normalizeModelProviderCredentialTestEndpoint } from './endpointAllowlistProtocol';
const PLAN_DIGEST_DOMAIN =
'qinglong/model-provider-credential-test-plan-digest@v1\0';
function user(value: SecuritySubject): Readonly<{ type: 'user'; id: string }> {
exact(value, ['id', 'type']);
if (
value.type !== 'user' ||
typeof value.id !== 'string' ||
value.id.length < 1 ||
Buffer.byteLength(value.id, 'utf8') > 128 ||
MODEL_PROVIDER_CREDENTIAL_TEST_CONTROL_PATTERN.test(value.id)
) {
invalid();
}
return Object.freeze({ type: 'user' as const, id: value.id });
}
function fence(value: SecurityPolicyFence): Readonly<{
projectVersion: number;
bindingVersion: number;
}> {
exact(value, ['bindingVersion', 'projectVersion']);
if (value.bindingVersion === null) invalid();
return Object.freeze({
projectVersion: integer(value.projectVersion, 1, 2_147_483_647),
bindingVersion: integer(value.bindingVersion, 1, 2_147_483_647),
});
}
function planSemantic(
input: Omit<ModelProviderCredentialTestPlan, 'planDigest'>,
) {
return Object.freeze({
schema: MODEL_PROVIDER_CREDENTIAL_TEST_PLAN_SCHEMA,
testId: input.testId,
requestId: input.requestId,
projectId: input.projectId,
provider: input.provider,
endpoint: input.endpoint,
requestedBy: input.requestedBy,
fence: input.fence,
plannedAtMs: input.plannedAtMs,
expiresAtMs: input.expiresAtMs,
});
}
export function createModelProviderCredentialTestPlan(
value: CreateModelProviderCredentialTestPlanInput,
): Readonly<ModelProviderCredentialTestPlan> {
exact(value, [
'endpoint',
'expiresAtMs',
'fence',
'plannedAtMs',
'projectId',
'provider',
'requestId',
'requestedBy',
'testId',
]);
const plannedAtMs = integer(value.plannedAtMs, 0, Number.MAX_SAFE_INTEGER);
const expiresAtMs = integer(value.expiresAtMs, 0, Number.MAX_SAFE_INTEGER);
const endpoint = normalizeModelProviderCredentialTestEndpoint(value.endpoint);
const provider = identifier(value.provider);
if (
endpoint.provider !== provider ||
expiresAtMs <= plannedAtMs ||
expiresAtMs - plannedAtMs >
MAX_MODEL_PROVIDER_CREDENTIAL_TEST_PLAN_LIFETIME_MS
) {
invalid();
}
const semantic = planSemantic({
schema: MODEL_PROVIDER_CREDENTIAL_TEST_PLAN_SCHEMA,
testId: uuid(value.testId),
requestId: identifier(value.requestId, true),
projectId: identifier(value.projectId),
provider,
endpoint,
requestedBy: user(value.requestedBy),
fence: fence(value.fence),
plannedAtMs,
expiresAtMs,
});
return Object.freeze({
...semantic,
planDigest: sha256(PLAN_DIGEST_DOMAIN, semantic),
});
}
export function normalizeModelProviderCredentialTestPlan(
value: ModelProviderCredentialTestPlan,
): Readonly<ModelProviderCredentialTestPlan> {
exact(value, [
'endpoint',
'expiresAtMs',
'fence',
'planDigest',
'plannedAtMs',
'projectId',
'provider',
'requestId',
'requestedBy',
'schema',
'testId',
]);
if (value.schema !== MODEL_PROVIDER_CREDENTIAL_TEST_PLAN_SCHEMA) invalid();
const normalized = createModelProviderCredentialTestPlan({
testId: value.testId,
requestId: value.requestId,
projectId: value.projectId,
provider: value.provider,
endpoint: value.endpoint,
requestedBy: value.requestedBy,
fence: value.fence,
plannedAtMs: value.plannedAtMs,
expiresAtMs: value.expiresAtMs,
});
if (digest(value.planDigest) !== normalized.planDigest) invalid();
return normalized;
}
@@ -0,0 +1,244 @@
import { normalizeProjectPolicySubject } from '@qinglong/runtime-core/project-policy';
import type {
SecurityPolicyFence,
SecuritySubject,
} from '@qinglong/runtime-core/security';
import {
normalizeSecurityAuditRecord,
type SecurityAuditRecord,
} from '@qinglong/runtime-core/security-audit';
import {
normalizeModelProviderCredentialTransitionCommand,
type CommitModelProviderCredentialTransitionResult,
type ModelProviderCredentialCatalogRepository,
type ModelProviderCredentialTransition,
type ModelProviderCredentialTransitionCommand,
} from './modelProviderCredentialCatalog';
export interface AuthorizedModelProviderCredentialTransitionMutation {
readonly command: ModelProviderCredentialTransitionCommand;
readonly actor: SecuritySubject;
readonly fence: SecurityPolicyFence;
readonly audit: SecurityAuditRecord;
}
export interface ModelProviderCredentialAdministrationRepository
extends ModelProviderCredentialCatalogRepository {
commitAuthorized(
mutation: AuthorizedModelProviderCredentialTransitionMutation,
): Promise<Readonly<CommitModelProviderCredentialTransitionResult>>;
}
export const MODEL_PROVIDER_CREDENTIAL_INSPECTION_OPERATION_ID =
'model_provider_credential.inspect' as const;
export interface AuthorizedModelProviderCredentialInspection {
readonly projectId: string;
readonly provider: string;
readonly actor: SecuritySubject;
readonly fence: SecurityPolicyFence;
readonly audit: SecurityAuditRecord;
}
export interface ModelProviderCredentialAdministrationInspectionRepository {
inspectAuthorized(
inspection: AuthorizedModelProviderCredentialInspection,
): Promise<Readonly<ModelProviderCredentialTransition> | null>;
}
export class InvalidModelProviderCredentialAdministrationMutationError extends TypeError {
readonly code = 'MODEL_PROVIDER_CREDENTIAL_ADMINISTRATION_MUTATION_INVALID';
constructor(message: string) {
super(
`Model provider credential administration mutation is invalid: ${message}`,
);
this.name = 'InvalidModelProviderCredentialAdministrationMutationError';
}
}
export class ModelProviderCredentialAdministrationAuthorizationFenceConflictError extends Error {
readonly code =
'MODEL_PROVIDER_CREDENTIAL_ADMINISTRATION_AUTHORIZATION_FENCE_CONFLICT';
constructor() {
super(
'Model provider credential administration authorization fence changed',
);
this.name =
'ModelProviderCredentialAdministrationAuthorizationFenceConflictError';
}
}
export class ModelProviderCredentialAdministrationMutationConflictError extends Error {
readonly code = 'MODEL_PROVIDER_CREDENTIAL_ADMINISTRATION_MUTATION_CONFLICT';
constructor() {
super(
'Model provider credential administration mutation conflicts with durable state',
);
this.name = 'ModelProviderCredentialAdministrationMutationConflictError';
}
}
function exactKeys(value: object, expected: readonly string[]): boolean {
const actual = Object.keys(value).sort();
const canonical = [...expected].sort();
return (
actual.length === canonical.length &&
actual.every((key, index) => key === canonical[index])
);
}
function sameSubject(
left: Readonly<SecuritySubject>,
right: Readonly<SecuritySubject>,
): boolean {
return left.type === right.type && left.id === right.id;
}
const IDENTITY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
function identity(value: unknown, label: string): string {
if (typeof value !== 'string' || !IDENTITY_PATTERN.test(value)) {
throw new InvalidModelProviderCredentialAdministrationMutationError(
`${label} is invalid`,
);
}
return value;
}
function normalizeFence(
value: SecurityPolicyFence,
): Readonly<SecurityPolicyFence> {
if (
!value ||
typeof value !== 'object' ||
Array.isArray(value) ||
!exactKeys(value, ['bindingVersion', 'projectVersion']) ||
!Number.isSafeInteger(value.projectVersion) ||
value.projectVersion < 1 ||
!Number.isSafeInteger(value.bindingVersion) ||
(value.bindingVersion as number) < 1
) {
throw new InvalidModelProviderCredentialAdministrationMutationError(
'authorization fence is invalid',
);
}
return Object.freeze({
projectVersion: value.projectVersion,
bindingVersion: value.bindingVersion,
});
}
export function modelProviderCredentialAdministrationOperationId(
action: ModelProviderCredentialTransitionCommand['action'],
): 'model_provider_credential.bind' | 'model_provider_credential.revoke' {
if (action === 'bind') return 'model_provider_credential.bind';
if (action === 'revoke') return 'model_provider_credential.revoke';
throw new InvalidModelProviderCredentialAdministrationMutationError(
'action is invalid',
);
}
export function normalizeAuthorizedModelProviderCredentialTransitionMutation(
value: AuthorizedModelProviderCredentialTransitionMutation,
): Readonly<AuthorizedModelProviderCredentialTransitionMutation> {
if (
!value ||
typeof value !== 'object' ||
Array.isArray(value) ||
!exactKeys(value, ['actor', 'audit', 'command', 'fence'])
) {
throw new InvalidModelProviderCredentialAdministrationMutationError(
'mutation shape is invalid',
);
}
try {
const command = normalizeModelProviderCredentialTransitionCommand(
value.command,
);
const actor = normalizeProjectPolicySubject(value.actor);
const fence = normalizeFence(value.fence);
const audit = normalizeSecurityAuditRecord(value.audit);
if (
command.changedBy.type !== 'user' ||
!sameSubject(command.changedBy, actor) ||
actor.type !== 'user' ||
audit.eventId !== command.mutationId ||
audit.operationId !==
modelProviderCredentialAdministrationOperationId(command.action) ||
audit.projectId !== command.projectId ||
audit.outcome !== 'allowed' ||
!audit.subject ||
!sameSubject(audit.subject, actor) ||
audit.authenticationId === null ||
!audit.fence ||
audit.fence.projectVersion !== fence.projectVersion ||
audit.fence.bindingVersion !== fence.bindingVersion
) {
throw new InvalidModelProviderCredentialAdministrationMutationError(
'audit binding is invalid',
);
}
return Object.freeze({ command, actor, fence, audit });
} catch (error) {
if (
error instanceof InvalidModelProviderCredentialAdministrationMutationError
) {
throw error;
}
throw new InvalidModelProviderCredentialAdministrationMutationError(
'mutation value is invalid',
);
}
}
export function normalizeAuthorizedModelProviderCredentialInspection(
value: AuthorizedModelProviderCredentialInspection,
): Readonly<AuthorizedModelProviderCredentialInspection> {
if (
!value ||
typeof value !== 'object' ||
Array.isArray(value) ||
!exactKeys(value, ['actor', 'audit', 'fence', 'projectId', 'provider'])
) {
throw new InvalidModelProviderCredentialAdministrationMutationError(
'inspection shape is invalid',
);
}
try {
const projectId = identity(value.projectId, 'projectId');
const provider = identity(value.provider, 'provider');
const actor = normalizeProjectPolicySubject(value.actor);
const fence = normalizeFence(value.fence);
const audit = normalizeSecurityAuditRecord(value.audit);
if (
actor.type !== 'user' ||
audit.operationId !== MODEL_PROVIDER_CREDENTIAL_INSPECTION_OPERATION_ID ||
audit.projectId !== projectId ||
audit.outcome !== 'allowed' ||
!audit.subject ||
!sameSubject(audit.subject, actor) ||
audit.authenticationId === null ||
!audit.fence ||
audit.fence.projectVersion !== fence.projectVersion ||
audit.fence.bindingVersion !== fence.bindingVersion
) {
throw new InvalidModelProviderCredentialAdministrationMutationError(
'inspection audit binding is invalid',
);
}
return Object.freeze({ projectId, provider, actor, fence, audit });
} catch (error) {
if (
error instanceof InvalidModelProviderCredentialAdministrationMutationError
) {
throw error;
}
throw new InvalidModelProviderCredentialAdministrationMutationError(
'inspection value is invalid',
);
}
}
@@ -0,0 +1,480 @@
import { createHash } from 'node:crypto';
import {
SECURITY_SUBJECT_TYPES,
type SecuritySubject,
} from '@qinglong/runtime-core/security';
import {
MODEL_PROVIDER_CREDENTIAL_BINDING_SCHEMA,
digestModelProviderCredentialBinding,
normalizeModelProviderCredentialBinding,
type ModelProviderCredentialBinding,
} from './providerCredential';
export const MODEL_PROVIDER_CREDENTIAL_TRANSITION_COMMAND_SCHEMA =
'qinglong/model-provider-credential-transition-command@v1';
export const MODEL_PROVIDER_CREDENTIAL_TRANSITION_SCHEMA =
'qinglong/model-provider-credential-transition@v1';
export interface ModelProviderCredentialTransitionCommand {
readonly schema: typeof MODEL_PROVIDER_CREDENTIAL_TRANSITION_COMMAND_SCHEMA;
readonly mutationId: string;
readonly projectId: string;
readonly provider: string;
readonly expectedGeneration: number;
readonly action: 'bind' | 'revoke';
readonly binding: Readonly<ModelProviderCredentialBinding> | null;
readonly changedBy: Readonly<SecuritySubject>;
readonly commandDigest: string;
}
export interface ModelProviderCredentialTransition {
readonly schema: typeof MODEL_PROVIDER_CREDENTIAL_TRANSITION_SCHEMA;
readonly mutationId: string;
readonly projectId: string;
readonly provider: string;
readonly generation: number;
readonly action: 'bind' | 'revoke';
readonly activeBindingRevision: string | null;
readonly activeBindingDigest: string | null;
readonly previousTransitionDigest: string | null;
readonly changedBy: Readonly<SecuritySubject>;
readonly changedAtMs: number;
readonly commandDigest: string;
readonly transitionDigest: string;
}
export interface CommitModelProviderCredentialTransitionResult {
readonly status: 'created' | 'existing';
readonly transition: Readonly<ModelProviderCredentialTransition>;
}
export interface ModelProviderCredentialCatalogRepository {
findCurrentTransition(
projectId: string,
provider: string,
): Promise<Readonly<ModelProviderCredentialTransition> | null>;
commit(
command: Readonly<ModelProviderCredentialTransitionCommand>,
): Promise<Readonly<CommitModelProviderCredentialTransitionResult>>;
}
export class InvalidModelProviderCredentialTransitionError extends TypeError {
readonly code = 'MODEL_PROVIDER_CREDENTIAL_TRANSITION_INVALID';
constructor(message: string) {
super(`Model provider credential transition is invalid: ${message}`);
this.name = 'InvalidModelProviderCredentialTransitionError';
}
}
export class ModelProviderCredentialTransitionConflictError extends Error {
readonly code = 'MODEL_PROVIDER_CREDENTIAL_TRANSITION_CONFLICT';
constructor() {
super(
'The model provider credential transition conflicts with durable state',
);
this.name = 'ModelProviderCredentialTransitionConflictError';
}
}
export class ModelProviderCredentialCatalogUnavailableError extends Error {
readonly code = 'MODEL_PROVIDER_CREDENTIAL_CATALOG_UNAVAILABLE';
constructor(options?: ErrorOptions) {
super('The model provider credential catalog is unavailable', options);
this.name = 'ModelProviderCredentialCatalogUnavailableError';
}
}
const IDENTITY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
const PROVIDER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
const DIGEST_PATTERN = /^[0-9a-f]{64}$/;
function exactObject(
value: unknown,
expectedKeys: readonly string[],
label: string,
): Record<string, unknown> {
if (
!value ||
typeof value !== 'object' ||
Array.isArray(value) ||
Object.keys(value).sort().join('\0') !== [...expectedKeys].sort().join('\0')
) {
throw new InvalidModelProviderCredentialTransitionError(
`${label} shape is invalid`,
);
}
return value as Record<string, unknown>;
}
function identity(value: unknown, label: string): string {
if (typeof value !== 'string' || !IDENTITY_PATTERN.test(value)) {
throw new InvalidModelProviderCredentialTransitionError(
`${label} is invalid`,
);
}
return value;
}
function providerIdentity(value: unknown): string {
if (typeof value !== 'string' || !PROVIDER_PATTERN.test(value)) {
throw new InvalidModelProviderCredentialTransitionError(
'provider is invalid',
);
}
return value;
}
function generation(value: unknown, minimum: number): number {
if (!Number.isSafeInteger(value) || (value as number) < minimum) {
throw new InvalidModelProviderCredentialTransitionError(
'generation is invalid',
);
}
return value as number;
}
function timestamp(value: unknown): number {
if (!Number.isSafeInteger(value) || (value as number) < 0) {
throw new InvalidModelProviderCredentialTransitionError(
'changedAtMs is invalid',
);
}
return value as number;
}
function nullableDigest(value: unknown, label: string): string | null {
if (value === null) return null;
if (typeof value !== 'string' || !DIGEST_PATTERN.test(value)) {
throw new InvalidModelProviderCredentialTransitionError(
`${label} is invalid`,
);
}
return value;
}
function subject(value: unknown): Readonly<SecuritySubject> {
const candidate = exactObject(value, ['id', 'type'], 'changedBy');
if (
!SECURITY_SUBJECT_TYPES.includes(candidate.type as SecuritySubject['type'])
) {
throw new InvalidModelProviderCredentialTransitionError(
'changedBy type is invalid',
);
}
return Object.freeze({
type: candidate.type as SecuritySubject['type'],
id: identity(candidate.id, 'changedBy id'),
});
}
function digest(label: string, value: object): string {
return createHash('sha256')
.update(label, 'utf8')
.update('\0', 'utf8')
.update(JSON.stringify(value), 'utf8')
.digest('hex');
}
function commandDigestInput(
command: Omit<ModelProviderCredentialTransitionCommand, 'commandDigest'>,
): object {
return {
schema: command.schema,
mutationId: command.mutationId,
projectId: command.projectId,
provider: command.provider,
expectedGeneration: command.expectedGeneration,
action: command.action,
binding: command.binding,
changedBy: command.changedBy,
};
}
export function createModelProviderCredentialTransitionCommand(
value: Omit<ModelProviderCredentialTransitionCommand, 'commandDigest'>,
): Readonly<ModelProviderCredentialTransitionCommand> {
const candidate = exactObject(
value,
[
'action',
'binding',
'changedBy',
'expectedGeneration',
'mutationId',
'projectId',
'provider',
'schema',
],
'command',
);
if (
candidate.schema !== MODEL_PROVIDER_CREDENTIAL_TRANSITION_COMMAND_SCHEMA
) {
throw new InvalidModelProviderCredentialTransitionError(
'command schema is invalid',
);
}
const projectId = identity(candidate.projectId, 'projectId');
const provider = providerIdentity(candidate.provider);
const action = candidate.action;
if (action !== 'bind' && action !== 'revoke') {
throw new InvalidModelProviderCredentialTransitionError(
'action is invalid',
);
}
let binding: Readonly<ModelProviderCredentialBinding> | null = null;
if (action === 'bind') {
if (!candidate.binding) {
throw new InvalidModelProviderCredentialTransitionError(
'bind requires a binding',
);
}
try {
binding = normalizeModelProviderCredentialBinding(
candidate.binding as ModelProviderCredentialBinding,
);
} catch {
throw new InvalidModelProviderCredentialTransitionError(
'binding is invalid',
);
}
if (binding.projectId !== projectId || binding.provider !== provider) {
throw new InvalidModelProviderCredentialTransitionError(
'binding identity does not match the command',
);
}
} else if (candidate.binding !== null) {
throw new InvalidModelProviderCredentialTransitionError(
'revoke cannot contain a binding',
);
}
const command = Object.freeze({
schema: MODEL_PROVIDER_CREDENTIAL_TRANSITION_COMMAND_SCHEMA,
mutationId: identity(candidate.mutationId, 'mutationId'),
projectId,
provider,
expectedGeneration: generation(candidate.expectedGeneration, 0),
action,
binding,
changedBy: subject(candidate.changedBy),
});
return Object.freeze({
...command,
commandDigest: digest(
'qinglong/model-provider-credential-transition-command@v1',
commandDigestInput(command),
),
});
}
export function normalizeModelProviderCredentialTransitionCommand(
value: ModelProviderCredentialTransitionCommand,
): Readonly<ModelProviderCredentialTransitionCommand> {
const candidate = exactObject(
value,
[
'action',
'binding',
'changedBy',
'commandDigest',
'expectedGeneration',
'mutationId',
'projectId',
'provider',
'schema',
],
'command',
);
const normalized = createModelProviderCredentialTransitionCommand({
schema:
candidate.schema as typeof MODEL_PROVIDER_CREDENTIAL_TRANSITION_COMMAND_SCHEMA,
mutationId: candidate.mutationId as string,
projectId: candidate.projectId as string,
provider: candidate.provider as string,
expectedGeneration: candidate.expectedGeneration as number,
action: candidate.action as 'bind' | 'revoke',
binding: candidate.binding as ModelProviderCredentialBinding | null,
changedBy: candidate.changedBy as SecuritySubject,
});
if (candidate.commandDigest !== normalized.commandDigest) {
throw new InvalidModelProviderCredentialTransitionError(
'commandDigest is invalid',
);
}
return normalized;
}
function transitionDigestInput(
transition: Omit<ModelProviderCredentialTransition, 'transitionDigest'>,
): object {
return {
schema: transition.schema,
mutationId: transition.mutationId,
projectId: transition.projectId,
provider: transition.provider,
generation: transition.generation,
action: transition.action,
activeBindingRevision: transition.activeBindingRevision,
activeBindingDigest: transition.activeBindingDigest,
previousTransitionDigest: transition.previousTransitionDigest,
changedBy: transition.changedBy,
changedAtMs: transition.changedAtMs,
commandDigest: transition.commandDigest,
};
}
export function createModelProviderCredentialTransition(
commandValue: ModelProviderCredentialTransitionCommand,
previous: Readonly<ModelProviderCredentialTransition> | null,
changedAtMs: number,
): Readonly<ModelProviderCredentialTransition> {
const command =
normalizeModelProviderCredentialTransitionCommand(commandValue);
const currentGeneration = previous?.generation ?? 0;
if (
command.expectedGeneration !== currentGeneration ||
(previous !== null &&
(previous.projectId !== command.projectId ||
previous.provider !== command.provider))
) {
throw new ModelProviderCredentialTransitionConflictError();
}
const transition = Object.freeze({
schema: MODEL_PROVIDER_CREDENTIAL_TRANSITION_SCHEMA,
mutationId: command.mutationId,
projectId: command.projectId,
provider: command.provider,
generation: currentGeneration + 1,
action: command.action,
activeBindingRevision: command.binding?.revision ?? null,
activeBindingDigest:
command.binding === null
? null
: digestModelProviderCredentialBinding(command.binding).slice(7),
previousTransitionDigest: previous?.transitionDigest ?? null,
changedBy: command.changedBy,
changedAtMs: timestamp(changedAtMs),
commandDigest: command.commandDigest,
});
return Object.freeze({
...transition,
transitionDigest: digest(
'qinglong/model-provider-credential-transition@v1',
transitionDigestInput(transition),
),
});
}
export function normalizeModelProviderCredentialTransition(
value: ModelProviderCredentialTransition,
): Readonly<ModelProviderCredentialTransition> {
const candidate = exactObject(
value,
[
'action',
'activeBindingDigest',
'activeBindingRevision',
'changedAtMs',
'changedBy',
'commandDigest',
'generation',
'mutationId',
'previousTransitionDigest',
'projectId',
'provider',
'schema',
'transitionDigest',
],
'transition',
);
if (candidate.schema !== MODEL_PROVIDER_CREDENTIAL_TRANSITION_SCHEMA) {
throw new InvalidModelProviderCredentialTransitionError(
'transition schema is invalid',
);
}
const action = candidate.action;
if (action !== 'bind' && action !== 'revoke') {
throw new InvalidModelProviderCredentialTransitionError(
'transition action is invalid',
);
}
const activeBindingRevision =
candidate.activeBindingRevision === null
? null
: identity(candidate.activeBindingRevision, 'active binding revision');
const activeBindingDigest = nullableDigest(
candidate.activeBindingDigest,
'active binding digest',
);
if (
(action === 'bind' &&
(activeBindingRevision === null || activeBindingDigest === null)) ||
(action === 'revoke' &&
(activeBindingRevision !== null || activeBindingDigest !== null))
) {
throw new InvalidModelProviderCredentialTransitionError(
'transition binding state is invalid',
);
}
const normalized = Object.freeze({
schema: MODEL_PROVIDER_CREDENTIAL_TRANSITION_SCHEMA,
mutationId: identity(candidate.mutationId, 'mutationId'),
projectId: identity(candidate.projectId, 'projectId'),
provider: providerIdentity(candidate.provider),
generation: generation(candidate.generation, 1),
action,
activeBindingRevision,
activeBindingDigest,
previousTransitionDigest: nullableDigest(
candidate.previousTransitionDigest,
'previous transition digest',
),
changedBy: subject(candidate.changedBy),
changedAtMs: timestamp(candidate.changedAtMs),
commandDigest: nullableDigest(candidate.commandDigest, 'command digest')!,
});
const expectedDigest = digest(
'qinglong/model-provider-credential-transition@v1',
transitionDigestInput(normalized),
);
if (candidate.transitionDigest !== expectedDigest) {
throw new InvalidModelProviderCredentialTransitionError(
'transitionDigest is invalid',
);
}
return Object.freeze({ ...normalized, transitionDigest: expectedDigest });
}
export function modelProviderCredentialBindingForTransition(
transitionValue: ModelProviderCredentialTransition,
bindingValue: ModelProviderCredentialBinding | null,
): Readonly<ModelProviderCredentialBinding> | null {
const transition =
normalizeModelProviderCredentialTransition(transitionValue);
if (transition.action === 'revoke') {
if (bindingValue !== null) {
throw new ModelProviderCredentialCatalogUnavailableError();
}
return null;
}
if (bindingValue === null) {
throw new ModelProviderCredentialCatalogUnavailableError();
}
const binding = normalizeModelProviderCredentialBinding(bindingValue);
if (
binding.schema !== MODEL_PROVIDER_CREDENTIAL_BINDING_SCHEMA ||
binding.projectId !== transition.projectId ||
binding.provider !== transition.provider ||
binding.revision !== transition.activeBindingRevision ||
digestModelProviderCredentialBinding(binding).slice(7) !==
transition.activeBindingDigest
) {
throw new ModelProviderCredentialCatalogUnavailableError();
}
return binding;
}
@@ -0,0 +1,38 @@
// Stable Model Provider Credential Test Connection facade.
export {
InvalidModelProviderCredentialTestConnectionError,
MAX_MODEL_PROVIDER_CREDENTIAL_TEST_ALLOWLIST_ENTRIES,
MAX_MODEL_PROVIDER_CREDENTIAL_TEST_DEADLINE_MS,
MAX_MODEL_PROVIDER_CREDENTIAL_TEST_MODELS,
MAX_MODEL_PROVIDER_CREDENTIAL_TEST_PLAN_LIFETIME_MS,
MAX_MODEL_PROVIDER_CREDENTIAL_TEST_RESPONSE_BYTES,
MODEL_PROVIDER_CREDENTIAL_TEST_ALLOWLIST_SCHEMA,
MODEL_PROVIDER_CREDENTIAL_TEST_EXECUTION_SCHEMA,
MODEL_PROVIDER_CREDENTIAL_TEST_PLAN_SCHEMA,
MODEL_PROVIDER_CREDENTIAL_TEST_RESULT_SCHEMA,
type CreateModelProviderCredentialTestPlanInput,
type ModelProviderCredentialTestAllowlist,
type ModelProviderCredentialTestEndpoint,
type ModelProviderCredentialTestEndpointInput,
type ModelProviderCredentialTestExecution,
type ModelProviderCredentialTestOutcome,
type ModelProviderCredentialTestPlan,
type ModelProviderCredentialTestResult,
} from './model-provider-credential-test-connection-protocol/contractProtocol';
export {
createModelProviderCredentialTestAllowlist,
createModelProviderCredentialTestEndpoint,
normalizeModelProviderCredentialTestAllowlist,
normalizeModelProviderCredentialTestEndpoint,
resolveModelProviderCredentialTestEndpoint,
} from './model-provider-credential-test-connection-protocol/endpointAllowlistProtocol';
export {
createModelProviderCredentialTestExecution,
createModelProviderCredentialTestResult,
normalizeModelProviderCredentialTestExecution,
normalizeModelProviderCredentialTestResult,
} from './model-provider-credential-test-connection-protocol/executionResultProtocol';
export {
createModelProviderCredentialTestPlan,
normalizeModelProviderCredentialTestPlan,
} from './model-provider-credential-test-connection-protocol/planProtocol';
@@ -0,0 +1,172 @@
import type { PostgresClient } from '@qinglong/runtime-core';
import {
normalizeSecurityAuditRecord,
type SecurityAuditRecord,
} from '@qinglong/runtime-core/security-audit';
import {
ModelProviderCredentialAdministrationAuthorizationFenceConflictError,
ModelProviderCredentialAdministrationMutationConflictError,
type AuthorizedModelProviderCredentialTransitionMutation,
} from '../modelProviderCredentialAdministration';
import { ModelProviderCredentialCatalogUnavailableError } from '../modelProviderCredentialCatalog';
import {
identity,
integer,
sqlState,
unavailable,
type Queryable,
type Row,
} from './storageProtocol';
export function administrationAuditFromRow(
row: Row,
): Readonly<SecurityAuditRecord> {
const reasons = row.auditReasons;
if (!Array.isArray(reasons)) throw unavailable();
const subjectType = row.auditSubjectType;
const subjectId = row.auditSubjectId;
const authenticationId = row.auditAuthenticationId;
const projectVersion = row.auditProjectVersion;
const bindingVersion = row.auditBindingVersion;
try {
return normalizeSecurityAuditRecord({
eventId: identity(row.auditEventId),
requestId: identity(row.auditRequestId),
operationId: identity(row.auditOperationId),
projectId: identity(row.auditProjectId),
subject: {
type: identity(subjectType) as NonNullable<
SecurityAuditRecord['subject']
>['type'],
id: identity(subjectId),
},
authenticationId: identity(authenticationId),
outcome: identity(row.auditOutcome) as SecurityAuditRecord['outcome'],
reasons: reasons as string[],
fence: {
projectVersion: integer(projectVersion),
bindingVersion: integer(bindingVersion),
},
occurredAtMs: integer(row.auditOccurredAtMs),
});
} catch (cause) {
throw unavailable(cause);
}
}
export async function administrationAuditRows(
queryable: Queryable,
eventId: string,
): Promise<readonly Row[]> {
const result = await queryable.query<Row>(
`SELECT
event_id AS "auditEventId",
request_id AS "auditRequestId",
operation_id AS "auditOperationId",
project_id AS "auditProjectId",
subject_type AS "auditSubjectType",
subject_id AS "auditSubjectId",
authentication_id AS "auditAuthenticationId",
outcome AS "auditOutcome",
reasons AS "auditReasons",
project_version AS "auditProjectVersion",
binding_version AS "auditBindingVersion",
occurred_at_ms AS "auditOccurredAtMs"
FROM "ql3"."security_audit_events"
WHERE event_id = $1
LIMIT 2`,
[eventId],
);
return result.rows;
}
export function sameAdministrationReplayAudit(
left: Readonly<SecurityAuditRecord>,
right: Readonly<SecurityAuditRecord>,
): boolean {
const { occurredAtMs: _leftOccurredAtMs, ...leftSemantic } = left;
const { occurredAtMs: _rightOccurredAtMs, ...rightSemantic } = right;
return JSON.stringify(leftSemantic) === JSON.stringify(rightSemantic);
}
export async function confirmAdministrationFence(
client: PostgresClient,
mutation: Readonly<AuthorizedModelProviderCredentialTransitionMutation>,
): Promise<void> {
try {
const project = await client.query<Row>(
`SELECT status, version FROM "ql3"."projects" WHERE id = $1`,
[mutation.command.projectId],
);
const binding = await client.query<Row>(
`SELECT version, state
FROM "ql3"."project_role_bindings"
WHERE project_id = $1 AND subject_type = $2 AND subject_id = $3
ORDER BY version DESC
LIMIT 1`,
[mutation.command.projectId, mutation.actor.type, mutation.actor.id],
);
if (
project.rows.length !== 1 ||
project.rows[0]?.status !== 'active' ||
integer(project.rows[0]?.version) !== mutation.fence.projectVersion ||
binding.rows.length !== 1 ||
binding.rows[0]?.state !== 'active' ||
integer(binding.rows[0]?.version) !== mutation.fence.bindingVersion
) {
throw new ModelProviderCredentialAdministrationAuthorizationFenceConflictError();
}
} catch (error) {
if (
error instanceof
ModelProviderCredentialAdministrationAuthorizationFenceConflictError
) {
throw error;
}
throw new ModelProviderCredentialAdministrationAuthorizationFenceConflictError();
}
}
export async function insertAdministrationAudit(
client: PostgresClient,
audit: Readonly<SecurityAuditRecord>,
): Promise<void> {
await client.query(
`INSERT INTO "ql3"."security_audit_events" (
event_id, request_id, operation_id, project_id, subject_type,
subject_id, authentication_id, outcome, reasons, project_version,
binding_version, occurred_at_ms
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::jsonb, $10, $11, $12)`,
[
audit.eventId,
audit.requestId,
audit.operationId,
audit.projectId,
audit.subject?.type ?? null,
audit.subject?.id ?? null,
audit.authenticationId,
audit.outcome,
JSON.stringify(audit.reasons),
audit.fence?.projectVersion ?? null,
audit.fence?.bindingVersion ?? null,
audit.occurredAtMs,
],
);
}
export function mapAdministrationStorageError(error: unknown): Error {
if (
error instanceof
ModelProviderCredentialAdministrationAuthorizationFenceConflictError ||
error instanceof
ModelProviderCredentialAdministrationMutationConflictError ||
error instanceof ModelProviderCredentialCatalogUnavailableError
) {
return error;
}
if (['23503', '23505', '23514', '40001', '40P01'].includes(sqlState(error))) {
return new ModelProviderCredentialAdministrationMutationConflictError();
}
return unavailable(error);
}
@@ -0,0 +1,211 @@
import type { PostgresPool } from '@qinglong/runtime-core';
import { POSTGRES_MODEL_INVOCATION_SCHEMA } from '../../migration/modelInvocationMigration';
import {
modelProviderCredentialBindingForTransition,
type ModelProviderCredentialTransition,
} from '../modelProviderCredentialCatalog';
import {
type ModelProviderCredentialAuditRecord,
type ModelProviderCredentialAuditSink,
type ModelProviderCredentialBinding,
type ModelProviderCredentialBindingLookup,
type ModelProviderCredentialBindingSource,
} from '../providerCredential';
import {
PROVIDER_PATTERN,
auditDigest,
bindingFromRow,
identity,
integer,
jsonObject,
mapStorageError,
normalizeAuditRecord,
normalizeLookup,
transitionFromRow,
unavailable,
type Queryable,
type Row,
} from './storageProtocol';
/**
* PostgreSQL runtime reader plus append-only content-free credential audit.
* Administrative binding mutations are exposed by the subclass so runtime
* composition never receives mutation authority.
*/
export class PostgresModelProviderCredentialReader
implements
ModelProviderCredentialBindingSource,
ModelProviderCredentialAuditSink
{
constructor(protected readonly pool: PostgresPool) {
if (
!pool ||
typeof pool.query !== 'function' ||
typeof pool.connect !== 'function'
) {
throw new TypeError(
'PostgreSQL model provider credential pool is invalid',
);
}
}
protected async transitionRows(
queryable: Queryable,
where: string,
values: readonly unknown[],
): Promise<readonly Row[]> {
const result = await queryable.query<Row>(
`SELECT
project_id AS "projectId",
provider,
generation,
mutation_id AS "mutationId",
command_digest AS "commandDigest",
transition_digest AS "transitionDigest",
transition_json AS "transitionJson"
FROM "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_transitions"
WHERE ${where}
ORDER BY generation DESC
LIMIT 2`,
values,
);
return result.rows;
}
async findCurrentTransition(
projectIdValue: string,
providerValue: string,
): Promise<Readonly<ModelProviderCredentialTransition> | null> {
const projectId = identity(projectIdValue);
const provider = identity(providerValue, PROVIDER_PATTERN);
try {
const rows = await this.transitionRows(
this.pool,
'project_id = $1 AND provider = $2',
[projectId, provider],
);
if (
rows.length > 1 &&
integer(rows[0]!.generation) === integer(rows[1]!.generation)
) {
throw unavailable();
}
return rows[0] ? transitionFromRow(rows[0]) : null;
} catch (error) {
throw mapStorageError(error);
}
}
async resolveModelProviderCredentialBinding(
lookupValue: Readonly<ModelProviderCredentialBindingLookup>,
): Promise<Readonly<ModelProviderCredentialBinding> | null> {
const lookup = normalizeLookup(lookupValue);
try {
const result = await this.pool.query<Row>(
`SELECT
transition.project_id AS "projectId",
transition.provider,
transition.generation,
transition.mutation_id AS "mutationId",
transition.command_digest AS "commandDigest",
transition.transition_digest AS "transitionDigest",
transition.transition_json AS "transitionJson",
binding.project_id AS "bindingProjectId",
binding.provider AS "bindingProvider",
binding.revision,
binding.secret_ref AS "secretRef",
binding.scheme,
binding.binding_digest AS "bindingDigest",
binding.binding_json AS "bindingJson"
FROM "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_transitions"
AS transition
LEFT JOIN "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_bindings"
AS binding
ON binding.project_id = transition.project_id
AND binding.provider = transition.provider
AND binding.revision = transition.active_binding_revision
WHERE transition.project_id = $1
AND transition.provider = $2
ORDER BY transition.generation DESC
LIMIT 1`,
[lookup.projectId, lookup.provider],
);
if (result.rows.length !== 1) return null;
const row = result.rows[0]!;
const transition = transitionFromRow(row);
if (transition.action === 'revoke') {
return modelProviderCredentialBindingForTransition(transition, null);
}
if (
row.bindingProjectId !== transition.projectId ||
row.bindingProvider !== transition.provider
) {
throw unavailable();
}
return modelProviderCredentialBindingForTransition(
transition,
bindingFromRow(row),
);
} catch (error) {
throw mapStorageError(error);
}
}
async record(
recordValue: Readonly<ModelProviderCredentialAuditRecord>,
): Promise<void> {
const record = normalizeAuditRecord(recordValue);
const digest = auditDigest(record);
try {
const inserted = await this.pool.query<Row>(
`INSERT INTO "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_audits" (
project_id, provider, request_id, operation, binding_revision,
binding_digest, occurred_at_ms, audit_digest, audit_json
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::jsonb)
ON CONFLICT (project_id, provider, request_id, operation) DO NOTHING
RETURNING audit_digest AS "auditDigest"`,
[
record.projectId,
record.provider,
record.requestId,
record.operation,
record.bindingRevision,
record.bindingDigest,
record.occurredAtMs,
digest,
JSON.stringify(record),
],
);
if (
inserted.rows.length === 1 &&
inserted.rows[0]?.auditDigest === digest
) {
return;
}
const existing = await this.pool.query<Row>(
`SELECT audit_digest AS "auditDigest", audit_json AS "auditJson"
FROM "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_audits"
WHERE project_id = $1 AND provider = $2
AND request_id = $3 AND operation = $4
LIMIT 2`,
[record.projectId, record.provider, record.requestId, record.operation],
);
if (
existing.rows.length !== 1 ||
existing.rows[0]?.auditDigest !== digest ||
JSON.stringify(
normalizeAuditRecord(
jsonObject(
existing.rows[0]?.auditJson,
) as unknown as ModelProviderCredentialAuditRecord,
),
) !== JSON.stringify(record)
) {
throw unavailable();
}
} catch (error) {
throw mapStorageError(error);
}
}
}
@@ -0,0 +1,282 @@
import type { PostgresClient } from '@qinglong/runtime-core';
import type { SecurityAuditRecord } from '@qinglong/runtime-core/security-audit';
import { POSTGRES_MODEL_INVOCATION_SCHEMA } from '../../migration/modelInvocationMigration';
import {
ModelProviderCredentialAdministrationMutationConflictError,
normalizeAuthorizedModelProviderCredentialTransitionMutation,
type AuthorizedModelProviderCredentialTransitionMutation,
type ModelProviderCredentialAdministrationRepository,
} from '../modelProviderCredentialAdministration';
import {
ModelProviderCredentialTransitionConflictError,
createModelProviderCredentialTransition,
normalizeModelProviderCredentialTransitionCommand,
type CommitModelProviderCredentialTransitionResult,
type ModelProviderCredentialCatalogRepository,
type ModelProviderCredentialTransitionCommand,
} from '../modelProviderCredentialCatalog';
import { digestModelProviderCredentialBinding } from '../providerCredential';
import {
administrationAuditFromRow,
administrationAuditRows,
confirmAdministrationFence,
insertAdministrationAudit,
mapAdministrationStorageError,
sameAdministrationReplayAudit,
} from './administrationProtocol';
import { PostgresModelProviderCredentialReader } from './reader';
import {
integer,
mapStorageError,
rollback,
transitionFromRow,
unavailable,
type Row,
} from './storageProtocol';
export class PostgresModelProviderCredentialRepository
extends PostgresModelProviderCredentialReader
implements
ModelProviderCredentialCatalogRepository,
ModelProviderCredentialAdministrationRepository
{
async commit(
commandValue: Readonly<ModelProviderCredentialTransitionCommand>,
): Promise<Readonly<CommitModelProviderCredentialTransitionResult>> {
const command =
normalizeModelProviderCredentialTransitionCommand(commandValue);
let client: PostgresClient;
try {
client = await this.pool.connect();
} catch (error) {
throw unavailable(error);
}
try {
await client.query('BEGIN ISOLATION LEVEL SERIALIZABLE');
await client.query(
'SELECT pg_advisory_xact_lock(hashtextextended($1, 0))',
[JSON.stringify([command.projectId, command.provider])],
);
const replayRows = await this.transitionRows(client, 'mutation_id = $1', [
command.mutationId,
]);
if (replayRows.length > 1) throw unavailable();
if (replayRows[0]) {
const stored = transitionFromRow(replayRows[0]);
if (stored.commandDigest !== command.commandDigest) {
throw new ModelProviderCredentialTransitionConflictError();
}
await client.query('COMMIT');
return Object.freeze({
status: 'existing' as const,
transition: stored,
});
}
const currentRows = await this.transitionRows(
client,
'project_id = $1 AND provider = $2',
[command.projectId, command.provider],
);
const current = currentRows[0] ? transitionFromRow(currentRows[0]) : null;
if ((current?.generation ?? 0) !== command.expectedGeneration) {
throw new ModelProviderCredentialTransitionConflictError();
}
const clock = await client.query<Row>(
`SELECT floor(extract(epoch FROM clock_timestamp()) * 1000)::bigint AS now`,
);
if (clock.rows.length !== 1 || !clock.rows[0]) throw unavailable();
const transition = createModelProviderCredentialTransition(
command,
current,
integer(clock.rows[0].now),
);
if (command.binding) {
await client.query(
`INSERT INTO "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_bindings" (
project_id, provider, revision, secret_ref, scheme,
binding_digest, binding_json
) VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb)`,
[
command.binding.projectId,
command.binding.provider,
command.binding.revision,
command.binding.secretRef,
command.binding.scheme,
digestModelProviderCredentialBinding(command.binding),
JSON.stringify(command.binding),
],
);
}
await client.query(
`INSERT INTO "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_transitions" (
project_id, provider, generation, action,
active_binding_revision, active_binding_digest,
previous_transition_digest, mutation_id,
changed_by_type, changed_by_id, changed_at_ms,
command_digest, transition_digest, transition_json
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13,
$14::jsonb
)`,
[
transition.projectId,
transition.provider,
transition.generation,
transition.action,
transition.activeBindingRevision,
transition.activeBindingDigest,
transition.previousTransitionDigest,
transition.mutationId,
transition.changedBy.type,
transition.changedBy.id,
transition.changedAtMs,
transition.commandDigest,
transition.transitionDigest,
JSON.stringify(transition),
],
);
await client.query('COMMIT');
return Object.freeze({ status: 'created' as const, transition });
} catch (error) {
await rollback(client);
throw mapStorageError(error);
} finally {
client.release();
}
}
async commitAuthorized(
mutationValue: AuthorizedModelProviderCredentialTransitionMutation,
): Promise<Readonly<CommitModelProviderCredentialTransitionResult>> {
const mutation =
normalizeAuthorizedModelProviderCredentialTransitionMutation(
mutationValue,
);
const { command, audit } = mutation;
let client: PostgresClient;
try {
client = await this.pool.connect();
} catch (error) {
throw unavailable(error);
}
try {
await client.query('BEGIN ISOLATION LEVEL SERIALIZABLE');
await client.query(
'SELECT pg_advisory_xact_lock(hashtextextended($1, 0))',
[JSON.stringify([command.projectId, command.provider])],
);
await confirmAdministrationFence(client, mutation);
const replayRows = await this.transitionRows(client, 'mutation_id = $1', [
command.mutationId,
]);
const auditRows = await administrationAuditRows(client, audit.eventId);
if (replayRows.length > 1 || auditRows.length > 1) {
throw new ModelProviderCredentialAdministrationMutationConflictError();
}
if (replayRows[0]) {
const stored = transitionFromRow(replayRows[0]);
let storedAudit: Readonly<SecurityAuditRecord>;
try {
if (!auditRows[0]) {
throw new ModelProviderCredentialAdministrationMutationConflictError();
}
storedAudit = administrationAuditFromRow(auditRows[0]);
} catch (error) {
if (
error instanceof
ModelProviderCredentialAdministrationMutationConflictError
) {
throw error;
}
throw new ModelProviderCredentialAdministrationMutationConflictError();
}
if (
stored.commandDigest !== command.commandDigest ||
!sameAdministrationReplayAudit(storedAudit, audit)
) {
throw new ModelProviderCredentialAdministrationMutationConflictError();
}
await client.query('COMMIT');
return Object.freeze({
status: 'existing' as const,
transition: stored,
});
}
if (auditRows[0]) {
throw new ModelProviderCredentialAdministrationMutationConflictError();
}
const currentRows = await this.transitionRows(
client,
'project_id = $1 AND provider = $2',
[command.projectId, command.provider],
);
const current = currentRows[0] ? transitionFromRow(currentRows[0]) : null;
if ((current?.generation ?? 0) !== command.expectedGeneration) {
throw new ModelProviderCredentialAdministrationMutationConflictError();
}
const clock = await client.query<Row>(
`SELECT floor(extract(epoch FROM clock_timestamp()) * 1000)::bigint AS now`,
);
if (clock.rows.length !== 1 || !clock.rows[0]) throw unavailable();
const transition = createModelProviderCredentialTransition(
command,
current,
integer(clock.rows[0].now),
);
if (command.binding) {
await client.query(
`INSERT INTO "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_bindings" (
project_id, provider, revision, secret_ref, scheme,
binding_digest, binding_json
) VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb)`,
[
command.binding.projectId,
command.binding.provider,
command.binding.revision,
command.binding.secretRef,
command.binding.scheme,
digestModelProviderCredentialBinding(command.binding),
JSON.stringify(command.binding),
],
);
}
await client.query(
`INSERT INTO "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_transitions" (
project_id, provider, generation, action,
active_binding_revision, active_binding_digest,
previous_transition_digest, mutation_id,
changed_by_type, changed_by_id, changed_at_ms,
command_digest, transition_digest, transition_json
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13,
$14::jsonb
)`,
[
transition.projectId,
transition.provider,
transition.generation,
transition.action,
transition.activeBindingRevision,
transition.activeBindingDigest,
transition.previousTransitionDigest,
transition.mutationId,
transition.changedBy.type,
transition.changedBy.id,
transition.changedAtMs,
transition.commandDigest,
transition.transitionDigest,
JSON.stringify(transition),
],
);
await insertAdministrationAudit(client, audit);
await client.query('COMMIT');
return Object.freeze({ status: 'created' as const, transition });
} catch (error) {
await rollback(client);
throw mapAdministrationStorageError(error);
} finally {
client.release();
}
}
}
@@ -0,0 +1,223 @@
import { createHash } from 'node:crypto';
import type {
PostgresClient,
PostgresQueryable,
} from '@qinglong/runtime-core';
import {
ModelProviderCredentialCatalogUnavailableError,
ModelProviderCredentialTransitionConflictError,
normalizeModelProviderCredentialTransition,
type ModelProviderCredentialTransition,
} from '../modelProviderCredentialCatalog';
import {
MODEL_PROVIDER_CREDENTIAL_AUDIT_SCHEMA,
MODEL_PROVIDER_CREDENTIAL_OPERATIONS,
digestModelProviderCredentialBinding,
normalizeModelProviderCredentialBinding,
type ModelProviderCredentialAuditRecord,
type ModelProviderCredentialBinding,
type ModelProviderCredentialBindingLookup,
} from '../providerCredential';
export type Row = Record<string, unknown>;
export type Queryable = Pick<PostgresQueryable, 'query'>;
const IDENTITY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
export const PROVIDER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
const SHA256_PATTERN = /^sha256:[0-9a-f]{64}$/;
export function unavailable(
cause?: unknown,
): ModelProviderCredentialCatalogUnavailableError {
return new ModelProviderCredentialCatalogUnavailableError({
cause: cause instanceof Error ? cause : undefined,
});
}
export function sqlState(error: unknown): string {
if (!error || typeof error !== 'object') return '';
const code = (error as { code?: unknown }).code;
return typeof code === 'string' ? code : '';
}
export function mapStorageError(error: unknown): Error {
if (
error instanceof ModelProviderCredentialTransitionConflictError ||
error instanceof ModelProviderCredentialCatalogUnavailableError
) {
return error;
}
if (['23503', '23505', '23514', '40001', '40P01'].includes(sqlState(error))) {
return new ModelProviderCredentialTransitionConflictError();
}
return unavailable(error);
}
export function identity(
value: unknown,
pattern: RegExp = IDENTITY_PATTERN,
): string {
if (typeof value !== 'string' || !pattern.test(value)) throw unavailable();
return value;
}
export function integer(value: unknown): number {
if (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0) {
return value;
}
if (typeof value === 'string' && /^(0|[1-9]\d*)$/.test(value)) {
const parsed = Number(value);
if (Number.isSafeInteger(parsed)) return parsed;
}
throw unavailable();
}
export function jsonObject(value: unknown): Record<string, unknown> {
if (
value &&
typeof value === 'object' &&
!Array.isArray(value) &&
Object.getPrototypeOf(value) === Object.prototype
) {
return value as Record<string, unknown>;
}
if (typeof value === 'string') {
try {
const parsed: unknown = JSON.parse(value);
if (
parsed &&
typeof parsed === 'object' &&
!Array.isArray(parsed) &&
Object.getPrototypeOf(parsed) === Object.prototype
) {
return parsed as Record<string, unknown>;
}
} catch {
// Mapped to the stable unavailable error below.
}
}
throw unavailable();
}
export function bindingFromRow(
row: Row,
): Readonly<ModelProviderCredentialBinding> {
let binding: Readonly<ModelProviderCredentialBinding>;
try {
binding = normalizeModelProviderCredentialBinding(
jsonObject(row.bindingJson) as unknown as ModelProviderCredentialBinding,
);
} catch (cause) {
throw unavailable(cause);
}
if (
binding.projectId !== identity(row.projectId) ||
binding.provider !== identity(row.provider, PROVIDER_PATTERN) ||
binding.revision !== identity(row.revision) ||
binding.secretRef !==
identity(row.secretRef, /^qlsecret:v1:[^\0\r\n]{1,500}$/) ||
binding.scheme !== row.scheme ||
digestModelProviderCredentialBinding(binding) !== row.bindingDigest
) {
throw unavailable();
}
return binding;
}
export function transitionFromRow(
row: Row,
): Readonly<ModelProviderCredentialTransition> {
let transition: Readonly<ModelProviderCredentialTransition>;
try {
transition = normalizeModelProviderCredentialTransition(
jsonObject(
row.transitionJson,
) as unknown as ModelProviderCredentialTransition,
);
} catch (cause) {
throw unavailable(cause);
}
if (
transition.projectId !== identity(row.projectId) ||
transition.provider !== identity(row.provider, PROVIDER_PATTERN) ||
transition.generation !== integer(row.generation) ||
transition.mutationId !== identity(row.mutationId) ||
transition.commandDigest !== row.commandDigest ||
transition.transitionDigest !== row.transitionDigest
) {
throw unavailable();
}
return transition;
}
export async function rollback(client: PostgresClient): Promise<void> {
await client.query('ROLLBACK').catch(() => undefined);
}
export function normalizeLookup(
value: Readonly<ModelProviderCredentialBindingLookup>,
): Readonly<ModelProviderCredentialBindingLookup> {
if (
!value ||
typeof value !== 'object' ||
Array.isArray(value) ||
Object.keys(value).sort().join('\0') !== 'projectId\0provider'
) {
throw unavailable();
}
return Object.freeze({
projectId: identity(value.projectId),
provider: identity(value.provider, PROVIDER_PATTERN),
});
}
export function normalizeAuditRecord(
value: Readonly<ModelProviderCredentialAuditRecord>,
): Readonly<ModelProviderCredentialAuditRecord> {
if (
!value ||
typeof value !== 'object' ||
Array.isArray(value) ||
Object.keys(value).sort().join('\0') !==
[
'bindingDigest',
'bindingRevision',
'occurredAtMs',
'operation',
'projectId',
'provider',
'requestId',
'schema',
]
.sort()
.join('\0') ||
value.schema !== MODEL_PROVIDER_CREDENTIAL_AUDIT_SCHEMA ||
!MODEL_PROVIDER_CREDENTIAL_OPERATIONS.includes(value.operation) ||
typeof value.bindingDigest !== 'string' ||
!SHA256_PATTERN.test(value.bindingDigest)
) {
throw unavailable();
}
return Object.freeze({
schema: MODEL_PROVIDER_CREDENTIAL_AUDIT_SCHEMA,
operation: value.operation,
projectId: identity(value.projectId),
provider: identity(value.provider, PROVIDER_PATTERN),
requestId: identity(value.requestId),
bindingRevision: identity(value.bindingRevision),
bindingDigest: value.bindingDigest,
occurredAtMs: integer(value.occurredAtMs),
});
}
export function auditDigest(
record: Readonly<ModelProviderCredentialAuditRecord>,
): string {
return createHash('sha256')
.update('qinglong/model-provider-credential-audit@v1', 'utf8')
.update('\0', 'utf8')
.update(JSON.stringify(record), 'utf8')
.digest('hex');
}
@@ -0,0 +1,83 @@
import type { PostgresClient } from '@qinglong/runtime-core';
import { InvalidModelProviderCredentialTestConnectionError } from '../modelProviderCredentialTestConnection';
import {
ModelProviderCredentialTestPlanUnavailableError,
type PostgresModelProviderCredentialTestPlanOptions,
} from './contracts';
export type Row = Record<string, unknown>;
const DEFAULT_QUOTA_WINDOW_MS = 60_000;
const DEFAULT_QUOTA_LIMIT = 5;
export const UUID_V4_PATTERN =
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
const IDENTIFIER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
export function exact(
value: unknown,
keys: readonly string[],
): Record<string, unknown> {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new InvalidModelProviderCredentialTestConnectionError();
}
const candidate = value as Record<string, unknown>;
const actual = Object.keys(candidate).sort();
const expected = [...keys].sort();
if (
actual.length !== expected.length ||
actual.some((key, index) => key !== expected[index])
) {
throw new InvalidModelProviderCredentialTestConnectionError();
}
return candidate;
}
export function options(
value: PostgresModelProviderCredentialTestPlanOptions | undefined,
): Readonly<{ quotaWindowMs: number; quotaLimit: number }> {
if (value !== undefined) exact(value, ['quotaLimit', 'quotaWindowMs']);
const quotaWindowMs = value?.quotaWindowMs ?? DEFAULT_QUOTA_WINDOW_MS;
const quotaLimit = value?.quotaLimit ?? DEFAULT_QUOTA_LIMIT;
if (
!Number.isSafeInteger(quotaWindowMs) ||
quotaWindowMs < 1_000 ||
quotaWindowMs > 5 * 60_000 ||
!Number.isSafeInteger(quotaLimit) ||
quotaLimit < 1 ||
quotaLimit > 32
) {
throw new TypeError(
'PostgreSQL model provider credential test plan options are invalid',
);
}
return Object.freeze({ quotaWindowMs, quotaLimit });
}
export function integer(value: unknown, minimum = 0): number {
const normalized =
typeof value === 'string' && /^(0|[1-9]\d*)$/.test(value)
? Number(value)
: value;
if (!Number.isSafeInteger(normalized) || (normalized as number) < minimum) {
throw new ModelProviderCredentialTestPlanUnavailableError();
}
return normalized as number;
}
export function text(value: unknown, pattern: RegExp = IDENTIFIER_PATTERN): string {
if (typeof value !== 'string' || !pattern.test(value)) {
throw new ModelProviderCredentialTestPlanUnavailableError();
}
return value;
}
export async function rollback(client: PostgresClient): Promise<void> {
await client.query('ROLLBACK').catch(() => undefined);
}
export function sqlState(error: unknown): string {
if (!error || typeof error !== 'object') return '';
const code = (error as { code?: unknown }).code;
return typeof code === 'string' ? code : '';
}
@@ -0,0 +1,146 @@
import type { SecurityAuditRecord } from '@qinglong/runtime-core/security-audit';
import type {
ModelProviderCredentialTestAllowlist,
ModelProviderCredentialTestExecution,
ModelProviderCredentialTestPlan,
ModelProviderCredentialTestResult,
} from '../modelProviderCredentialTestConnection';
export const MODEL_PROVIDER_CREDENTIAL_TEST_PLAN_OPERATION_ID =
'model_provider_credential.test.plan';
type Row = Record<string, unknown>;
export interface AuthorizedModelProviderCredentialTestPlan {
readonly plan: ModelProviderCredentialTestPlan;
readonly audit: SecurityAuditRecord;
}
export interface CreateModelProviderCredentialTestPlanResult {
readonly status: 'created' | 'existing';
readonly plan: Readonly<ModelProviderCredentialTestPlan>;
}
export interface ModelProviderCredentialTestPlanRepository {
createAuthorized(
value: AuthorizedModelProviderCredentialTestPlan,
): Promise<Readonly<CreateModelProviderCredentialTestPlanResult>>;
}
export interface PostgresModelProviderCredentialTestPlanOptions {
readonly quotaWindowMs?: number;
readonly quotaLimit?: number;
}
export class ModelProviderCredentialTestPlanAuthorizationFenceConflictError extends Error {
readonly code =
'MODEL_PROVIDER_CREDENTIAL_TEST_PLAN_AUTHORIZATION_FENCE_CONFLICT';
constructor() {
super('Model provider credential test plan authorization fence changed');
this.name =
'ModelProviderCredentialTestPlanAuthorizationFenceConflictError';
}
}
export class ModelProviderCredentialTestPlanConflictError extends Error {
readonly code = 'MODEL_PROVIDER_CREDENTIAL_TEST_PLAN_CONFLICT';
constructor() {
super('Model provider credential test plan conflicts with durable state');
this.name = 'ModelProviderCredentialTestPlanConflictError';
}
}
export class ModelProviderCredentialTestPlanQuotaExceededError extends Error {
readonly code = 'MODEL_PROVIDER_CREDENTIAL_TEST_PLAN_QUOTA_EXCEEDED';
constructor(readonly retryAfterMs: number) {
super('Model provider credential test plan quota is exceeded');
this.name = 'ModelProviderCredentialTestPlanQuotaExceededError';
}
}
export class ModelProviderCredentialTestPlanUnavailableError extends Error {
readonly code = 'MODEL_PROVIDER_CREDENTIAL_TEST_PLAN_UNAVAILABLE';
constructor(options?: ErrorOptions) {
super('Model provider credential test plan is unavailable', options);
this.name = 'ModelProviderCredentialTestPlanUnavailableError';
}
}
export interface BeginModelProviderCredentialTestExecutionInput {
readonly executionId: string;
readonly testId: string;
readonly allowlist: ModelProviderCredentialTestAllowlist;
}
export interface BeginModelProviderCredentialTestExecutionResult {
readonly status: 'created' | 'existing';
readonly plan: Readonly<ModelProviderCredentialTestPlan>;
readonly execution: Readonly<ModelProviderCredentialTestExecution>;
readonly result: Readonly<ModelProviderCredentialTestResult> | null;
}
export interface CompleteModelProviderCredentialTestExecutionResult {
readonly status: 'created' | 'existing';
readonly result: Readonly<ModelProviderCredentialTestResult>;
}
export interface ModelProviderCredentialTestExecutionRepository {
beginExecution(
value: BeginModelProviderCredentialTestExecutionInput,
): Promise<Readonly<BeginModelProviderCredentialTestExecutionResult>>;
complete(
value: ModelProviderCredentialTestResult,
): Promise<Readonly<CompleteModelProviderCredentialTestExecutionResult>>;
}
export interface PostgresModelProviderCredentialTesterReadinessReport {
readonly ready: true;
readonly currentUser: string;
readonly migrationIds: readonly string[];
readonly writablePrimary: true;
readonly testerAuthority: true;
readonly leastPrivilege: true;
}
export class ModelProviderCredentialTestExecutionRejectedError extends Error {
readonly code = 'MODEL_PROVIDER_CREDENTIAL_TEST_EXECUTION_REJECTED';
constructor() {
super('Model provider credential test execution is rejected');
this.name = 'ModelProviderCredentialTestExecutionRejectedError';
}
}
export class ModelProviderCredentialTestExecutionConflictError extends Error {
readonly code = 'MODEL_PROVIDER_CREDENTIAL_TEST_EXECUTION_CONFLICT';
constructor() {
super(
'Model provider credential test execution conflicts with durable state',
);
this.name = 'ModelProviderCredentialTestExecutionConflictError';
}
}
export class ModelProviderCredentialTestExecutionUnavailableError extends Error {
readonly code = 'MODEL_PROVIDER_CREDENTIAL_TEST_EXECUTION_UNAVAILABLE';
constructor(options?: ErrorOptions) {
super('Model provider credential test execution is unavailable', options);
this.name = 'ModelProviderCredentialTestExecutionUnavailableError';
}
}
export class PostgresModelProviderCredentialTesterNotReadyError extends Error {
readonly code = 'POSTGRES_MODEL_PROVIDER_CREDENTIAL_TESTER_NOT_READY';
constructor(options?: ErrorOptions) {
super('PostgreSQL model provider credential tester is not ready', options);
this.name = 'PostgresModelProviderCredentialTesterNotReadyError';
}
}
@@ -0,0 +1,361 @@
import type { PostgresClient, PostgresPool } from '@qinglong/runtime-core';
import {
InvalidModelProviderCredentialTestConnectionError,
createModelProviderCredentialTestExecution,
normalizeModelProviderCredentialTestAllowlist,
normalizeModelProviderCredentialTestExecution,
normalizeModelProviderCredentialTestPlan,
normalizeModelProviderCredentialTestResult,
resolveModelProviderCredentialTestEndpoint,
type ModelProviderCredentialTestAllowlist,
type ModelProviderCredentialTestExecution,
type ModelProviderCredentialTestPlan,
type ModelProviderCredentialTestResult,
} from '../modelProviderCredentialTestConnection';
import {
ModelProviderCredentialTestExecutionConflictError,
ModelProviderCredentialTestExecutionRejectedError,
ModelProviderCredentialTestExecutionUnavailableError,
type BeginModelProviderCredentialTestExecutionInput,
type BeginModelProviderCredentialTestExecutionResult,
type CompleteModelProviderCredentialTestExecutionResult,
type ModelProviderCredentialTestExecutionRepository,
} from './contracts';
import {
UUID_V4_PATTERN,
exact,
integer,
rollback,
sqlState,
type Row,
} from './common';
function executionError(error: unknown): Error {
if (
error instanceof InvalidModelProviderCredentialTestConnectionError ||
error instanceof ModelProviderCredentialTestExecutionRejectedError ||
error instanceof ModelProviderCredentialTestExecutionConflictError ||
error instanceof ModelProviderCredentialTestExecutionUnavailableError
) {
return error;
}
if (['23503', '23505', '23514', '40001', '40P01'].includes(sqlState(error))) {
return new ModelProviderCredentialTestExecutionConflictError();
}
return new ModelProviderCredentialTestExecutionUnavailableError({
cause: error instanceof Error ? error : undefined,
});
}
function normalizeStoredPlan(
value: unknown,
): Readonly<ModelProviderCredentialTestPlan> {
try {
return normalizeModelProviderCredentialTestPlan(
value as ModelProviderCredentialTestPlan,
);
} catch (error) {
throw new ModelProviderCredentialTestExecutionUnavailableError({
cause: error instanceof Error ? error : undefined,
});
}
}
function normalizeStoredExecution(
value: unknown,
): Readonly<ModelProviderCredentialTestExecution> {
try {
return normalizeModelProviderCredentialTestExecution(
value as ModelProviderCredentialTestExecution,
);
} catch (error) {
throw new ModelProviderCredentialTestExecutionUnavailableError({
cause: error instanceof Error ? error : undefined,
});
}
}
function normalizeStoredResult(
value: unknown,
): Readonly<ModelProviderCredentialTestResult> {
try {
return normalizeModelProviderCredentialTestResult(
value as ModelProviderCredentialTestResult,
);
} catch (error) {
throw new ModelProviderCredentialTestExecutionUnavailableError({
cause: error instanceof Error ? error : undefined,
});
}
}
async function loadTestPlan(
client: PostgresClient,
testId: string,
): Promise<
Readonly<{
plan: Readonly<ModelProviderCredentialTestPlan>;
observedAtMs: number;
}>
> {
const result = await client.query<Row>(
`WITH database_clock AS (
SELECT floor(extract(epoch FROM clock_timestamp()) * 1000)::bigint
AS now_ms
)
SELECT plan.plan_json AS "planJson",
database_clock.now_ms AS "observedAtMs"
FROM "ql3_ai"."model_provider_credential_test_plans" AS plan
CROSS JOIN database_clock
WHERE plan.test_id = $1::uuid
LIMIT 2`,
[testId],
);
if (result.rows.length !== 1) {
throw new ModelProviderCredentialTestExecutionRejectedError();
}
const plan = normalizeStoredPlan(result.rows[0]?.planJson);
const observedAtMs = integer(result.rows[0]?.observedAtMs);
if (plan.testId !== testId || observedAtMs >= plan.expiresAtMs) {
throw new ModelProviderCredentialTestExecutionRejectedError();
}
return Object.freeze({ plan, observedAtMs });
}
function confirmExecutionAllowlist(
plan: Readonly<ModelProviderCredentialTestPlan>,
allowlistValue: ModelProviderCredentialTestAllowlist,
): void {
let selected;
try {
const allowlist =
normalizeModelProviderCredentialTestAllowlist(allowlistValue);
selected = resolveModelProviderCredentialTestEndpoint(
allowlist,
plan.provider,
);
} catch {
throw new ModelProviderCredentialTestExecutionRejectedError();
}
if (JSON.stringify(selected) !== JSON.stringify(plan.endpoint)) {
throw new ModelProviderCredentialTestExecutionRejectedError();
}
}
async function loadExecutionState(
client: PostgresClient,
testId: string,
): Promise<Readonly<{
execution: Readonly<ModelProviderCredentialTestExecution>;
result: Readonly<ModelProviderCredentialTestResult> | null;
}> | null> {
const selected = await client.query<Row>(
`SELECT execution.execution_json AS "executionJson",
result.result_json AS "resultJson"
FROM "ql3_ai"."model_provider_credential_test_executions" AS execution
LEFT JOIN "ql3_ai"."model_provider_credential_test_results" AS result
ON result.execution_id = execution.execution_id
WHERE execution.test_id = $1::uuid
LIMIT 2`,
[testId],
);
if (selected.rows.length === 0) return null;
if (selected.rows.length !== 1) {
throw new ModelProviderCredentialTestExecutionConflictError();
}
const execution = normalizeStoredExecution(selected.rows[0]?.executionJson);
const result =
selected.rows[0]?.resultJson === null ||
selected.rows[0]?.resultJson === undefined
? null
: normalizeStoredResult(selected.rows[0]?.resultJson);
if (
execution.testId !== testId ||
(result !== null &&
(result.testId !== testId ||
result.executionId !== execution.executionId ||
result.planDigest !== execution.planDigest))
) {
throw new ModelProviderCredentialTestExecutionConflictError();
}
return Object.freeze({ execution, result });
}
async function insertExecution(
client: PostgresClient,
execution: Readonly<ModelProviderCredentialTestExecution>,
): Promise<void> {
await client.query(
`INSERT INTO "ql3_ai"."model_provider_credential_test_executions" (
execution_id, test_id, plan_digest, started_at_ms,
execution_digest, execution_json
) VALUES ($1::uuid, $2::uuid, $3, $4, $5, $6::jsonb)`,
[
execution.executionId,
execution.testId,
execution.planDigest,
execution.startedAtMs,
execution.executionDigest,
JSON.stringify(execution),
],
);
}
async function insertResult(
client: PostgresClient,
result: Readonly<ModelProviderCredentialTestResult>,
): Promise<void> {
await client.query(
`INSERT INTO "ql3_ai"."model_provider_credential_test_results" (
execution_id, test_id, plan_digest, outcome, model_count,
duration_ms, completed_at_ms, result_digest, result_json
) VALUES ($1::uuid, $2::uuid, $3, $4, $5, $6, $7, $8, $9::jsonb)`,
[
result.executionId,
result.testId,
result.planDigest,
result.outcome,
result.modelCount,
result.durationMs,
result.completedAtMs,
result.resultDigest,
JSON.stringify(result),
],
);
}
export class PostgresModelProviderCredentialTestExecutionRepository
implements ModelProviderCredentialTestExecutionRepository
{
constructor(private readonly pool: PostgresPool) {
if (!pool || typeof pool.connect !== 'function') {
throw new TypeError(
'PostgreSQL model provider credential test execution pool is invalid',
);
}
}
async beginExecution(
value: BeginModelProviderCredentialTestExecutionInput,
): Promise<Readonly<BeginModelProviderCredentialTestExecutionResult>> {
exact(value, ['allowlist', 'executionId', 'testId']);
if (
typeof value.executionId !== 'string' ||
!UUID_V4_PATTERN.test(value.executionId) ||
typeof value.testId !== 'string' ||
!UUID_V4_PATTERN.test(value.testId)
) {
throw new InvalidModelProviderCredentialTestConnectionError();
}
let client: PostgresClient;
try {
client = await this.pool.connect();
} catch (error) {
throw new ModelProviderCredentialTestExecutionUnavailableError({
cause: error,
});
}
try {
await client.query('BEGIN ISOLATION LEVEL SERIALIZABLE');
await client.query(
'SELECT pg_advisory_xact_lock(hashtextextended($1, 0))',
[
JSON.stringify([
value.testId,
'model-provider-credential-test-execution',
]),
],
);
const { plan, observedAtMs } = await loadTestPlan(client, value.testId);
confirmExecutionAllowlist(plan, value.allowlist);
const stored = await loadExecutionState(client, value.testId);
if (stored) {
if (
stored.execution.executionId !== value.executionId ||
stored.execution.planDigest !== plan.planDigest
) {
throw new ModelProviderCredentialTestExecutionConflictError();
}
await client.query('COMMIT');
return Object.freeze({
status: 'existing' as const,
plan,
execution: stored.execution,
result: stored.result,
});
}
const execution = createModelProviderCredentialTestExecution({
executionId: value.executionId,
testId: value.testId,
planDigest: plan.planDigest,
startedAtMs: observedAtMs,
});
await insertExecution(client, execution);
await client.query('COMMIT');
return Object.freeze({
status: 'created' as const,
plan,
execution,
result: null,
});
} catch (error) {
await rollback(client);
throw executionError(error);
} finally {
client.release();
}
}
async complete(
value: ModelProviderCredentialTestResult,
): Promise<Readonly<CompleteModelProviderCredentialTestExecutionResult>> {
const candidate = normalizeModelProviderCredentialTestResult(value);
let client: PostgresClient;
try {
client = await this.pool.connect();
} catch (error) {
throw new ModelProviderCredentialTestExecutionUnavailableError({
cause: error,
});
}
try {
await client.query('BEGIN ISOLATION LEVEL SERIALIZABLE');
await client.query(
'SELECT pg_advisory_xact_lock(hashtextextended($1, 0))',
[
JSON.stringify([
candidate.testId,
'model-provider-credential-test-execution',
]),
],
);
const stored = await loadExecutionState(client, candidate.testId);
if (
!stored ||
stored.execution.executionId !== candidate.executionId ||
stored.execution.planDigest !== candidate.planDigest
) {
throw new ModelProviderCredentialTestExecutionConflictError();
}
if (stored.result) {
if (JSON.stringify(stored.result) !== JSON.stringify(candidate)) {
throw new ModelProviderCredentialTestExecutionConflictError();
}
await client.query('COMMIT');
return Object.freeze({
status: 'existing' as const,
result: stored.result,
});
}
await insertResult(client, candidate);
await client.query('COMMIT');
return Object.freeze({ status: 'created' as const, result: candidate });
} catch (error) {
await rollback(client);
throw executionError(error);
} finally {
client.release();
}
}
}
@@ -0,0 +1,480 @@
import type { PostgresClient, PostgresPool } from '@qinglong/runtime-core';
import {
normalizeSecurityAuditRecord,
type SecurityAuditRecord,
} from '@qinglong/runtime-core/security-audit';
import {
InvalidModelProviderCredentialTestConnectionError,
normalizeModelProviderCredentialTestPlan,
type ModelProviderCredentialTestPlan,
} from '../modelProviderCredentialTestConnection';
import {
MODEL_PROVIDER_CREDENTIAL_TEST_PLAN_OPERATION_ID,
ModelProviderCredentialTestPlanAuthorizationFenceConflictError,
ModelProviderCredentialTestPlanConflictError,
ModelProviderCredentialTestPlanQuotaExceededError,
ModelProviderCredentialTestPlanUnavailableError,
type AuthorizedModelProviderCredentialTestPlan,
type CreateModelProviderCredentialTestPlanResult,
type ModelProviderCredentialTestPlanRepository,
type PostgresModelProviderCredentialTestPlanOptions,
} from './contracts';
import {
UUID_V4_PATTERN,
exact,
integer,
options,
rollback,
sqlState,
text,
type Row,
} from './common';
function normalizeAuthorized(
value: AuthorizedModelProviderCredentialTestPlan,
): Readonly<AuthorizedModelProviderCredentialTestPlan> {
exact(value, ['audit', 'plan']);
try {
const plan = normalizeModelProviderCredentialTestPlan(value.plan);
const audit = normalizeSecurityAuditRecord(value.audit);
if (
audit.eventId !== plan.testId ||
audit.requestId !== plan.requestId ||
audit.operationId !== MODEL_PROVIDER_CREDENTIAL_TEST_PLAN_OPERATION_ID ||
audit.projectId !== plan.projectId ||
audit.outcome !== 'allowed' ||
audit.subject?.type !== 'user' ||
audit.subject.id !== plan.requestedBy.id ||
audit.authenticationId === null ||
audit.fence?.projectVersion !== plan.fence.projectVersion ||
audit.fence.bindingVersion !== plan.fence.bindingVersion
) {
throw new InvalidModelProviderCredentialTestConnectionError();
}
return Object.freeze({ plan, audit });
} catch (error) {
if (error instanceof InvalidModelProviderCredentialTestConnectionError) {
throw error;
}
throw new InvalidModelProviderCredentialTestConnectionError();
}
}
function normalizeAuditRow(row: Row): Readonly<SecurityAuditRecord> {
if (!Array.isArray(row.reasons)) {
throw new ModelProviderCredentialTestPlanUnavailableError();
}
try {
return normalizeSecurityAuditRecord({
eventId: text(row.eventId, UUID_V4_PATTERN),
requestId: text(row.requestId),
operationId: text(row.operationId),
projectId: text(row.projectId),
subject: {
type: text(row.subjectType) as 'user',
id: text(row.subjectId),
},
authenticationId: text(row.authenticationId),
outcome: text(row.outcome) as SecurityAuditRecord['outcome'],
reasons: row.reasons as string[],
fence: {
projectVersion: integer(row.projectVersion, 1),
bindingVersion: integer(row.bindingVersion, 1),
},
occurredAtMs: integer(row.occurredAtMs),
});
} catch (error) {
if (error instanceof ModelProviderCredentialTestPlanUnavailableError) {
throw error;
}
throw new ModelProviderCredentialTestPlanUnavailableError({
cause: error instanceof Error ? error : undefined,
});
}
}
function semanticAuditEqual(
left: Readonly<SecurityAuditRecord>,
right: Readonly<SecurityAuditRecord>,
): boolean {
const { occurredAtMs: _leftTime, ...leftSemantic } = left;
const { occurredAtMs: _rightTime, ...rightSemantic } = right;
return JSON.stringify(leftSemantic) === JSON.stringify(rightSemantic);
}
function samePlan(
left: Readonly<ModelProviderCredentialTestPlan>,
right: Readonly<ModelProviderCredentialTestPlan>,
): boolean {
const {
planDigest: _leftPlanDigest,
plannedAtMs: leftPlannedAtMs,
expiresAtMs: leftExpiresAtMs,
...leftIdentity
} = left;
const {
planDigest: _rightPlanDigest,
plannedAtMs: rightPlannedAtMs,
expiresAtMs: rightExpiresAtMs,
...rightIdentity
} = right;
return (
leftExpiresAtMs - leftPlannedAtMs === rightExpiresAtMs - rightPlannedAtMs &&
JSON.stringify(leftIdentity) === JSON.stringify(rightIdentity)
);
}
function mapError(error: unknown): Error {
if (
error instanceof InvalidModelProviderCredentialTestConnectionError ||
error instanceof
ModelProviderCredentialTestPlanAuthorizationFenceConflictError ||
error instanceof ModelProviderCredentialTestPlanConflictError ||
error instanceof ModelProviderCredentialTestPlanQuotaExceededError ||
error instanceof ModelProviderCredentialTestPlanUnavailableError
) {
return error;
}
if (['23503', '23505', '23514', '40001', '40P01'].includes(sqlState(error))) {
return new ModelProviderCredentialTestPlanConflictError();
}
return new ModelProviderCredentialTestPlanUnavailableError({
cause: error instanceof Error ? error : undefined,
});
}
async function confirmFence(
client: PostgresClient,
value: Readonly<AuthorizedModelProviderCredentialTestPlan>,
): Promise<void> {
const project = await client.query<Row>(
`SELECT status, version FROM "ql3"."projects" WHERE id = $1`,
[value.plan.projectId],
);
const binding = await client.query<Row>(
`SELECT version, state
FROM "ql3"."project_role_bindings"
WHERE project_id = $1 AND subject_type = 'user' AND subject_id = $2
ORDER BY version DESC
LIMIT 1`,
[value.plan.projectId, value.plan.requestedBy.id],
);
if (
project.rows.length !== 1 ||
project.rows[0]?.status !== 'active' ||
integer(project.rows[0]?.version, 1) !== value.plan.fence.projectVersion ||
binding.rows.length !== 1 ||
binding.rows[0]?.state !== 'active' ||
integer(binding.rows[0]?.version, 1) !== value.plan.fence.bindingVersion
) {
throw new ModelProviderCredentialTestPlanAuthorizationFenceConflictError();
}
}
async function plansByIdentity(
client: PostgresClient,
plan: Readonly<ModelProviderCredentialTestPlan>,
): Promise<readonly Readonly<ModelProviderCredentialTestPlan>[]> {
const result = await client.query<Row>(
`SELECT plan_json AS "planJson"
FROM "ql3_ai"."model_provider_credential_test_plans"
WHERE test_id = $1::uuid
OR (project_id = $2 AND request_id = $3)
LIMIT 3`,
[plan.testId, plan.projectId, plan.requestId],
);
try {
return Object.freeze(
result.rows.map((row) =>
normalizeModelProviderCredentialTestPlan(
row.planJson as ModelProviderCredentialTestPlan,
),
),
);
} catch (error) {
throw new ModelProviderCredentialTestPlanUnavailableError({
cause: error instanceof Error ? error : undefined,
});
}
}
async function auditById(
client: PostgresClient,
eventId: string,
): Promise<Readonly<SecurityAuditRecord> | null> {
const result = await client.query<Row>(
`SELECT event_id AS "eventId", request_id AS "requestId",
operation_id AS "operationId", project_id AS "projectId",
subject_type AS "subjectType", subject_id AS "subjectId",
authentication_id AS "authenticationId", outcome, reasons,
project_version AS "projectVersion",
binding_version AS "bindingVersion",
occurred_at_ms AS "occurredAtMs"
FROM "ql3"."security_audit_events"
WHERE event_id = $1::uuid
LIMIT 2`,
[eventId],
);
if (result.rows.length > 1) {
throw new ModelProviderCredentialTestPlanConflictError();
}
return result.rows[0] ? normalizeAuditRow(result.rows[0]) : null;
}
async function quotaHasReceipt(
client: PostgresClient,
plan: Readonly<ModelProviderCredentialTestPlan>,
): Promise<boolean> {
const result = await client.query<Row>(
`SELECT receipt_ids ? $3::text AS "hasReceipt"
FROM "ql3_ai"."model_provider_credential_test_quota_buckets"
WHERE project_id = $1 AND subject_id = $2`,
[plan.projectId, plan.requestedBy.id, plan.testId],
);
return result.rows.length === 1 && result.rows[0]?.hasReceipt === true;
}
async function consumeQuota(
client: PostgresClient,
plan: Readonly<ModelProviderCredentialTestPlan>,
quotaWindowMs: number,
quotaLimit: number,
): Promise<void> {
let result = await client.query<Row>(
`WITH database_clock AS (
SELECT floor(extract(epoch FROM clock_timestamp()) * 1000)::bigint
AS now_ms
)
INSERT INTO "ql3_ai"."model_provider_credential_test_quota_buckets" (
project_id, subject_id, window_started_at_ms, consumed_count,
receipt_ids, updated_at_ms
)
SELECT $1, $2, (now_ms / $4::bigint) * $4::bigint, 1,
jsonb_build_array($3::text), now_ms
FROM database_clock
ON CONFLICT (project_id, subject_id)
DO UPDATE SET
window_started_at_ms = CASE
WHEN model_provider_credential_test_quota_buckets.window_started_at_ms
+ $4::bigint <= EXCLUDED.updated_at_ms
THEN (EXCLUDED.updated_at_ms / $4::bigint) * $4::bigint
ELSE model_provider_credential_test_quota_buckets.window_started_at_ms
END,
consumed_count = CASE
WHEN model_provider_credential_test_quota_buckets.window_started_at_ms
+ $4::bigint <= EXCLUDED.updated_at_ms THEN 1
WHEN model_provider_credential_test_quota_buckets.receipt_ids ? $3::text
THEN model_provider_credential_test_quota_buckets.consumed_count
ELSE model_provider_credential_test_quota_buckets.consumed_count + 1
END,
receipt_ids = CASE
WHEN model_provider_credential_test_quota_buckets.window_started_at_ms
+ $4::bigint <= EXCLUDED.updated_at_ms
THEN jsonb_build_array($3::text)
WHEN model_provider_credential_test_quota_buckets.receipt_ids ? $3::text
THEN model_provider_credential_test_quota_buckets.receipt_ids
ELSE model_provider_credential_test_quota_buckets.receipt_ids ||
jsonb_build_array($3::text)
END,
updated_at_ms = EXCLUDED.updated_at_ms
WHERE model_provider_credential_test_quota_buckets.window_started_at_ms
+ $4::bigint <= EXCLUDED.updated_at_ms
OR model_provider_credential_test_quota_buckets.receipt_ids ? $3::text
OR model_provider_credential_test_quota_buckets.consumed_count < $5
RETURNING consumed_count AS "consumedCount",
window_started_at_ms + $4::bigint AS "resetAtMs",
updated_at_ms AS "observedAtMs"`,
[
plan.projectId,
plan.requestedBy.id,
plan.testId,
quotaWindowMs,
quotaLimit,
],
);
if (result.rows.length === 0) {
result = await client.query<Row>(
`WITH database_clock AS (
SELECT floor(extract(epoch FROM clock_timestamp()) * 1000)::bigint
AS now_ms
)
SELECT consumed_count AS "consumedCount",
window_started_at_ms + $3::bigint AS "resetAtMs",
database_clock.now_ms AS "observedAtMs"
FROM "ql3_ai"."model_provider_credential_test_quota_buckets"
CROSS JOIN database_clock
WHERE project_id = $1 AND subject_id = $2
LIMIT 2`,
[plan.projectId, plan.requestedBy.id, quotaWindowMs],
);
if (result.rows.length !== 1) {
throw new ModelProviderCredentialTestPlanUnavailableError();
}
const retryAfterMs = Math.max(
1,
integer(result.rows[0]?.resetAtMs) -
integer(result.rows[0]?.observedAtMs),
);
throw new ModelProviderCredentialTestPlanQuotaExceededError(retryAfterMs);
}
if (
result.rows.length !== 1 ||
integer(result.rows[0]?.consumedCount, 1) > quotaLimit
) {
throw new ModelProviderCredentialTestPlanUnavailableError();
}
}
async function insertPlan(
client: PostgresClient,
plan: Readonly<ModelProviderCredentialTestPlan>,
): Promise<void> {
await client.query(
`INSERT INTO "ql3_ai"."model_provider_credential_test_plans" (
test_id, request_id, project_id, provider, adapter, base_url,
endpoint_revision, endpoint_config_digest, deadline_ms,
max_response_bytes, max_models, max_cost_microusd, retry_limit,
requested_by_type, requested_by_id, project_version, binding_version,
planned_at_ms, expires_at_ms, plan_digest, plan_json
) VALUES (
$1::uuid, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12,
$13, $14, $15, $16, $17, $18, $19, $20, $21::jsonb
)`,
[
plan.testId,
plan.requestId,
plan.projectId,
plan.provider,
plan.endpoint.adapter,
plan.endpoint.baseUrl,
plan.endpoint.revision,
plan.endpoint.configDigest,
plan.endpoint.deadlineMs,
plan.endpoint.maxResponseBytes,
plan.endpoint.maxModels,
plan.endpoint.maxCostMicrousd,
plan.endpoint.retryLimit,
plan.requestedBy.type,
plan.requestedBy.id,
plan.fence.projectVersion,
plan.fence.bindingVersion,
plan.plannedAtMs,
plan.expiresAtMs,
plan.planDigest,
JSON.stringify(plan),
],
);
}
async function insertAudit(
client: PostgresClient,
audit: Readonly<SecurityAuditRecord>,
): Promise<void> {
await client.query(
`INSERT INTO "ql3"."security_audit_events" (
event_id, request_id, operation_id, project_id, subject_type,
subject_id, authentication_id, outcome, reasons, project_version,
binding_version, occurred_at_ms
) VALUES ($1::uuid, $2, $3, $4, $5, $6, $7, $8, $9::jsonb, $10, $11, $12)`,
[
audit.eventId,
audit.requestId,
audit.operationId,
audit.projectId,
audit.subject?.type ?? null,
audit.subject?.id ?? null,
audit.authenticationId,
audit.outcome,
JSON.stringify(audit.reasons),
audit.fence?.projectVersion ?? null,
audit.fence?.bindingVersion ?? null,
audit.occurredAtMs,
],
);
}
export class PostgresModelProviderCredentialTestPlanRepository
implements ModelProviderCredentialTestPlanRepository
{
readonly #quotaWindowMs: number;
readonly #quotaLimit: number;
constructor(
private readonly pool: PostgresPool,
optionValue?: PostgresModelProviderCredentialTestPlanOptions,
) {
if (!pool || typeof pool.connect !== 'function') {
throw new TypeError(
'PostgreSQL model provider credential test plan pool is invalid',
);
}
const reviewed = options(optionValue);
this.#quotaWindowMs = reviewed.quotaWindowMs;
this.#quotaLimit = reviewed.quotaLimit;
}
async createAuthorized(
value: AuthorizedModelProviderCredentialTestPlan,
): Promise<Readonly<CreateModelProviderCredentialTestPlanResult>> {
const authorized = normalizeAuthorized(value);
let client: PostgresClient;
try {
client = await this.pool.connect();
} catch (error) {
throw new ModelProviderCredentialTestPlanUnavailableError({
cause: error,
});
}
try {
await client.query('BEGIN ISOLATION LEVEL SERIALIZABLE');
await client.query(
'SELECT pg_advisory_xact_lock(hashtextextended($1, 0))',
[
JSON.stringify([
authorized.plan.projectId,
'model-provider-credential-test-plan',
authorized.plan.requestedBy.id,
]),
],
);
await confirmFence(client, authorized);
const storedPlans = await plansByIdentity(client, authorized.plan);
const storedAudit = await auditById(client, authorized.audit.eventId);
if (storedPlans.length > 0 || storedAudit) {
if (
storedPlans.length !== 1 ||
!samePlan(storedPlans[0]!, authorized.plan) ||
!storedAudit ||
!semanticAuditEqual(storedAudit, authorized.audit) ||
!(await quotaHasReceipt(client, authorized.plan))
) {
throw new ModelProviderCredentialTestPlanConflictError();
}
await client.query('COMMIT');
return Object.freeze({
status: 'existing' as const,
plan: storedPlans[0]!,
});
}
await consumeQuota(
client,
authorized.plan,
this.#quotaWindowMs,
this.#quotaLimit,
);
await insertPlan(client, authorized.plan);
await insertAudit(client, authorized.audit);
await client.query('COMMIT');
return Object.freeze({
status: 'created' as const,
plan: authorized.plan,
});
} catch (error) {
await rollback(client);
throw mapError(error);
} finally {
client.release();
}
}
}
@@ -0,0 +1,106 @@
import type { PostgresPool } from '@qinglong/runtime-core';
import {
POSTGRES_MODEL_INVOCATION_MIGRATION_STREAM_ID,
postgresModelInvocationMigrationDefinition,
} from '../../migration/modelInvocationMigration';
import {
PostgresModelProviderCredentialTesterNotReadyError,
type PostgresModelProviderCredentialTesterReadinessReport,
} from './contracts';
import type { Row } from './common';
export async function assertPostgresModelProviderCredentialTesterReady(
pool: PostgresPool,
): Promise<Readonly<PostgresModelProviderCredentialTesterReadinessReport>> {
if (!pool || typeof pool.query !== 'function') {
throw new PostgresModelProviderCredentialTesterNotReadyError();
}
try {
const history = await pool.query<Row>(
`SELECT migration_id AS "migrationId", stream_id AS "streamId",
dialect, checksum
FROM "ql3_ai"."ai_schema_migrations"
ORDER BY migration_id`,
);
const expected = postgresModelInvocationMigrationDefinition.migrations;
if (
history.rows.length !== expected.length ||
history.rows.some(
(row, index) =>
row.migrationId !== expected[index]!.id ||
row.streamId !== POSTGRES_MODEL_INVOCATION_MIGRATION_STREAM_ID ||
row.dialect !== 'postgresql' ||
row.checksum !== expected[index]!.checksum,
)
) {
throw new Error('migration history is invalid');
}
const facts = await pool.query<Row>(
`SELECT current_user AS "currentUser",
(NOT pg_is_in_recovery()) AND
current_setting('transaction_read_only') = 'off'
AS "writablePrimary",
pg_has_role(current_user, 'ql3_ai_credential_tester', 'member') AND
NOT pg_has_role(current_user, 'ql3_ai_credential_manager', 'member') AND
NOT pg_has_role(current_user, 'ql3_ai_maintenance', 'member') AND
NOT pg_has_role(current_user, 'ql3_runtime', 'member') AND
NOT pg_has_role(current_user, 'ql3_admin', 'member')
AS "testerAuthority",
has_schema_privilege(current_user, 'ql3_ai', 'USAGE') AND
NOT has_schema_privilege(current_user, 'ql3_ai', 'CREATE') AND
has_table_privilege(current_user, 'ql3_ai.ai_schema_migrations', 'SELECT') AND
NOT has_table_privilege(current_user, 'ql3_ai.ai_schema_migrations', 'INSERT,UPDATE,DELETE') AND
has_table_privilege(current_user, 'ql3_ai.model_provider_credential_bindings', 'SELECT') AND
NOT has_table_privilege(current_user, 'ql3_ai.model_provider_credential_bindings', 'INSERT,UPDATE,DELETE') AND
has_table_privilege(current_user, 'ql3_ai.model_provider_credential_transitions', 'SELECT') AND
NOT has_table_privilege(current_user, 'ql3_ai.model_provider_credential_transitions', 'INSERT,UPDATE,DELETE') AND
has_table_privilege(current_user, 'ql3_ai.model_provider_credential_audits', 'SELECT,INSERT') AND
NOT has_table_privilege(current_user, 'ql3_ai.model_provider_credential_audits', 'UPDATE,DELETE') AND
has_table_privilege(current_user, 'ql3_ai.model_provider_credential_test_plans', 'SELECT') AND
NOT has_table_privilege(current_user, 'ql3_ai.model_provider_credential_test_plans', 'INSERT,UPDATE,DELETE') AND
has_table_privilege(current_user, 'ql3_ai.model_provider_credential_test_executions', 'SELECT,INSERT') AND
NOT has_table_privilege(current_user, 'ql3_ai.model_provider_credential_test_executions', 'UPDATE,DELETE') AND
has_table_privilege(current_user, 'ql3_ai.model_provider_credential_test_results', 'SELECT,INSERT') AND
NOT has_table_privilege(current_user, 'ql3_ai.model_provider_credential_test_results', 'UPDATE,DELETE') AND
NOT has_table_privilege(current_user, 'ql3_ai.model_provider_credential_test_quota_buckets', 'SELECT,INSERT,UPDATE,DELETE') AND
NOT has_schema_privilege(current_user, 'ql3', 'USAGE') AND
NOT has_table_privilege(
current_user,
(
SELECT relation.oid
FROM pg_catalog.pg_class AS relation
JOIN pg_catalog.pg_namespace AS namespace
ON namespace.oid = relation.relnamespace
WHERE namespace.nspname = 'ql3'
AND relation.relname = 'security_audit_events'
),
'SELECT,INSERT,UPDATE,DELETE'
) AND
NOT has_table_privilege(current_user, 'ql3_ai.model_invocation_prompt_output_artifacts', 'SELECT,INSERT,UPDATE,DELETE')
AS "leastPrivilege"`,
);
const row = facts.rows[0];
if (
facts.rows.length !== 1 ||
typeof row?.currentUser !== 'string' ||
row.writablePrimary !== true ||
row.testerAuthority !== true ||
row.leastPrivilege !== true
) {
throw new Error('tester authority is invalid');
}
return Object.freeze({
ready: true as const,
currentUser: row.currentUser,
migrationIds: Object.freeze(expected.map(({ id }) => id)),
writablePrimary: true as const,
testerAuthority: true as const,
leastPrivilege: true as const,
});
} catch (error) {
throw new PostgresModelProviderCredentialTesterNotReadyError({
cause: error instanceof Error ? error : undefined,
});
}
}
@@ -0,0 +1,568 @@
import type { PostgresClient, PostgresPool } from '@qinglong/runtime-core';
import { normalizeProjectPolicySubject } from '@qinglong/runtime-core/project-policy';
import type {
SecurityPolicyFence,
SecuritySubject,
} from '@qinglong/runtime-core/security';
import {
normalizeSecurityAuditRecord,
type SecurityAuditRecord,
} from '@qinglong/runtime-core/security-audit';
// Durable audit read model owned by the credential-management capability.
const UUID_V4_PATTERN =
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
const IDENTITY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
const MANAGEMENT_OPERATIONS = Object.freeze([
'model_provider_credential.bind',
'model_provider_credential.revoke',
] as const);
type Row = Record<string, unknown>;
export const MAX_MODEL_PROVIDER_CREDENTIAL_MANAGEMENT_AUDIT_PAGE_SIZE = 32;
export const MODEL_PROVIDER_CREDENTIAL_MANAGEMENT_AUDIT_QUERY_OPERATION_ID =
'model_provider_credential.audit.list';
export interface ModelProviderCredentialManagementAuditCursor {
readonly occurredAtMs: number;
readonly eventId: string;
}
export interface ModelProviderCredentialManagementAuditQuery {
readonly schemaVersion: 1;
readonly queryId: string;
readonly requestId: string;
readonly projectId: string;
readonly limit: number;
readonly before?: ModelProviderCredentialManagementAuditCursor;
}
export interface ModelProviderCredentialManagementAuditRecord {
readonly eventId: string;
readonly requestId: string;
readonly operation: 'provider-credential.bind' | 'provider-credential.revoke';
readonly actor: Readonly<{ type: 'user'; id: string }>;
readonly fence: Readonly<{
projectVersion: number;
bindingVersion: number;
}>;
readonly occurredAtMs: number;
}
export interface ModelProviderCredentialManagementAuditPage {
readonly projectId: string;
readonly records: readonly Readonly<ModelProviderCredentialManagementAuditRecord>[];
readonly nextCursor: Readonly<ModelProviderCredentialManagementAuditCursor> | null;
}
export interface AuthorizedModelProviderCredentialManagementAuditQuery {
readonly query: ModelProviderCredentialManagementAuditQuery;
readonly actor: SecuritySubject;
readonly fence: SecurityPolicyFence;
readonly audit: SecurityAuditRecord;
}
export interface ModelProviderCredentialManagementAuditQueryRepository {
listAuthorized(
query: AuthorizedModelProviderCredentialManagementAuditQuery,
): Promise<Readonly<ModelProviderCredentialManagementAuditPage>>;
}
export class InvalidModelProviderCredentialManagementAuditQueryError extends TypeError {
readonly code = 'MODEL_PROVIDER_CREDENTIAL_MANAGEMENT_AUDIT_QUERY_INVALID';
constructor() {
super('Model provider credential management audit query is invalid');
this.name = 'InvalidModelProviderCredentialManagementAuditQueryError';
}
}
export class ModelProviderCredentialManagementAuditAuthorizationFenceConflictError extends Error {
readonly code =
'MODEL_PROVIDER_CREDENTIAL_MANAGEMENT_AUDIT_AUTHORIZATION_FENCE_CONFLICT';
constructor() {
super(
'Model provider credential management audit authorization fence changed',
);
this.name =
'ModelProviderCredentialManagementAuditAuthorizationFenceConflictError';
}
}
export class ModelProviderCredentialManagementAuditConflictError extends Error {
readonly code = 'MODEL_PROVIDER_CREDENTIAL_MANAGEMENT_AUDIT_CONFLICT';
constructor() {
super(
'Model provider credential management audit query conflicts with durable state',
);
this.name = 'ModelProviderCredentialManagementAuditConflictError';
}
}
export class ModelProviderCredentialManagementAuditUnavailableError extends Error {
readonly code = 'MODEL_PROVIDER_CREDENTIAL_MANAGEMENT_AUDIT_UNAVAILABLE';
constructor(options?: ErrorOptions) {
super('Model provider credential management audit is unavailable', options);
this.name = 'ModelProviderCredentialManagementAuditUnavailableError';
}
}
function exactKeys(value: object, expected: readonly string[]): boolean {
const actual = Object.keys(value).sort();
const canonical = [...expected].sort();
return (
actual.length === canonical.length &&
actual.every((key, index) => key === canonical[index])
);
}
function invalid(): never {
throw new InvalidModelProviderCredentialManagementAuditQueryError();
}
function positiveInteger(value: unknown): number {
const normalized =
typeof value === 'string' && /^(0|[1-9]\d*)$/.test(value)
? Number(value)
: value;
if (!Number.isSafeInteger(normalized) || (normalized as number) < 1) {
throw new ModelProviderCredentialManagementAuditUnavailableError();
}
return normalized as number;
}
function nonNegativeInteger(value: unknown): number {
const normalized =
typeof value === 'string' && /^(0|[1-9]\d*)$/.test(value)
? Number(value)
: value;
if (!Number.isSafeInteger(normalized) || (normalized as number) < 0) {
throw new ModelProviderCredentialManagementAuditUnavailableError();
}
return normalized as number;
}
function string(value: unknown, pattern: RegExp = IDENTITY_PATTERN): string {
if (typeof value !== 'string' || !pattern.test(value)) {
throw new ModelProviderCredentialManagementAuditUnavailableError();
}
return value;
}
function normalizeFence(value: SecurityPolicyFence): Readonly<{
projectVersion: number;
bindingVersion: number;
}> {
if (
!value ||
typeof value !== 'object' ||
Array.isArray(value) ||
!exactKeys(value, ['bindingVersion', 'projectVersion']) ||
!Number.isSafeInteger(value.projectVersion) ||
value.projectVersion < 1 ||
!Number.isSafeInteger(value.bindingVersion) ||
(value.bindingVersion as number) < 1
) {
invalid();
}
return Object.freeze({
projectVersion: value.projectVersion,
bindingVersion: value.bindingVersion as number,
});
}
export function normalizeModelProviderCredentialManagementAuditQuery(
value: ModelProviderCredentialManagementAuditQuery,
): Readonly<ModelProviderCredentialManagementAuditQuery> {
if (
!value ||
typeof value !== 'object' ||
Array.isArray(value) ||
!exactKeys(
value,
value.before === undefined
? ['limit', 'projectId', 'queryId', 'requestId', 'schemaVersion']
: [
'before',
'limit',
'projectId',
'queryId',
'requestId',
'schemaVersion',
],
) ||
value.schemaVersion !== 1 ||
!UUID_V4_PATTERN.test(value.queryId) ||
!IDENTITY_PATTERN.test(value.requestId) ||
!IDENTITY_PATTERN.test(value.projectId) ||
!Number.isSafeInteger(value.limit) ||
value.limit < 1 ||
value.limit > MAX_MODEL_PROVIDER_CREDENTIAL_MANAGEMENT_AUDIT_PAGE_SIZE
) {
invalid();
}
let before:
| Readonly<ModelProviderCredentialManagementAuditCursor>
| undefined;
if (value.before !== undefined) {
if (
!value.before ||
typeof value.before !== 'object' ||
Array.isArray(value.before) ||
!exactKeys(value.before, ['eventId', 'occurredAtMs']) ||
!Number.isSafeInteger(value.before.occurredAtMs) ||
value.before.occurredAtMs < 0 ||
!UUID_V4_PATTERN.test(value.before.eventId)
) {
invalid();
}
before = Object.freeze({ ...value.before });
}
return Object.freeze({
schemaVersion: 1 as const,
queryId: value.queryId,
requestId: value.requestId,
projectId: value.projectId,
limit: value.limit,
...(before ? { before } : {}),
});
}
function sameSubject(
left: Readonly<SecuritySubject>,
right: Readonly<SecuritySubject>,
): boolean {
return left.type === right.type && left.id === right.id;
}
export function normalizeAuthorizedModelProviderCredentialManagementAuditQuery(
value: AuthorizedModelProviderCredentialManagementAuditQuery,
): Readonly<AuthorizedModelProviderCredentialManagementAuditQuery> {
if (
!value ||
typeof value !== 'object' ||
Array.isArray(value) ||
!exactKeys(value, ['actor', 'audit', 'fence', 'query'])
) {
invalid();
}
try {
const query = normalizeModelProviderCredentialManagementAuditQuery(
value.query,
);
const actor = normalizeProjectPolicySubject(value.actor);
const fence = normalizeFence(value.fence);
const audit = normalizeSecurityAuditRecord(value.audit);
if (
actor.type !== 'user' ||
audit.eventId !== query.queryId ||
audit.requestId !== query.requestId ||
audit.operationId !==
MODEL_PROVIDER_CREDENTIAL_MANAGEMENT_AUDIT_QUERY_OPERATION_ID ||
audit.projectId !== query.projectId ||
audit.outcome !== 'allowed' ||
!audit.subject ||
!sameSubject(audit.subject, actor) ||
audit.authenticationId === null ||
!audit.fence ||
audit.fence.projectVersion !== fence.projectVersion ||
audit.fence.bindingVersion !== fence.bindingVersion
) {
invalid();
}
return Object.freeze({ query, actor, fence, audit });
} catch (error) {
if (
error instanceof InvalidModelProviderCredentialManagementAuditQueryError
) {
throw error;
}
invalid();
}
}
function auditRecordFromRow(row: Row): Readonly<SecurityAuditRecord> {
const reasons = row.reasons;
if (!Array.isArray(reasons)) {
throw new ModelProviderCredentialManagementAuditUnavailableError();
}
try {
return normalizeSecurityAuditRecord({
eventId: string(row.eventId, UUID_V4_PATTERN),
requestId: string(row.requestId),
operationId: string(row.operationId),
projectId: string(row.projectId),
subject: {
type: string(row.subjectType) as NonNullable<
SecurityAuditRecord['subject']
>['type'],
id: string(row.subjectId),
},
authenticationId: string(row.authenticationId),
outcome: string(row.outcome) as SecurityAuditRecord['outcome'],
reasons: reasons as string[],
fence: {
projectVersion: positiveInteger(row.projectVersion),
bindingVersion: positiveInteger(row.bindingVersion),
},
occurredAtMs: nonNegativeInteger(row.occurredAtMs),
});
} catch (error) {
if (
error instanceof ModelProviderCredentialManagementAuditUnavailableError
) {
throw error;
}
throw new ModelProviderCredentialManagementAuditUnavailableError({
cause: error instanceof Error ? error : undefined,
});
}
}
function contentFreeRecord(
audit: Readonly<SecurityAuditRecord>,
): Readonly<ModelProviderCredentialManagementAuditRecord> {
if (
!MANAGEMENT_OPERATIONS.includes(
audit.operationId as (typeof MANAGEMENT_OPERATIONS)[number],
) ||
audit.outcome !== 'allowed' ||
audit.subject?.type !== 'user' ||
!audit.fence ||
audit.fence.bindingVersion === null
) {
throw new ModelProviderCredentialManagementAuditUnavailableError();
}
return Object.freeze({
eventId: audit.eventId,
requestId: audit.requestId,
operation:
audit.operationId === 'model_provider_credential.bind'
? ('provider-credential.bind' as const)
: ('provider-credential.revoke' as const),
actor: Object.freeze({ type: 'user' as const, id: audit.subject.id }),
fence: Object.freeze({
projectVersion: audit.fence.projectVersion,
bindingVersion: audit.fence.bindingVersion,
}),
occurredAtMs: audit.occurredAtMs,
});
}
async function rollback(client: PostgresClient): Promise<void> {
await client.query('ROLLBACK').catch(() => undefined);
}
function sqlState(error: unknown): string {
if (!error || typeof error !== 'object') return '';
const code = (error as { code?: unknown }).code;
return typeof code === 'string' ? code : '';
}
function semanticAuditEqual(
left: Readonly<SecurityAuditRecord>,
right: Readonly<SecurityAuditRecord>,
): boolean {
const { occurredAtMs: _leftTime, ...leftSemantic } = left;
const { occurredAtMs: _rightTime, ...rightSemantic } = right;
return JSON.stringify(leftSemantic) === JSON.stringify(rightSemantic);
}
async function confirmFence(
client: PostgresClient,
value: Readonly<AuthorizedModelProviderCredentialManagementAuditQuery>,
): Promise<void> {
const project = await client.query<Row>(
`SELECT status, version FROM "ql3"."projects" WHERE id = $1`,
[value.query.projectId],
);
const binding = await client.query<Row>(
`SELECT version, state
FROM "ql3"."project_role_bindings"
WHERE project_id = $1 AND subject_type = $2 AND subject_id = $3
ORDER BY version DESC
LIMIT 1`,
[value.query.projectId, value.actor.type, value.actor.id],
);
if (
project.rows.length !== 1 ||
project.rows[0]?.status !== 'active' ||
positiveInteger(project.rows[0]?.version) !== value.fence.projectVersion ||
binding.rows.length !== 1 ||
binding.rows[0]?.state !== 'active' ||
positiveInteger(binding.rows[0]?.version) !== value.fence.bindingVersion
) {
throw new ModelProviderCredentialManagementAuditAuthorizationFenceConflictError();
}
}
async function auditById(
client: PostgresClient,
eventId: string,
): Promise<Readonly<SecurityAuditRecord> | null> {
const result = await client.query<Row>(
`SELECT event_id AS "eventId", request_id AS "requestId",
operation_id AS "operationId", project_id AS "projectId",
subject_type AS "subjectType", subject_id AS "subjectId",
authentication_id AS "authenticationId", outcome, reasons,
project_version AS "projectVersion",
binding_version AS "bindingVersion",
occurred_at_ms AS "occurredAtMs"
FROM "ql3"."security_audit_events"
WHERE event_id = $1
LIMIT 2`,
[eventId],
);
if (result.rows.length > 1) {
throw new ModelProviderCredentialManagementAuditConflictError();
}
return result.rows[0] ? auditRecordFromRow(result.rows[0]) : null;
}
async function insertAudit(
client: PostgresClient,
audit: Readonly<SecurityAuditRecord>,
): Promise<void> {
await client.query(
`INSERT INTO "ql3"."security_audit_events" (
event_id, request_id, operation_id, project_id, subject_type,
subject_id, authentication_id, outcome, reasons, project_version,
binding_version, occurred_at_ms
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::jsonb, $10, $11, $12)`,
[
audit.eventId,
audit.requestId,
audit.operationId,
audit.projectId,
audit.subject?.type ?? null,
audit.subject?.id ?? null,
audit.authenticationId,
audit.outcome,
JSON.stringify(audit.reasons),
audit.fence?.projectVersion ?? null,
audit.fence?.bindingVersion ?? null,
audit.occurredAtMs,
],
);
}
function mapError(error: unknown): Error {
if (
error instanceof InvalidModelProviderCredentialManagementAuditQueryError ||
error instanceof
ModelProviderCredentialManagementAuditAuthorizationFenceConflictError ||
error instanceof ModelProviderCredentialManagementAuditConflictError ||
error instanceof ModelProviderCredentialManagementAuditUnavailableError
) {
return error;
}
if (['23503', '23505', '23514', '40001', '40P01'].includes(sqlState(error))) {
return new ModelProviderCredentialManagementAuditConflictError();
}
return new ModelProviderCredentialManagementAuditUnavailableError({
cause: error instanceof Error ? error : undefined,
});
}
export class PostgresModelProviderCredentialManagementAuditQueryRepository
implements ModelProviderCredentialManagementAuditQueryRepository
{
constructor(private readonly pool: PostgresPool) {
if (!pool || typeof pool.connect !== 'function') {
throw new TypeError(
'PostgreSQL model provider credential management audit pool is invalid',
);
}
}
async listAuthorized(
input: AuthorizedModelProviderCredentialManagementAuditQuery,
): Promise<Readonly<ModelProviderCredentialManagementAuditPage>> {
const authorized =
normalizeAuthorizedModelProviderCredentialManagementAuditQuery(input);
let client: PostgresClient;
try {
client = await this.pool.connect();
} catch (error) {
throw new ModelProviderCredentialManagementAuditUnavailableError({
cause: error,
});
}
try {
await client.query('BEGIN ISOLATION LEVEL SERIALIZABLE');
await client.query(
'SELECT pg_advisory_xact_lock(hashtextextended($1, 0))',
[
JSON.stringify([
authorized.query.projectId,
'model-provider-credential-audit-query',
authorized.actor.type,
authorized.actor.id,
]),
],
);
await confirmFence(client, authorized);
const storedAudit = await auditById(client, authorized.audit.eventId);
if (storedAudit && !semanticAuditEqual(storedAudit, authorized.audit)) {
throw new ModelProviderCredentialManagementAuditConflictError();
}
const values: unknown[] = [authorized.query.projectId];
const before = authorized.query.before;
let cursor = '';
if (before) {
values.push(before.occurredAtMs, before.eventId);
cursor = 'AND (occurred_at_ms, event_id) < ($2, $3::uuid)';
}
values.push(authorized.query.limit + 1);
const limitParameter = `$${values.length}`;
const result = await client.query<Row>(
`SELECT event_id AS "eventId", request_id AS "requestId",
operation_id AS "operationId", project_id AS "projectId",
subject_type AS "subjectType", subject_id AS "subjectId",
authentication_id AS "authenticationId", outcome, reasons,
project_version AS "projectVersion",
binding_version AS "bindingVersion",
occurred_at_ms AS "occurredAtMs"
FROM "ql3"."security_audit_events"
WHERE project_id = $1
AND operation_id IN (
'model_provider_credential.bind',
'model_provider_credential.revoke'
)
${cursor}
ORDER BY occurred_at_ms DESC, event_id DESC
LIMIT ${limitParameter}`,
values,
);
const hasMore = result.rows.length > authorized.query.limit;
const records = Object.freeze(
result.rows
.slice(0, authorized.query.limit)
.map((row) => contentFreeRecord(auditRecordFromRow(row))),
);
const last = records.at(-1);
if (!storedAudit) await insertAudit(client, authorized.audit);
await client.query('COMMIT');
return Object.freeze({
projectId: authorized.query.projectId,
records,
nextCursor:
hasMore && last
? Object.freeze({
occurredAtMs: last.occurredAtMs,
eventId: last.eventId,
})
: null,
});
} catch (error) {
await rollback(client);
throw mapError(error);
} finally {
client.release();
}
}
}
@@ -0,0 +1,408 @@
import type { PostgresClient, PostgresPool } from '@qinglong/runtime-core';
import {
POSTGRES_MODEL_INVOCATION_MIGRATION_STREAM_ID,
POSTGRES_MODEL_INVOCATION_SCHEMA,
postgresModelInvocationMigrationDefinition,
} from '../migration/modelInvocationMigration';
const AUTHORITY = 'model-provider-credential-management';
const DIGEST_PATTERN = /^[A-Za-z0-9_-]{43}$/;
const KEY_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
const CONTROL_PATTERN = /[\u0000-\u001f\u007f]/;
type Row = Record<string, unknown>;
export interface ModelProviderCredentialManagementIdentityKeysetSnapshot {
readonly schemaVersion: 1;
readonly generation: number;
readonly digest: string;
readonly issuer: string;
readonly audience: string;
readonly activeKeyIds: readonly string[];
readonly revokedKeyIds: readonly string[];
}
export interface PostgresModelProviderCredentialManagerReadinessReport {
readonly ready: true;
readonly currentUser: string;
readonly migrationIds: readonly string[];
readonly writablePrimary: true;
readonly managerAuthority: true;
readonly leastPrivilege: true;
}
export class PostgresModelProviderCredentialManagementIdentityLedgerConflictError extends Error {
readonly code =
'POSTGRES_MODEL_PROVIDER_CREDENTIAL_MANAGEMENT_IDENTITY_LEDGER_CONFLICT';
constructor() {
super(
'PostgreSQL model provider credential management identity ledger conflicts',
);
this.name =
'PostgresModelProviderCredentialManagementIdentityLedgerConflictError';
}
}
export class PostgresModelProviderCredentialManagementIdentityLedgerUnavailableError extends Error {
readonly code =
'POSTGRES_MODEL_PROVIDER_CREDENTIAL_MANAGEMENT_IDENTITY_LEDGER_UNAVAILABLE';
constructor(options?: ErrorOptions) {
super(
'PostgreSQL model provider credential management identity ledger is unavailable',
options,
);
this.name =
'PostgresModelProviderCredentialManagementIdentityLedgerUnavailableError';
}
}
export class PostgresModelProviderCredentialManagerNotReadyError extends Error {
readonly code = 'POSTGRES_MODEL_PROVIDER_CREDENTIAL_MANAGER_NOT_READY';
constructor(options?: ErrorOptions) {
super('PostgreSQL model provider credential manager is not ready', options);
this.name = 'PostgresModelProviderCredentialManagerNotReadyError';
}
}
function reviewedKeyIds(
value: readonly string[],
minimum: number,
maximum: number,
): readonly string[] {
if (
!Array.isArray(value) ||
value.length < minimum ||
value.length > maximum
) {
throw new TypeError(
'Model provider credential management identity key ids are invalid',
);
}
const seen = new Set<string>();
for (const keyId of value) {
if (
typeof keyId !== 'string' ||
!KEY_ID_PATTERN.test(keyId) ||
seen.has(keyId)
) {
throw new TypeError(
'Model provider credential management identity key ids are invalid',
);
}
seen.add(keyId);
}
return Object.freeze([...value].sort());
}
function reviewedSnapshot(
value: Readonly<ModelProviderCredentialManagementIdentityKeysetSnapshot>,
): Readonly<ModelProviderCredentialManagementIdentityKeysetSnapshot> {
if (
!value ||
typeof value !== 'object' ||
Array.isArray(value) ||
Object.keys(value).length !== 7 ||
Object.keys(value).some(
(key) =>
![
'schemaVersion',
'generation',
'digest',
'issuer',
'audience',
'activeKeyIds',
'revokedKeyIds',
].includes(key),
) ||
value.schemaVersion !== 1 ||
!Number.isSafeInteger(value.generation) ||
value.generation < 1 ||
typeof value.digest !== 'string' ||
!DIGEST_PATTERN.test(value.digest) ||
typeof value.issuer !== 'string' ||
value.issuer.length < 1 ||
value.issuer.length > 512 ||
CONTROL_PATTERN.test(value.issuer) ||
typeof value.audience !== 'string' ||
value.audience.length < 1 ||
value.audience.length > 256 ||
CONTROL_PATTERN.test(value.audience)
) {
throw new TypeError(
'Model provider credential management identity snapshot is invalid',
);
}
const activeKeyIds = reviewedKeyIds(value.activeKeyIds, 1, 8);
const revokedKeyIds = reviewedKeyIds(value.revokedKeyIds, 0, 64);
if (activeKeyIds.some((keyId) => revokedKeyIds.includes(keyId))) {
throw new TypeError(
'Model provider credential management identity snapshot is invalid',
);
}
return Object.freeze({ ...value, activeKeyIds, revokedKeyIds });
}
function integer(row: Row, name: string): number {
const value = Number(row[name]);
if (!Number.isSafeInteger(value) || value < 1)
throw new Error(`invalid ${name}`);
return value;
}
function text(row: Row, name: string): string {
const value = row[name];
if (typeof value !== 'string') throw new Error(`invalid ${name}`);
return value;
}
function textArray(row: Row, name: string): readonly string[] {
const value = row[name];
if (
!Array.isArray(value) ||
value.some((candidate) => typeof candidate !== 'string')
) {
throw new Error(`invalid ${name}`);
}
return value;
}
function sameArray(left: readonly string[], right: readonly string[]): boolean {
return (
left.length === right.length &&
left.every((value, index) => value === right[index])
);
}
function includesAll(
candidate: readonly string[],
required: readonly string[],
): boolean {
const values = new Set(candidate);
return required.every((value) => values.has(value));
}
async function rollback(client: PostgresClient): Promise<void> {
await client.query('ROLLBACK').catch(() => undefined);
}
export class PostgresModelProviderCredentialManagementIdentityLedgerRepository {
constructor(private readonly pool: PostgresPool) {
if (!pool || typeof pool.connect !== 'function') {
throw new TypeError(
'PostgreSQL model provider credential management identity pool is invalid',
);
}
}
async observe(
value: Readonly<ModelProviderCredentialManagementIdentityKeysetSnapshot>,
): Promise<void> {
const candidate = reviewedSnapshot(value);
let client: PostgresClient;
try {
client = await this.pool.connect();
} catch (error) {
throw new PostgresModelProviderCredentialManagementIdentityLedgerUnavailableError(
{
cause: error,
},
);
}
try {
await client.query('BEGIN');
await client.query(
`INSERT INTO "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_management_identity_keyset_ledger" (
authority, generation, digest, issuer, audience,
active_key_ids, revoked_key_ids, updated_at_ms
) VALUES (
$1, $2, $3, $4, $5, $6::jsonb, $7::jsonb,
floor(extract(epoch FROM clock_timestamp()) * 1000)::bigint
) ON CONFLICT (authority) DO NOTHING`,
[
AUTHORITY,
candidate.generation,
candidate.digest,
candidate.issuer,
candidate.audience,
JSON.stringify(candidate.activeKeyIds),
JSON.stringify(candidate.revokedKeyIds),
],
);
const selected = await client.query<Row>(
`SELECT generation, digest, issuer, audience,
active_key_ids AS "activeKeyIds",
revoked_key_ids AS "revokedKeyIds"
FROM "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_management_identity_keyset_ledger"
WHERE authority = $1
FOR UPDATE`,
[AUTHORITY],
);
if (selected.rows.length !== 1)
throw new Error('identity ledger row is missing');
const current = selected.rows[0]!;
const generation = integer(current, 'generation');
const digest = text(current, 'digest');
const issuer = text(current, 'issuer');
const audience = text(current, 'audience');
const activeKeyIds = textArray(current, 'activeKeyIds');
const revokedKeyIds = textArray(current, 'revokedKeyIds');
const exact =
generation === candidate.generation &&
digest === candidate.digest &&
issuer === candidate.issuer &&
audience === candidate.audience &&
sameArray(activeKeyIds, candidate.activeKeyIds) &&
sameArray(revokedKeyIds, candidate.revokedKeyIds);
if (!exact) {
const retained = [
...candidate.activeKeyIds,
...candidate.revokedKeyIds,
];
if (
candidate.generation <= generation ||
candidate.issuer !== issuer ||
candidate.audience !== audience ||
!includesAll(candidate.revokedKeyIds, revokedKeyIds) ||
!includesAll(retained, activeKeyIds)
) {
throw new PostgresModelProviderCredentialManagementIdentityLedgerConflictError();
}
await client.query(
`UPDATE "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_provider_credential_management_identity_keyset_ledger"
SET generation = $2,
digest = $3,
active_key_ids = $4::jsonb,
revoked_key_ids = $5::jsonb,
updated_at_ms =
floor(extract(epoch FROM clock_timestamp()) * 1000)::bigint
WHERE authority = $1`,
[
AUTHORITY,
candidate.generation,
candidate.digest,
JSON.stringify(candidate.activeKeyIds),
JSON.stringify(candidate.revokedKeyIds),
],
);
}
await client.query('COMMIT');
} catch (error) {
await rollback(client);
if (
error instanceof
PostgresModelProviderCredentialManagementIdentityLedgerConflictError
) {
throw error;
}
throw new PostgresModelProviderCredentialManagementIdentityLedgerUnavailableError(
{
cause: error,
},
);
} finally {
client.release();
}
}
}
export async function assertPostgresModelProviderCredentialManagerReady(
pool: PostgresPool,
): Promise<Readonly<PostgresModelProviderCredentialManagerReadinessReport>> {
if (!pool || typeof pool.query !== 'function') {
throw new PostgresModelProviderCredentialManagerNotReadyError();
}
try {
const history = await pool.query<Row>(
`SELECT migration_id AS "migrationId", stream_id AS "streamId",
dialect, checksum
FROM "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."ai_schema_migrations"
ORDER BY migration_id`,
);
const expected = postgresModelInvocationMigrationDefinition.migrations;
if (
history.rows.length !== expected.length ||
history.rows.some(
(row, index) =>
row.migrationId !== expected[index]!.id ||
row.streamId !== POSTGRES_MODEL_INVOCATION_MIGRATION_STREAM_ID ||
row.dialect !== 'postgresql' ||
row.checksum !== expected[index]!.checksum,
)
) {
throw new Error('migration history is invalid');
}
const facts = await pool.query<Row>(
`SELECT current_user AS "currentUser",
(NOT pg_is_in_recovery()) AND
current_setting('transaction_read_only') = 'off'
AS "writablePrimary",
pg_has_role(current_user, 'ql3_ai_credential_manager', 'member') AND
NOT pg_has_role(current_user, 'ql3_ai_credential_tester', 'member') AND
NOT pg_has_role(current_user, 'ql3_ai_maintenance', 'member') AND
NOT pg_has_role(current_user, 'ql3_runtime', 'member') AND
NOT pg_has_role(current_user, 'ql3_admin', 'member')
AS "managerAuthority",
has_schema_privilege(current_user, 'ql3', 'USAGE') AND
NOT has_schema_privilege(current_user, 'ql3', 'CREATE') AND
has_schema_privilege(current_user, '${POSTGRES_MODEL_INVOCATION_SCHEMA}', 'USAGE') AND
NOT has_schema_privilege(current_user, '${POSTGRES_MODEL_INVOCATION_SCHEMA}', 'CREATE') AND
has_table_privilege(current_user, 'ql3.projects', 'SELECT') AND
NOT has_table_privilege(current_user, 'ql3.projects', 'INSERT,UPDATE,DELETE') AND
has_table_privilege(current_user, 'ql3.project_role_bindings', 'SELECT') AND
NOT has_table_privilege(current_user, 'ql3.project_role_bindings', 'INSERT,UPDATE,DELETE') AND
has_table_privilege(current_user, 'ql3.security_audit_events', 'SELECT,INSERT') AND
NOT has_table_privilege(current_user, 'ql3.security_audit_events', 'UPDATE,DELETE') AND
has_table_privilege(current_user, '${POSTGRES_MODEL_INVOCATION_SCHEMA}.ai_schema_migrations', 'SELECT') AND
NOT has_table_privilege(current_user, '${POSTGRES_MODEL_INVOCATION_SCHEMA}.ai_schema_migrations', 'INSERT,UPDATE,DELETE') AND
has_table_privilege(current_user, '${POSTGRES_MODEL_INVOCATION_SCHEMA}.model_provider_credential_bindings', 'SELECT,INSERT') AND
NOT has_table_privilege(current_user, '${POSTGRES_MODEL_INVOCATION_SCHEMA}.model_provider_credential_bindings', 'UPDATE,DELETE') AND
has_table_privilege(current_user, '${POSTGRES_MODEL_INVOCATION_SCHEMA}.model_provider_credential_transitions', 'SELECT,INSERT') AND
NOT has_table_privilege(current_user, '${POSTGRES_MODEL_INVOCATION_SCHEMA}.model_provider_credential_transitions', 'UPDATE,DELETE') AND
has_table_privilege(current_user, '${POSTGRES_MODEL_INVOCATION_SCHEMA}.model_provider_credential_audits', 'SELECT') AND
NOT has_table_privilege(current_user, '${POSTGRES_MODEL_INVOCATION_SCHEMA}.model_provider_credential_audits', 'INSERT,UPDATE,DELETE') AND
has_table_privilege(current_user, '${POSTGRES_MODEL_INVOCATION_SCHEMA}.model_provider_credential_management_identity_keyset_ledger', 'SELECT,INSERT,UPDATE') AND
NOT has_table_privilege(current_user, '${POSTGRES_MODEL_INVOCATION_SCHEMA}.model_provider_credential_management_identity_keyset_ledger', 'DELETE') AND
has_table_privilege(current_user, '${POSTGRES_MODEL_INVOCATION_SCHEMA}.model_provider_credential_test_plans', 'SELECT,INSERT') AND
NOT has_table_privilege(current_user, '${POSTGRES_MODEL_INVOCATION_SCHEMA}.model_provider_credential_test_plans', 'UPDATE,DELETE') AND
has_table_privilege(current_user, '${POSTGRES_MODEL_INVOCATION_SCHEMA}.model_provider_credential_test_quota_buckets', 'SELECT,INSERT,UPDATE') AND
NOT has_table_privilege(current_user, '${POSTGRES_MODEL_INVOCATION_SCHEMA}.model_provider_credential_test_quota_buckets', 'DELETE') AND
has_table_privilege(current_user, '${POSTGRES_MODEL_INVOCATION_SCHEMA}.model_provider_credential_test_executions', 'SELECT') AND
NOT has_table_privilege(current_user, '${POSTGRES_MODEL_INVOCATION_SCHEMA}.model_provider_credential_test_executions', 'INSERT,UPDATE,DELETE') AND
has_table_privilege(current_user, '${POSTGRES_MODEL_INVOCATION_SCHEMA}.model_provider_credential_test_results', 'SELECT') AND
NOT has_table_privilege(current_user, '${POSTGRES_MODEL_INVOCATION_SCHEMA}.model_provider_credential_test_results', 'INSERT,UPDATE,DELETE') AND
NOT has_table_privilege(current_user, '${POSTGRES_MODEL_INVOCATION_SCHEMA}.model_invocation_prompt_output_artifacts', 'SELECT,INSERT,UPDATE,DELETE') AND
NOT has_table_privilege(current_user, '${POSTGRES_MODEL_INVOCATION_SCHEMA}.model_invocation_prompt_output_key_retirement_preparations', 'SELECT,INSERT,UPDATE,DELETE') AND
NOT has_table_privilege(current_user, '${POSTGRES_MODEL_INVOCATION_SCHEMA}.model_price_catalog_publications', 'SELECT,INSERT,UPDATE,DELETE')
AS "leastPrivilege"`,
);
const row = facts.rows[0];
if (
facts.rows.length !== 1 ||
typeof row?.currentUser !== 'string' ||
row.writablePrimary !== true ||
row.managerAuthority !== true ||
row.leastPrivilege !== true
) {
throw new Error('manager authority is invalid');
}
return Object.freeze({
ready: true as const,
currentUser: row.currentUser,
migrationIds: Object.freeze(expected.map(({ id }) => id)),
writablePrimary: true as const,
managerAuthority: true as const,
leastPrivilege: true as const,
});
} catch (error) {
if (error instanceof PostgresModelProviderCredentialManagerNotReadyError) {
throw error;
}
throw new PostgresModelProviderCredentialManagerNotReadyError({
cause: error,
});
}
}
@@ -0,0 +1,2 @@
export { PostgresModelProviderCredentialReader } from './postgres-model-provider-credential-repository/reader';
export { PostgresModelProviderCredentialRepository } from './postgres-model-provider-credential-repository/repository';
@@ -0,0 +1,23 @@
export {
MODEL_PROVIDER_CREDENTIAL_TEST_PLAN_OPERATION_ID,
ModelProviderCredentialTestExecutionConflictError,
ModelProviderCredentialTestExecutionRejectedError,
ModelProviderCredentialTestExecutionUnavailableError,
ModelProviderCredentialTestPlanAuthorizationFenceConflictError,
ModelProviderCredentialTestPlanConflictError,
ModelProviderCredentialTestPlanQuotaExceededError,
ModelProviderCredentialTestPlanUnavailableError,
PostgresModelProviderCredentialTesterNotReadyError,
type AuthorizedModelProviderCredentialTestPlan,
type BeginModelProviderCredentialTestExecutionInput,
type BeginModelProviderCredentialTestExecutionResult,
type CompleteModelProviderCredentialTestExecutionResult,
type CreateModelProviderCredentialTestPlanResult,
type ModelProviderCredentialTestExecutionRepository,
type ModelProviderCredentialTestPlanRepository,
type PostgresModelProviderCredentialTestPlanOptions,
type PostgresModelProviderCredentialTesterReadinessReport,
} from './postgres-model-provider-credential-test-connection/contracts';
export { PostgresModelProviderCredentialTestPlanRepository } from './postgres-model-provider-credential-test-connection/planRepository';
export { PostgresModelProviderCredentialTestExecutionRepository } from './postgres-model-provider-credential-test-connection/executionRepository';
export { assertPostgresModelProviderCredentialTesterReady } from './postgres-model-provider-credential-test-connection/readiness';
@@ -0,0 +1,235 @@
import { Buffer } from 'node:buffer';
import { createHash } from 'node:crypto';
import { constants } from 'node:fs';
import { lstat, open, realpath } from 'node:fs/promises';
import { isAbsolute, join, normalize, parse, relative } from 'node:path';
import { parseSecretRef } from '@qinglong/runtime-core/secret-reference';
import {
MAX_MODEL_PROVIDER_AUTHORIZATION_BYTES,
type ModelProviderSecretMaterial,
type ModelProviderSecretMaterialProvider,
type ModelProviderSecretMaterialRequest,
} from './providerCredential';
const MAX_ROOT_DIRECTORY_BYTES = 4096;
const SECRET_FILE_NAME = /^[0-9a-f]{64}$/;
export interface ProjectedModelProviderSecretMaterialOptions {
/**
* Direct read-only volume root. Each key is the lowercase SHA-256 of one
* canonical SecretRef. Kubernetes atomic-writer symlinks are accepted only
* when their resolved regular file remains below this root.
*/
readonly rootDirectory: string;
}
export class ProjectedModelProviderSecretMaterialUnavailableError extends Error {
readonly code = 'PROJECTED_MODEL_PROVIDER_SECRET_MATERIAL_UNAVAILABLE';
constructor(options?: ErrorOptions) {
super('Projected model provider Secret material is unavailable', options);
this.name = 'ProjectedModelProviderSecretMaterialUnavailableError';
}
}
function unavailable(
cause?: unknown,
): ProjectedModelProviderSecretMaterialUnavailableError {
return new ProjectedModelProviderSecretMaterialUnavailableError({
cause: cause instanceof Error ? cause : undefined,
});
}
function rootDirectory(value: unknown): string {
if (
typeof value !== 'string' ||
!isAbsolute(value) ||
parse(value).root === value ||
normalize(value) !== value ||
value.includes('\0') ||
Buffer.byteLength(value, 'utf8') > MAX_ROOT_DIRECTORY_BYTES
) {
throw unavailable();
}
return value;
}
function remainsBelow(root: string, candidate: string): boolean {
const suffix = relative(root, candidate);
return (
suffix.length > 0 &&
!isAbsolute(suffix) &&
suffix !== '..' &&
!suffix.startsWith(`..${process.platform === 'win32' ? '\\' : '/'}`)
);
}
async function resolvedRoot(configuredRoot: string): Promise<string> {
try {
const stat = await lstat(configuredRoot);
if (!stat.isDirectory() || stat.isSymbolicLink()) throw unavailable();
return await realpath(configuredRoot);
} catch (cause) {
throw cause instanceof ProjectedModelProviderSecretMaterialUnavailableError
? cause
: unavailable(cause);
}
}
export function projectedModelProviderSecretFileName(
secretRef: string,
): string {
try {
parseSecretRef(secretRef);
} catch (cause) {
throw unavailable(cause);
}
const result = createHash('sha256').update(secretRef, 'utf8').digest('hex');
if (!SECRET_FILE_NAME.test(result)) throw unavailable();
return result;
}
function normalizeRequest(
value: Readonly<ModelProviderSecretMaterialRequest>,
): Readonly<ModelProviderSecretMaterialRequest> {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw unavailable();
}
const expected = ['projectId', 'secretRef'];
if (value.signal !== undefined) expected.push('signal');
if (
Object.keys(value).sort().join('\0') !== expected.sort().join('\0') ||
typeof value.projectId !== 'string' ||
value.projectId.length < 1 ||
Buffer.byteLength(value.projectId, 'utf8') > 128 ||
(value.signal !== undefined && typeof value.signal.aborted !== 'boolean')
) {
throw unavailable();
}
try {
if (parseSecretRef(value.secretRef).projectId !== value.projectId) {
throw unavailable();
}
} catch (cause) {
throw cause instanceof ProjectedModelProviderSecretMaterialUnavailableError
? cause
: unavailable(cause);
}
if (value.signal?.aborted) throw unavailable(value.signal.reason);
return Object.freeze({
projectId: value.projectId,
secretRef: value.secretRef,
...(value.signal === undefined ? {} : { signal: value.signal }),
});
}
async function readMaterial(
configuredRoot: string,
secretRef: string,
): Promise<Buffer> {
let handle: Awaited<ReturnType<typeof open>> | undefined;
let bytes: Buffer | undefined;
try {
const root = await resolvedRoot(configuredRoot);
const candidate = join(
root,
projectedModelProviderSecretFileName(secretRef),
);
const target = await realpath(candidate);
if (!remainsBelow(root, target)) throw unavailable();
handle = await open(
target,
constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0),
);
const before = await handle.stat();
if (
!before.isFile() ||
before.nlink !== 1 ||
before.size < 1 ||
before.size > MAX_MODEL_PROVIDER_AUTHORIZATION_BYTES ||
(before.mode & 0o222) !== 0 ||
(before.mode & 0o111) !== 0 ||
(before.mode & 0o007) !== 0 ||
(before.mode & 0o440) === 0
) {
throw unavailable();
}
bytes = await handle.readFile();
const after = await handle.stat();
if (
bytes.byteLength !== before.size ||
after.dev !== before.dev ||
after.ino !== before.ino ||
after.size !== before.size ||
after.mtimeMs !== before.mtimeMs ||
(await realpath(candidate)) !== target ||
(await realpath(configuredRoot)) !== root
) {
throw unavailable();
}
const owned = bytes;
bytes = undefined;
return owned;
} catch (cause) {
throw cause instanceof ProjectedModelProviderSecretMaterialUnavailableError
? cause
: unavailable(cause);
} finally {
bytes?.fill(0);
await handle?.close().catch(() => undefined);
}
}
/**
* Zero-client, zero-cache Cluster material provider for Kubernetes Secret,
* CSI, or Secret-operator projections. Every authorization reopens exactly one
* bounded file, so an atomic projection replacement rotates an unpinned
* SecretRef without a watcher, timer, API credential, or process restart.
*/
export class ProjectedModelProviderSecretMaterialProvider
implements ModelProviderSecretMaterialProvider
{
readonly #rootDirectory: string;
constructor(options: ProjectedModelProviderSecretMaterialOptions) {
if (!options || typeof options !== 'object' || Array.isArray(options)) {
throw unavailable();
}
this.#rootDirectory = rootDirectory(options.rootDirectory);
}
async verify(): Promise<void> {
await resolvedRoot(this.#rootDirectory);
}
async resolveProjectSecretMaterial(
requestValue: Readonly<ModelProviderSecretMaterialRequest>,
): Promise<Readonly<ModelProviderSecretMaterial>> {
const request = normalizeRequest(requestValue);
const bytes = await readMaterial(this.#rootDirectory, request.secretRef);
if (request.signal?.aborted) {
bytes.fill(0);
throw unavailable(request.signal.reason);
}
let disposed = false;
return Object.freeze({
secretRef: request.secretRef,
bytes,
dispose(): void {
if (disposed) return;
disposed = true;
bytes.fill(0);
},
});
}
}
export async function createProjectedModelProviderSecretMaterialProvider(
options: ProjectedModelProviderSecretMaterialOptions,
): Promise<Readonly<ProjectedModelProviderSecretMaterialProvider>> {
const provider = new ProjectedModelProviderSecretMaterialProvider(options);
await provider.verify();
return provider;
}
@@ -0,0 +1,445 @@
import { Buffer } from 'node:buffer';
import { createHash } from 'node:crypto';
import { parseSecretRef } from '@qinglong/runtime-core/secret-reference';
// Core contract for the model-provider-credential bounded capability.
export const MODEL_PROVIDER_CREDENTIAL_BINDING_SCHEMA =
'qinglong/model-provider-credential-binding@v1';
export const MODEL_PROVIDER_CREDENTIAL_AUDIT_SCHEMA =
'qinglong/model-provider-credential-audit@v1';
export const MODEL_PROVIDER_CREDENTIAL_OPERATIONS = [
'list_models',
'generate',
'stream',
] as const;
export const MAX_MODEL_PROVIDER_AUTHORIZATION_BYTES = 4 * 1024;
const BEARER_PREFIX = 'Bearer ';
const BEARER_TOKEN_PATTERN = /^[A-Za-z0-9\-._~+/]+=*$/;
export type ModelProviderCredentialOperation =
(typeof MODEL_PROVIDER_CREDENTIAL_OPERATIONS)[number];
export interface ModelProviderCredentialBinding {
readonly schema: typeof MODEL_PROVIDER_CREDENTIAL_BINDING_SCHEMA;
readonly projectId: string;
readonly provider: string;
readonly revision: string;
readonly secretRef: string;
readonly scheme: 'bearer';
}
export interface ModelProviderCredentialBindingLookup {
readonly projectId: string;
readonly provider: string;
}
export interface ModelProviderCredentialBindingSource {
resolveModelProviderCredentialBinding(
lookup: Readonly<ModelProviderCredentialBindingLookup>,
): Promise<Readonly<ModelProviderCredentialBinding> | null>;
}
export interface ModelProviderSecretMaterialRequest {
readonly projectId: string;
readonly secretRef: string;
readonly signal?: AbortSignal;
}
export interface ModelProviderSecretMaterial {
readonly secretRef: string;
/**
* Consumer-owned plaintext bytes. The consumer must call dispose exactly
* once and must not retain a string or another copy beyond the operation.
*/
readonly bytes: Uint8Array;
dispose(): void | Promise<void>;
}
export interface ModelProviderSecretMaterialProvider {
resolveProjectSecretMaterial(
request: Readonly<ModelProviderSecretMaterialRequest>,
): Promise<Readonly<ModelProviderSecretMaterial> | null>;
}
export interface ModelProviderAuthorizationRequest {
readonly operation: ModelProviderCredentialOperation;
readonly provider: string;
readonly projectId?: string;
readonly requestId?: string;
readonly signal?: AbortSignal;
}
export interface ModelProviderAuthorizationLease {
readonly value: string;
dispose(): void | Promise<void>;
}
export interface ModelProviderAuthorizationProvider {
authorizationHeader(
request: Readonly<ModelProviderAuthorizationRequest>,
): Promise<Readonly<ModelProviderAuthorizationLease> | null>;
}
export interface ModelProviderCredentialAuditRecord {
readonly schema: typeof MODEL_PROVIDER_CREDENTIAL_AUDIT_SCHEMA;
readonly operation: ModelProviderCredentialOperation;
readonly projectId: string;
readonly provider: string;
readonly requestId: string;
readonly bindingRevision: string;
readonly bindingDigest: string;
readonly occurredAtMs: number;
}
export interface ModelProviderCredentialAuditSink {
record(record: Readonly<ModelProviderCredentialAuditRecord>): Promise<void>;
}
export interface BoundModelProviderCredentialOptions {
readonly bindings: ModelProviderCredentialBindingSource;
readonly secrets: ModelProviderSecretMaterialProvider;
readonly audit: ModelProviderCredentialAuditSink;
readonly now?: () => number;
}
export class InvalidModelProviderCredentialBindingError extends TypeError {
readonly code = 'MODEL_PROVIDER_CREDENTIAL_BINDING_INVALID';
constructor(message: string) {
super(`Model provider credential binding is invalid: ${message}`);
this.name = 'InvalidModelProviderCredentialBindingError';
}
}
export class ModelProviderCredentialUnavailableError extends Error {
readonly code = 'MODEL_PROVIDER_CREDENTIAL_UNAVAILABLE';
constructor(options?: ErrorOptions) {
super('The model provider credential is unavailable', options);
this.name = 'ModelProviderCredentialUnavailableError';
}
}
function exactKeys(value: object, expected: readonly string[]): boolean {
const actual = Object.keys(value).sort();
const sortedExpected = [...expected].sort();
return (
actual.length === sortedExpected.length &&
actual.every((key, index) => key === sortedExpected[index])
);
}
function record(value: unknown, label: string): Record<string, unknown> {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new InvalidModelProviderCredentialBindingError(
`${label} is not an object`,
);
}
return value as Record<string, unknown>;
}
function identifier(value: unknown, label: string, maximumBytes = 128): string {
if (
typeof value !== 'string' ||
value.length === 0 ||
Buffer.byteLength(value, 'utf8') > maximumBytes ||
/[\u0000-\u001f\u007f]/.test(value)
) {
throw new InvalidModelProviderCredentialBindingError(`${label} is invalid`);
}
return value;
}
function providerIdentifier(value: unknown): string {
const provider = identifier(value, 'provider');
if (!/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/.test(provider)) {
throw new InvalidModelProviderCredentialBindingError('provider is invalid');
}
return provider;
}
function operation(value: unknown): ModelProviderCredentialOperation {
if (
typeof value !== 'string' ||
!MODEL_PROVIDER_CREDENTIAL_OPERATIONS.includes(
value as ModelProviderCredentialOperation,
)
) {
throw new InvalidModelProviderCredentialBindingError(
'operation is invalid',
);
}
return value as ModelProviderCredentialOperation;
}
function timestamp(value: unknown): number {
if (!Number.isSafeInteger(value) || (value as number) < 0) {
throw new ModelProviderCredentialUnavailableError();
}
return value as number;
}
export function normalizeModelProviderCredentialBinding(
value: ModelProviderCredentialBinding,
): Readonly<ModelProviderCredentialBinding> {
const binding = record(value, 'binding');
if (
!exactKeys(binding, [
'projectId',
'provider',
'revision',
'schema',
'scheme',
'secretRef',
]) ||
binding.schema !== MODEL_PROVIDER_CREDENTIAL_BINDING_SCHEMA ||
binding.scheme !== 'bearer'
) {
throw new InvalidModelProviderCredentialBindingError('shape is invalid');
}
const projectId = identifier(binding.projectId, 'projectId');
const provider = providerIdentifier(binding.provider);
const revision = identifier(binding.revision, 'revision');
let secretRef: string;
try {
secretRef = identifier(binding.secretRef, 'secretRef', 512);
if (parseSecretRef(secretRef).projectId !== projectId) {
throw new InvalidModelProviderCredentialBindingError(
'Secret belongs to another Project',
);
}
} catch (error) {
if (error instanceof InvalidModelProviderCredentialBindingError) {
throw error;
}
throw new InvalidModelProviderCredentialBindingError(
'secretRef is invalid',
);
}
return Object.freeze({
schema: MODEL_PROVIDER_CREDENTIAL_BINDING_SCHEMA,
projectId,
provider,
revision,
secretRef,
scheme: 'bearer',
});
}
export function digestModelProviderCredentialBinding(
value: ModelProviderCredentialBinding,
): string {
const binding = normalizeModelProviderCredentialBinding(value);
const hash = createHash('sha256');
hash.update('qinglong/model-provider-credential-binding@v1\0', 'utf8');
hash.update(binding.projectId, 'utf8');
hash.update('\0', 'utf8');
hash.update(binding.provider, 'utf8');
hash.update('\0', 'utf8');
hash.update(binding.revision, 'utf8');
hash.update('\0', 'utf8');
hash.update(binding.secretRef, 'utf8');
hash.update('\0bearer', 'utf8');
return `sha256:${hash.digest('hex')}`;
}
function normalizeAuthorizationRequest(
value: Readonly<ModelProviderAuthorizationRequest>,
): Readonly<
Required<Omit<ModelProviderAuthorizationRequest, 'signal'>> & {
readonly signal?: AbortSignal;
}
> {
const request = record(value, 'authorization request');
const expected = ['operation', 'projectId', 'provider', 'requestId'];
if (request.signal !== undefined) expected.push('signal');
if (
!exactKeys(request, expected) ||
request.projectId === undefined ||
request.requestId === undefined ||
(request.signal !== undefined &&
typeof (request.signal as AbortSignal).aborted !== 'boolean')
) {
throw new InvalidModelProviderCredentialBindingError(
'authorization request is invalid',
);
}
return Object.freeze({
operation: operation(request.operation),
provider: providerIdentifier(request.provider),
projectId: identifier(request.projectId, 'projectId'),
requestId: identifier(request.requestId, 'requestId'),
...(request.signal === undefined
? {}
: { signal: request.signal as AbortSignal }),
});
}
function normalizeMaterial(
value: Readonly<ModelProviderSecretMaterial>,
expectedSecretRef: string,
): Readonly<ModelProviderSecretMaterial> {
const material = record(value, 'Secret material');
if (
!exactKeys(material, ['bytes', 'dispose', 'secretRef']) ||
material.secretRef !== expectedSecretRef ||
!(material.bytes instanceof Uint8Array) ||
typeof material.dispose !== 'function'
) {
throw new ModelProviderCredentialUnavailableError();
}
return value;
}
async function disposeMaterial(
material: Readonly<ModelProviderSecretMaterial> | undefined,
): Promise<void> {
if (!material || typeof material.dispose !== 'function') return;
await material.dispose();
}
function bearerToken(bytes: Uint8Array): Buffer {
const token = Buffer.from(bytes);
if (
token.length === 0 ||
token.length >
MAX_MODEL_PROVIDER_AUTHORIZATION_BYTES - BEARER_PREFIX.length ||
token.some((byte) => byte > 0x7f)
) {
token.fill(0);
throw new ModelProviderCredentialUnavailableError();
}
const value = token.toString('ascii');
if (
!BEARER_TOKEN_PATTERN.test(value) ||
Buffer.byteLength(value, 'ascii') !== token.length
) {
token.fill(0);
throw new ModelProviderCredentialUnavailableError();
}
return token;
}
/**
* Per-invocation credential bridge. Bindings are metadata-only and are
* re-resolved for every request, so an unversioned SecretRef rotates without a
* watcher or cache. Plaintext is owned only by a short-lived authorization
* lease and is wiped before the provider starts reading its response body.
*/
export class BoundModelProviderCredentialProvider
implements ModelProviderAuthorizationProvider
{
readonly #bindings: ModelProviderCredentialBindingSource;
readonly #secrets: ModelProviderSecretMaterialProvider;
readonly #audit: ModelProviderCredentialAuditSink;
readonly #now: () => number;
constructor(options: BoundModelProviderCredentialOptions) {
if (
!options ||
typeof options !== 'object' ||
Array.isArray(options) ||
typeof options.bindings?.resolveModelProviderCredentialBinding !==
'function' ||
typeof options.secrets?.resolveProjectSecretMaterial !== 'function' ||
typeof options.audit?.record !== 'function' ||
(options.now !== undefined && typeof options.now !== 'function')
) {
throw new InvalidModelProviderCredentialBindingError(
'provider options are invalid',
);
}
this.#bindings = options.bindings;
this.#secrets = options.secrets;
this.#audit = options.audit;
this.#now = options.now ?? Date.now;
}
async authorizationHeader(
request: Readonly<ModelProviderAuthorizationRequest>,
): Promise<Readonly<ModelProviderAuthorizationLease>> {
let material: Readonly<ModelProviderSecretMaterial> | undefined;
let token: Buffer | undefined;
try {
const normalizedRequest = normalizeAuthorizationRequest(request);
if (normalizedRequest.signal?.aborted) {
throw new ModelProviderCredentialUnavailableError();
}
const rawBinding =
await this.#bindings.resolveModelProviderCredentialBinding(
Object.freeze({
projectId: normalizedRequest.projectId,
provider: normalizedRequest.provider,
}),
);
if (!rawBinding) throw new ModelProviderCredentialUnavailableError();
const binding = normalizeModelProviderCredentialBinding(rawBinding);
if (
binding.projectId !== normalizedRequest.projectId ||
binding.provider !== normalizedRequest.provider
) {
throw new ModelProviderCredentialUnavailableError();
}
const bindingDigest = digestModelProviderCredentialBinding(binding);
material =
(await this.#secrets.resolveProjectSecretMaterial(
Object.freeze({
projectId: normalizedRequest.projectId,
secretRef: binding.secretRef,
...(normalizedRequest.signal === undefined
? {}
: { signal: normalizedRequest.signal }),
}),
)) ?? undefined;
if (!material) throw new ModelProviderCredentialUnavailableError();
const normalizedMaterial = normalizeMaterial(material, binding.secretRef);
token = bearerToken(normalizedMaterial.bytes);
await disposeMaterial(material);
material = undefined;
if (normalizedRequest.signal?.aborted) {
throw new ModelProviderCredentialUnavailableError();
}
await this.#audit.record(
Object.freeze({
schema: MODEL_PROVIDER_CREDENTIAL_AUDIT_SCHEMA,
operation: normalizedRequest.operation,
projectId: normalizedRequest.projectId,
provider: normalizedRequest.provider,
requestId: normalizedRequest.requestId,
bindingRevision: binding.revision,
bindingDigest,
occurredAtMs: timestamp(this.#now()),
}),
);
const ownedToken = token;
token = undefined;
let disposed = false;
return Object.freeze({
get value(): string {
if (disposed) throw new ModelProviderCredentialUnavailableError();
return `${BEARER_PREFIX}${ownedToken.toString('ascii')}`;
},
dispose(): void {
if (disposed) return;
disposed = true;
ownedToken.fill(0);
},
});
} catch (cause) {
token?.fill(0);
try {
await disposeMaterial(material);
} catch {
// The stable fail-closed error below intentionally hides provider data.
}
if (cause instanceof InvalidModelProviderCredentialBindingError) {
throw cause;
}
throw new ModelProviderCredentialUnavailableError({
cause: cause instanceof Error ? cause : undefined,
});
}
}
}
@@ -0,0 +1,349 @@
import { Buffer } from 'node:buffer';
import {
createModelPriceCatalogPublishCommand,
createModelPriceCatalogTransitionCommand,
type ModelPriceCatalogPublishCommand,
type ModelPriceCatalogTransitionCommand,
} from '../modelPriceCatalog';
import {
MAX_MODEL_PRICE_CATALOG_PRINCIPAL_AGE_MS,
MODEL_PRICE_CATALOG_AUTHORIZATION_COMMAND_SCHEMA,
MODEL_PRICE_CATALOG_AUTHORIZATION_SCHEMA,
MODEL_PRICE_CATALOG_POLICY_DECISION_SCHEMA,
type ModelPriceCatalogAuthorization,
type ModelPriceCatalogAuthorizationCommand,
type ModelPriceCatalogPolicyDecision,
} from './contracts';
import {
decisionMode,
digest,
exactKeys,
hash,
identity,
integer,
invalid,
normalizeReasons,
normalizeStoredPrincipal,
nullableIdentity,
operation,
record,
requestId,
validateOperationRevision,
} from './validation';
const POLICY_DECISION_DIGEST_DOMAIN = Buffer.from(
'qinglong/model-price-catalog-policy-decision-digest@v1\0',
'utf8',
);
const AUTHORIZATION_COMMAND_DIGEST_DOMAIN = Buffer.from(
'qinglong/model-price-catalog-authorization-command-digest@v1\0',
'utf8',
);
const AUTHORIZATION_DIGEST_DOMAIN = Buffer.from(
'qinglong/model-price-catalog-authorization-digest@v1\0',
'utf8',
);
export function createModelPriceCatalogPolicyDecision(
value: Omit<ModelPriceCatalogPolicyDecision, 'schema' | 'decisionDigest'>,
): Readonly<ModelPriceCatalogPolicyDecision> {
const candidate = record(value, 'policy decision');
exactKeys(candidate, ['effect', 'revision', 'reasons'], 'policy decision');
if (value.effect !== 'allow' && value.effect !== 'deny') {
return invalid('policy effect is invalid');
}
const semantic = Object.freeze({
schema: MODEL_PRICE_CATALOG_POLICY_DECISION_SCHEMA,
effect: value.effect,
revision: identity(value.revision, 'policy revision'),
reasons: normalizeReasons(value.reasons),
});
return Object.freeze({
...semantic,
decisionDigest: hash(POLICY_DECISION_DIGEST_DOMAIN, semantic),
});
}
export function normalizeModelPriceCatalogPolicyDecision(
value: ModelPriceCatalogPolicyDecision,
): Readonly<ModelPriceCatalogPolicyDecision> {
const candidate = record(value, 'policy decision');
exactKeys(
candidate,
['schema', 'effect', 'revision', 'reasons', 'decisionDigest'],
'policy decision',
);
if (value.schema !== MODEL_PRICE_CATALOG_POLICY_DECISION_SCHEMA) {
return invalid('policy decision schema is invalid');
}
const normalized = createModelPriceCatalogPolicyDecision({
effect: value.effect,
revision: value.revision,
reasons: value.reasons,
});
if (normalized.decisionDigest !== value.decisionDigest) {
return invalid('policy decision digest is invalid');
}
return normalized;
}
export function createModelPriceCatalogAuthorizationCommand(
value: Omit<
ModelPriceCatalogAuthorizationCommand,
'schema' | 'commandDigest'
>,
): Readonly<ModelPriceCatalogAuthorizationCommand> {
const candidate = record(value, 'authorization command');
exactKeys(
candidate,
[
'authorizationId',
'requestId',
'operation',
'provider',
'model',
'priceRevision',
'catalogCommandDigest',
'principal',
'policy',
'decisionMode',
],
'authorization command',
);
const normalizedOperation = operation(value.operation);
const priceRevision = nullableIdentity(
value.priceRevision,
'authorization price revision',
);
validateOperationRevision(normalizedOperation, priceRevision);
const principal = normalizeStoredPrincipal(value.principal);
const policy = normalizeModelPriceCatalogPolicyDecision(value.policy);
if (policy.effect !== 'allow') {
return invalid('authorization policy must allow');
}
const semantic = Object.freeze({
schema: MODEL_PRICE_CATALOG_AUTHORIZATION_COMMAND_SCHEMA,
authorizationId: identity(value.authorizationId, 'authorization identity'),
requestId: requestId(value.requestId),
operation: normalizedOperation,
provider: identity(value.provider, 'authorization provider'),
model: identity(value.model, 'authorization model'),
priceRevision,
catalogCommandDigest: digest(
value.catalogCommandDigest,
'catalog command digest',
),
principal,
policy,
decisionMode: decisionMode(value.decisionMode),
});
return Object.freeze({
...semantic,
commandDigest: hash(AUTHORIZATION_COMMAND_DIGEST_DOMAIN, semantic),
});
}
export function normalizeModelPriceCatalogAuthorizationCommand(
value: ModelPriceCatalogAuthorizationCommand,
): Readonly<ModelPriceCatalogAuthorizationCommand> {
const candidate = record(value, 'authorization command');
exactKeys(
candidate,
[
'schema',
'authorizationId',
'requestId',
'operation',
'provider',
'model',
'priceRevision',
'catalogCommandDigest',
'principal',
'policy',
'decisionMode',
'commandDigest',
],
'authorization command',
);
if (value.schema !== MODEL_PRICE_CATALOG_AUTHORIZATION_COMMAND_SCHEMA) {
return invalid('authorization command schema is invalid');
}
const normalized = createModelPriceCatalogAuthorizationCommand({
authorizationId: value.authorizationId,
requestId: value.requestId,
operation: value.operation,
provider: value.provider,
model: value.model,
priceRevision: value.priceRevision,
catalogCommandDigest: value.catalogCommandDigest,
principal: value.principal,
policy: value.policy,
decisionMode: value.decisionMode,
});
if (normalized.commandDigest !== value.commandDigest) {
return invalid('authorization command digest is invalid');
}
return normalized;
}
export function normalizeModelPriceCatalogPublishAuthorization(
catalogCommandValue: Readonly<ModelPriceCatalogPublishCommand>,
authorizationValue: Readonly<ModelPriceCatalogAuthorizationCommand>,
): Readonly<ModelPriceCatalogAuthorizationCommand> {
const catalogCommand = createModelPriceCatalogPublishCommand({
provider: catalogCommandValue.provider,
model: catalogCommandValue.model,
priceRevision: catalogCommandValue.priceRevision,
currency: catalogCommandValue.currency,
inputMicrosPerMillionTokens:
catalogCommandValue.inputMicrosPerMillionTokens,
outputMicrosPerMillionTokens:
catalogCommandValue.outputMicrosPerMillionTokens,
mutationId: catalogCommandValue.mutationId,
publishedByUserId: catalogCommandValue.publishedByUserId,
});
if (catalogCommand.commandDigest !== catalogCommandValue.commandDigest) {
return invalid('publish command digest is invalid');
}
const authorization =
normalizeModelPriceCatalogAuthorizationCommand(authorizationValue);
if (
authorization.operation !== 'publish' ||
authorization.provider !== catalogCommand.provider ||
authorization.model !== catalogCommand.model ||
authorization.priceRevision !== catalogCommand.priceRevision ||
authorization.catalogCommandDigest !== catalogCommand.commandDigest ||
authorization.principal.subject.id !== catalogCommand.publishedByUserId
) {
return invalid('publish authorization binding is invalid');
}
return authorization;
}
export function normalizeModelPriceCatalogTransitionAuthorization(
catalogCommandValue: Readonly<ModelPriceCatalogTransitionCommand>,
authorizationValue: Readonly<ModelPriceCatalogAuthorizationCommand>,
): Readonly<ModelPriceCatalogAuthorizationCommand> {
const catalogCommand = createModelPriceCatalogTransitionCommand({
provider: catalogCommandValue.provider,
model: catalogCommandValue.model,
expectedGeneration: catalogCommandValue.expectedGeneration,
expectedHeadDigest: catalogCommandValue.expectedHeadDigest,
action: catalogCommandValue.action,
priceRevision: catalogCommandValue.priceRevision,
mutationId: catalogCommandValue.mutationId,
changedByUserId: catalogCommandValue.changedByUserId,
});
if (catalogCommand.commandDigest !== catalogCommandValue.commandDigest) {
return invalid('transition command digest is invalid');
}
const authorization =
normalizeModelPriceCatalogAuthorizationCommand(authorizationValue);
if (
authorization.operation !== catalogCommand.action ||
authorization.provider !== catalogCommand.provider ||
authorization.model !== catalogCommand.model ||
authorization.priceRevision !== catalogCommand.priceRevision ||
authorization.catalogCommandDigest !== catalogCommand.commandDigest ||
authorization.principal.subject.id !== catalogCommand.changedByUserId
) {
return invalid('transition authorization binding is invalid');
}
return authorization;
}
export function createModelPriceCatalogAuthorization(
commandValue: Readonly<ModelPriceCatalogAuthorizationCommand>,
resultDigestValue: string,
committedAtMsValue: number,
): Readonly<ModelPriceCatalogAuthorization> {
const command = normalizeModelPriceCatalogAuthorizationCommand(commandValue);
const resultDigest = digest(resultDigestValue, 'authorization result digest');
const committedAtMs = integer(
committedAtMsValue,
'authorization commit time',
);
if (
committedAtMs < command.principal.authenticatedAtMs ||
committedAtMs >= command.principal.expiresAtMs ||
committedAtMs - command.principal.authenticatedAtMs >
MAX_MODEL_PRICE_CATALOG_PRINCIPAL_AGE_MS
) {
return invalid('authorization principal is not active at commit');
}
const semantic = Object.freeze({
schema: MODEL_PRICE_CATALOG_AUTHORIZATION_SCHEMA,
authorizationId: command.authorizationId,
requestId: command.requestId,
operation: command.operation,
provider: command.provider,
model: command.model,
priceRevision: command.priceRevision,
catalogCommandDigest: command.catalogCommandDigest,
resultDigest,
principal: command.principal,
policy: command.policy,
decisionMode: command.decisionMode,
commandDigest: command.commandDigest,
committedAtMs,
});
return Object.freeze({
...semantic,
authorizationDigest: hash(AUTHORIZATION_DIGEST_DOMAIN, semantic),
});
}
export function normalizeModelPriceCatalogAuthorization(
value: ModelPriceCatalogAuthorization,
): Readonly<ModelPriceCatalogAuthorization> {
const candidate = record(value, 'authorization');
exactKeys(
candidate,
[
'schema',
'authorizationId',
'requestId',
'operation',
'provider',
'model',
'priceRevision',
'catalogCommandDigest',
'resultDigest',
'principal',
'policy',
'decisionMode',
'commandDigest',
'committedAtMs',
'authorizationDigest',
],
'authorization',
);
if (value.schema !== MODEL_PRICE_CATALOG_AUTHORIZATION_SCHEMA) {
return invalid('authorization schema is invalid');
}
const command = createModelPriceCatalogAuthorizationCommand({
authorizationId: value.authorizationId,
requestId: value.requestId,
operation: value.operation,
provider: value.provider,
model: value.model,
priceRevision: value.priceRevision,
catalogCommandDigest: value.catalogCommandDigest,
principal: value.principal,
policy: value.policy,
decisionMode: value.decisionMode,
});
if (command.commandDigest !== value.commandDigest) {
return invalid('authorization command digest is invalid');
}
const normalized = createModelPriceCatalogAuthorization(
command,
value.resultDigest,
value.committedAtMs,
);
if (normalized.authorizationDigest !== value.authorizationDigest) {
return invalid('authorization digest is invalid');
}
return normalized;
}
@@ -0,0 +1,231 @@
import type {
SecurityPrincipal,
SecuritySubject,
} from '@qinglong/runtime-core/security';
import { MODEL_PRICE_CURRENCY } from '../pricing';
import type {
CommitModelPriceCatalogHeadResult,
CommitModelPriceCatalogPublicationResult,
ModelPriceCatalogAction,
ModelPriceCatalogAdministrationRepository,
ModelPriceCatalogPublishCommand,
ModelPriceCatalogTransitionCommand,
} from '../modelPriceCatalog';
export const MODEL_PRICE_CATALOG_POLICY_DECISION_SCHEMA =
'qinglong/model-price-catalog-policy-decision@v1' as const;
export const MODEL_PRICE_CATALOG_AUTHORIZATION_COMMAND_SCHEMA =
'qinglong/model-price-catalog-authorization-command@v1' as const;
export const MODEL_PRICE_CATALOG_AUTHORIZATION_SCHEMA =
'qinglong/model-price-catalog-authorization@v1' as const;
export const MODEL_PRICE_CATALOG_MANAGEMENT_OPERATIONS = [
'publish',
'activate',
'deactivate',
'revoke',
] as const;
export const MODEL_PRICE_CATALOG_MANAGEMENT_DECISION_MODES = [
'human_confirmation',
'separation_of_duty',
] as const;
export const MAX_MODEL_PRICE_CATALOG_PRINCIPAL_AGE_MS = 5 * 60 * 1000;
export type ModelPriceCatalogManagementOperation =
(typeof MODEL_PRICE_CATALOG_MANAGEMENT_OPERATIONS)[number];
export type ModelPriceCatalogManagementDecisionMode =
(typeof MODEL_PRICE_CATALOG_MANAGEMENT_DECISION_MODES)[number];
export interface ModelPriceCatalogPolicyDecision {
readonly schema: typeof MODEL_PRICE_CATALOG_POLICY_DECISION_SCHEMA;
readonly effect: 'allow' | 'deny';
readonly revision: string;
readonly reasons: readonly string[];
readonly decisionDigest: string;
}
export interface AuthorizeModelPriceCatalogManagementRequest {
readonly operation: ModelPriceCatalogManagementOperation;
readonly provider: string;
readonly model: string;
readonly priceRevision: string | null;
readonly requestId: string;
readonly principal: SecurityPrincipal;
}
export interface ModelPriceCatalogManagementAuthorizer {
authorize(
request: Readonly<AuthorizeModelPriceCatalogManagementRequest>,
): Promise<Readonly<ModelPriceCatalogPolicyDecision>>;
}
export interface ConsumeModelPriceCatalogManagementQuotaCommand {
readonly operation: ModelPriceCatalogManagementOperation;
readonly subject: SecuritySubject;
readonly idempotencyKey: string;
}
export interface ModelPriceCatalogManagementQuota {
consume(
command: Readonly<ConsumeModelPriceCatalogManagementQuotaCommand>,
): Promise<void>;
}
export interface ModelPriceCatalogAuthorizationCommand {
readonly schema: typeof MODEL_PRICE_CATALOG_AUTHORIZATION_COMMAND_SCHEMA;
readonly authorizationId: string;
readonly requestId: string;
readonly operation: ModelPriceCatalogManagementOperation;
readonly provider: string;
readonly model: string;
readonly priceRevision: string | null;
readonly catalogCommandDigest: string;
readonly principal: Readonly<SecurityPrincipal>;
readonly policy: Readonly<ModelPriceCatalogPolicyDecision>;
readonly decisionMode: ModelPriceCatalogManagementDecisionMode;
readonly commandDigest: string;
}
export interface ModelPriceCatalogAuthorization {
readonly schema: typeof MODEL_PRICE_CATALOG_AUTHORIZATION_SCHEMA;
readonly authorizationId: string;
readonly requestId: string;
readonly operation: ModelPriceCatalogManagementOperation;
readonly provider: string;
readonly model: string;
readonly priceRevision: string | null;
readonly catalogCommandDigest: string;
readonly resultDigest: string;
readonly principal: Readonly<SecurityPrincipal>;
readonly policy: Readonly<ModelPriceCatalogPolicyDecision>;
readonly decisionMode: ModelPriceCatalogManagementDecisionMode;
readonly commandDigest: string;
readonly committedAtMs: number;
readonly authorizationDigest: string;
}
export interface CommitAuthorizedModelPriceCatalogPublicationResult
extends CommitModelPriceCatalogPublicationResult {
readonly authorization: Readonly<ModelPriceCatalogAuthorization>;
}
export interface CommitAuthorizedModelPriceCatalogHeadResult
extends CommitModelPriceCatalogHeadResult {
readonly authorization: Readonly<ModelPriceCatalogAuthorization>;
}
export interface ModelPriceCatalogAuthorizedAdministrationRepository
extends ModelPriceCatalogAdministrationRepository {
findAuthorization(
authorizationId: string,
): Promise<Readonly<ModelPriceCatalogAuthorization> | null>;
publishAuthorized(
command: Readonly<ModelPriceCatalogPublishCommand>,
authorization: Readonly<ModelPriceCatalogAuthorizationCommand>,
): Promise<Readonly<CommitAuthorizedModelPriceCatalogPublicationResult>>;
transitionAuthorized(
command: Readonly<ModelPriceCatalogTransitionCommand>,
authorization: Readonly<ModelPriceCatalogAuthorizationCommand>,
): Promise<Readonly<CommitAuthorizedModelPriceCatalogHeadResult>>;
}
export interface BaseManagementRequest {
readonly authorizationId: string;
readonly requestId: string;
readonly mutationId: string;
readonly provider: string;
readonly model: string;
readonly principal: SecurityPrincipal;
}
export interface PublishModelPriceCatalogRequest extends BaseManagementRequest {
readonly priceRevision: string;
readonly currency: typeof MODEL_PRICE_CURRENCY;
readonly inputMicrosPerMillionTokens: number;
readonly outputMicrosPerMillionTokens: number;
}
export interface TransitionModelPriceCatalogRequest
extends BaseManagementRequest {
readonly expectedGeneration: number;
readonly expectedHeadDigest: string | null;
readonly action: ModelPriceCatalogAction;
readonly priceRevision: string | null;
}
export interface ModelPriceCatalogManagementService {
publish(
request: Readonly<PublishModelPriceCatalogRequest>,
): Promise<Readonly<CommitAuthorizedModelPriceCatalogPublicationResult>>;
transition(
request: Readonly<TransitionModelPriceCatalogRequest>,
): Promise<Readonly<CommitAuthorizedModelPriceCatalogHeadResult>>;
}
export interface CreateModelPriceCatalogManagementServiceOptions {
readonly decisionMode: ModelPriceCatalogManagementDecisionMode;
readonly authorizer: ModelPriceCatalogManagementAuthorizer;
readonly quota?: ModelPriceCatalogManagementQuota;
readonly now?: () => number;
}
export class InvalidModelPriceCatalogManagementValueError extends TypeError {
readonly code = 'MODEL_PRICE_CATALOG_MANAGEMENT_INVALID';
constructor(message: string) {
super(`Model price catalog management is invalid: ${message}`);
this.name = 'InvalidModelPriceCatalogManagementValueError';
}
}
export class ModelPriceCatalogManagementAuthenticationError extends Error {
readonly code = 'MODEL_PRICE_CATALOG_MANAGEMENT_AUTHENTICATION_REQUIRED';
constructor() {
super('Model price catalog management requires a recent strong User');
this.name = 'ModelPriceCatalogManagementAuthenticationError';
}
}
export class ModelPriceCatalogManagementAuthorizationError extends Error {
readonly code = 'MODEL_PRICE_CATALOG_MANAGEMENT_FORBIDDEN';
constructor() {
super('Model price catalog management is not authorized');
this.name = 'ModelPriceCatalogManagementAuthorizationError';
}
}
export class ModelPriceCatalogManagementSeparationOfDutyError extends Error {
readonly code = 'MODEL_PRICE_CATALOG_MANAGEMENT_SEPARATION_OF_DUTY_REQUIRED';
constructor() {
super('Model price activation requires a different publishing User');
this.name = 'ModelPriceCatalogManagementSeparationOfDutyError';
}
}
export class ModelPriceCatalogManagementQuotaExceededError extends Error {
readonly code = 'MODEL_PRICE_CATALOG_MANAGEMENT_QUOTA_EXCEEDED';
constructor(readonly retryAfterMs: number) {
if (
!Number.isSafeInteger(retryAfterMs) ||
retryAfterMs < 1 ||
retryAfterMs > 5 * 60 * 1000
) {
throw new TypeError('Model price catalog quota retry delay is invalid');
}
super('Model price catalog management quota is exhausted');
this.name = 'ModelPriceCatalogManagementQuotaExceededError';
}
}
export class ModelPriceCatalogManagementUnavailableError extends Error {
readonly code = 'MODEL_PRICE_CATALOG_MANAGEMENT_UNAVAILABLE';
constructor(options?: ErrorOptions) {
super('Model price catalog management is unavailable', options);
this.name = 'ModelPriceCatalogManagementUnavailableError';
}
}
@@ -0,0 +1,230 @@
import {
createModelPriceCatalogPublishCommand,
createModelPriceCatalogTransitionCommand,
type ModelPriceCatalogPublication,
} from '../modelPriceCatalog';
import {
createModelPriceCatalogAuthorizationCommand,
normalizeModelPriceCatalogPolicyDecision,
} from './authorization';
import {
InvalidModelPriceCatalogManagementValueError,
MODEL_PRICE_CATALOG_MANAGEMENT_DECISION_MODES,
ModelPriceCatalogManagementAuthorizationError,
ModelPriceCatalogManagementQuotaExceededError,
ModelPriceCatalogManagementSeparationOfDutyError,
ModelPriceCatalogManagementUnavailableError,
type BaseManagementRequest,
type CommitAuthorizedModelPriceCatalogHeadResult,
type CommitAuthorizedModelPriceCatalogPublicationResult,
type CreateModelPriceCatalogManagementServiceOptions,
type ModelPriceCatalogAuthorizationCommand,
type ModelPriceCatalogAuthorizedAdministrationRepository,
type ModelPriceCatalogManagementOperation,
type ModelPriceCatalogManagementService,
type ModelPriceCatalogPolicyDecision,
type PublishModelPriceCatalogRequest,
type TransitionModelPriceCatalogRequest,
} from './contracts';
import {
currentTime,
exactRequest,
normalizedPrincipal,
unavailable,
} from './validation';
export function createModelPriceCatalogManagementService(
repository: ModelPriceCatalogAuthorizedAdministrationRepository,
options: CreateModelPriceCatalogManagementServiceOptions,
): Readonly<ModelPriceCatalogManagementService> {
if (
!repository ||
typeof repository.findPublication !== 'function' ||
typeof repository.findCurrent !== 'function' ||
typeof repository.findAuthorization !== 'function' ||
typeof repository.publishAuthorized !== 'function' ||
typeof repository.transitionAuthorized !== 'function' ||
!options ||
typeof options !== 'object' ||
Array.isArray(options) ||
Object.keys(options).some(
(key) =>
key !== 'decisionMode' &&
key !== 'authorizer' &&
key !== 'quota' &&
key !== 'now',
) ||
!MODEL_PRICE_CATALOG_MANAGEMENT_DECISION_MODES.includes(
options.decisionMode,
) ||
!options.authorizer ||
typeof options.authorizer.authorize !== 'function' ||
(options.quota !== undefined &&
(!options.quota || typeof options.quota.consume !== 'function')) ||
(options.now !== undefined && typeof options.now !== 'function')
) {
throw new InvalidModelPriceCatalogManagementValueError(
'service options are invalid',
);
}
const now = options.now ?? Date.now;
const authorize = async (
operationValue: ModelPriceCatalogManagementOperation,
request: BaseManagementRequest,
priceRevision: string | null,
catalogCommandDigest: string,
): Promise<Readonly<ModelPriceCatalogAuthorizationCommand>> => {
const principal = normalizedPrincipal(request.principal, currentTime(now));
let policy: Readonly<ModelPriceCatalogPolicyDecision>;
try {
policy = normalizeModelPriceCatalogPolicyDecision(
await options.authorizer.authorize({
operation: operationValue,
provider: request.provider,
model: request.model,
priceRevision,
requestId: request.requestId,
principal,
}),
);
} catch (error) {
throw unavailable(error);
}
if (policy.effect !== 'allow') {
throw new ModelPriceCatalogManagementAuthorizationError();
}
if (options.quota) {
try {
await options.quota.consume({
operation: operationValue,
subject: principal.subject,
idempotencyKey: request.authorizationId,
});
} catch (error) {
if (error instanceof ModelPriceCatalogManagementQuotaExceededError) {
throw error;
}
throw unavailable(error);
}
}
return createModelPriceCatalogAuthorizationCommand({
authorizationId: request.authorizationId,
requestId: request.requestId,
operation: operationValue,
provider: request.provider,
model: request.model,
priceRevision,
catalogCommandDigest,
principal,
policy,
decisionMode: options.decisionMode,
});
};
return Object.freeze({
async publish(
request: Readonly<PublishModelPriceCatalogRequest>,
): Promise<Readonly<CommitAuthorizedModelPriceCatalogPublicationResult>> {
exactRequest(
request,
[
'authorizationId',
'requestId',
'mutationId',
'provider',
'model',
'principal',
'priceRevision',
'currency',
'inputMicrosPerMillionTokens',
'outputMicrosPerMillionTokens',
],
'publish request',
);
const principal = normalizedPrincipal(
request.principal,
currentTime(now),
);
const command = createModelPriceCatalogPublishCommand({
provider: request.provider,
model: request.model,
priceRevision: request.priceRevision,
currency: request.currency,
inputMicrosPerMillionTokens: request.inputMicrosPerMillionTokens,
outputMicrosPerMillionTokens: request.outputMicrosPerMillionTokens,
mutationId: request.mutationId,
publishedByUserId: principal.subject.id,
});
const authorization = await authorize(
'publish',
{ ...request, principal },
request.priceRevision,
command.commandDigest,
);
return repository.publishAuthorized(command, authorization);
},
async transition(
request: Readonly<TransitionModelPriceCatalogRequest>,
): Promise<Readonly<CommitAuthorizedModelPriceCatalogHeadResult>> {
exactRequest(
request,
[
'authorizationId',
'requestId',
'mutationId',
'provider',
'model',
'principal',
'expectedGeneration',
'expectedHeadDigest',
'action',
'priceRevision',
],
'transition request',
);
const principal = normalizedPrincipal(
request.principal,
currentTime(now),
);
const command = createModelPriceCatalogTransitionCommand({
provider: request.provider,
model: request.model,
expectedGeneration: request.expectedGeneration,
expectedHeadDigest: request.expectedHeadDigest,
action: request.action,
priceRevision: request.priceRevision,
mutationId: request.mutationId,
changedByUserId: principal.subject.id,
});
const authorization = await authorize(
request.action,
{ ...request, principal },
request.priceRevision,
command.commandDigest,
);
if (
options.decisionMode === 'separation_of_duty' &&
request.action === 'activate'
) {
let publication: Readonly<ModelPriceCatalogPublication> | null;
try {
publication = await repository.findPublication({
provider: request.provider,
model: request.model,
priceRevision: request.priceRevision!,
});
} catch (error) {
throw unavailable(error);
}
if (!publication) {
throw new ModelPriceCatalogManagementUnavailableError();
}
if (publication.publishedByUserId === principal.subject.id) {
throw new ModelPriceCatalogManagementSeparationOfDutyError();
}
}
return repository.transitionAuthorized(command, authorization);
},
});
}
@@ -0,0 +1,243 @@
import { Buffer } from 'node:buffer';
import { createHash } from 'node:crypto';
import {
normalizeSecurityPrincipal,
type SecurityAuthenticationAssurance,
type SecurityPrincipal,
} from '@qinglong/runtime-core/security';
import {
InvalidModelPriceCatalogManagementValueError,
MAX_MODEL_PRICE_CATALOG_PRINCIPAL_AGE_MS,
ModelPriceCatalogManagementAuthenticationError,
ModelPriceCatalogManagementUnavailableError,
type ModelPriceCatalogManagementDecisionMode,
type ModelPriceCatalogManagementOperation,
} from './contracts';
const IDENTITY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
const REQUEST_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
const DIGEST_PATTERN = /^[0-9a-f]{64}$/;
const REASON_PATTERN = /^[a-z][a-z0-9_]{0,63}$/;
const STRONG_ASSURANCES = new Set<SecurityAuthenticationAssurance>([
'multi_factor',
'hardware',
'local_console',
]);
export function invalid(message: string): never {
throw new InvalidModelPriceCatalogManagementValueError(message);
}
export function record(
value: unknown,
label: string,
): Record<string, unknown> {
if (
!value ||
typeof value !== 'object' ||
Array.isArray(value) ||
Object.getPrototypeOf(value) !== Object.prototype
) {
return invalid(`${label} must be a plain object`);
}
return value as Record<string, unknown>;
}
export function exactKeys(
value: object,
expected: readonly string[],
label: string,
): void {
const actual = Object.keys(value).sort();
const canonical = [...expected].sort();
if (
actual.length !== canonical.length ||
actual.some((key, index) => key !== canonical[index])
) {
invalid(`${label} shape is invalid`);
}
}
export function identity(value: unknown, label: string): string {
if (typeof value !== 'string' || !IDENTITY_PATTERN.test(value)) {
return invalid(`${label} is invalid`);
}
return value;
}
export function requestId(value: unknown): string {
if (typeof value !== 'string' || !REQUEST_ID_PATTERN.test(value)) {
return invalid('request id is invalid');
}
return value;
}
export function digest(value: unknown, label: string): string {
if (typeof value !== 'string' || !DIGEST_PATTERN.test(value)) {
return invalid(`${label} is invalid`);
}
return value;
}
export function nullableIdentity(
value: unknown,
label: string,
): string | null {
return value === null ? null : identity(value, label);
}
export function integer(value: unknown, label: string): number {
if (!Number.isSafeInteger(value) || (value as number) < 0) {
return invalid(`${label} is invalid`);
}
return value as number;
}
export function hash(domain: Buffer, value: object): string {
return createHash('sha256')
.update(domain)
.update(JSON.stringify(value), 'utf8')
.digest('hex');
}
export function operation(
value: unknown,
): ModelPriceCatalogManagementOperation {
if (
value !== 'publish' &&
value !== 'activate' &&
value !== 'deactivate' &&
value !== 'revoke'
) {
return invalid('operation is invalid');
}
return value;
}
export function decisionMode(
value: unknown,
): ModelPriceCatalogManagementDecisionMode {
if (value !== 'human_confirmation' && value !== 'separation_of_duty') {
return invalid('decision mode is invalid');
}
return value;
}
export function normalizeReasons(value: unknown): readonly string[] {
if (
!Array.isArray(value) ||
value.length < 1 ||
value.length > 8 ||
value.some(
(reason) => typeof reason !== 'string' || !REASON_PATTERN.test(reason),
)
) {
return invalid('policy reasons are invalid');
}
return Object.freeze([...value]);
}
export function normalizedPrincipal(
value: SecurityPrincipal,
nowMs: number,
): Readonly<SecurityPrincipal> {
let principal: Readonly<SecurityPrincipal>;
try {
principal = normalizeSecurityPrincipal(value, nowMs);
} catch {
throw new ModelPriceCatalogManagementAuthenticationError();
}
if (
principal.subject.type !== 'user' ||
!STRONG_ASSURANCES.has(principal.assurance) ||
nowMs - principal.authenticatedAtMs >
MAX_MODEL_PRICE_CATALOG_PRINCIPAL_AGE_MS
) {
throw new ModelPriceCatalogManagementAuthenticationError();
}
return principal;
}
export function normalizeStoredPrincipal(
value: SecurityPrincipal,
): Readonly<SecurityPrincipal> {
const candidate = record(value, 'principal');
exactKeys(
candidate,
[
'subject',
'authenticationId',
'authenticatedAtMs',
'expiresAtMs',
'assurance',
],
'principal',
);
const subject = record(value.subject, 'principal subject');
exactKeys(subject, ['type', 'id'], 'principal subject');
if (
value.subject.type !== 'user' ||
!STRONG_ASSURANCES.has(value.assurance) ||
typeof value.authenticationId !== 'string' ||
!IDENTITY_PATTERN.test(value.authenticationId)
) {
return invalid('principal identity is invalid');
}
const authenticatedAtMs = integer(
value.authenticatedAtMs,
'principal authenticatedAtMs',
);
const expiresAtMs = integer(value.expiresAtMs, 'principal expiresAtMs');
if (expiresAtMs <= authenticatedAtMs) {
return invalid('principal lifetime is invalid');
}
return Object.freeze({
subject: Object.freeze({
type: 'user' as const,
id: identity(value.subject.id, 'principal User id'),
}),
authenticationId: value.authenticationId,
authenticatedAtMs,
expiresAtMs,
assurance: value.assurance,
});
}
export function validateOperationRevision(
operationValue: ModelPriceCatalogManagementOperation,
revision: string | null,
): void {
if (
(operationValue === 'deactivate' && revision !== null) ||
(operationValue !== 'deactivate' && revision === null)
) {
invalid('operation price revision is invalid');
}
}
export function exactRequest(
value: unknown,
expected: readonly string[],
label: string,
): void {
const candidate = record(value, label);
exactKeys(candidate, expected, label);
}
export function currentTime(now: () => number): number {
const value = now();
if (!Number.isSafeInteger(value) || value < 0) {
throw new ModelPriceCatalogManagementUnavailableError();
}
return value;
}
export function unavailable(
error: unknown,
): ModelPriceCatalogManagementUnavailableError {
return new ModelPriceCatalogManagementUnavailableError({
cause: error instanceof Error ? error : undefined,
});
}
@@ -0,0 +1,682 @@
import { Buffer } from 'node:buffer';
// Catalog facts are immutable pricing-domain contracts shared by both storage dialects.
import { createHash } from 'node:crypto';
import {
MODEL_PRICE_CURRENCY,
createModelPriceCatalogEntry,
normalizeModelPriceCatalogEntry,
type ModelPriceCatalogEntry,
type ModelPriceCatalogLookup,
type ModelPriceCatalogResolver,
} from './pricing';
export const MODEL_PRICE_CATALOG_PUBLISH_COMMAND_SCHEMA =
'qinglong/model-price-catalog-publish-command@v1' as const;
export const MODEL_PRICE_CATALOG_PUBLICATION_SCHEMA =
'qinglong/model-price-catalog-publication@v1' as const;
export const MODEL_PRICE_CATALOG_TRANSITION_COMMAND_SCHEMA =
'qinglong/model-price-catalog-transition-command@v1' as const;
export const MODEL_PRICE_CATALOG_HEAD_SCHEMA =
'qinglong/model-price-catalog-head@v1' as const;
export const MODEL_PRICE_CATALOG_ACTIONS = [
'activate',
'deactivate',
'revoke',
] as const;
export const MAX_MODEL_PRICE_CATALOG_GENERATION = 2_147_483_647;
export type ModelPriceCatalogAction =
(typeof MODEL_PRICE_CATALOG_ACTIONS)[number];
export interface ModelPriceCatalogPublishCommand {
readonly schema: typeof MODEL_PRICE_CATALOG_PUBLISH_COMMAND_SCHEMA;
readonly provider: string;
readonly model: string;
readonly priceRevision: string;
readonly currency: typeof MODEL_PRICE_CURRENCY;
readonly inputMicrosPerMillionTokens: number;
readonly outputMicrosPerMillionTokens: number;
readonly mutationId: string;
readonly publishedByUserId: string;
readonly commandDigest: string;
}
export interface ModelPriceCatalogPublication {
readonly schema: typeof MODEL_PRICE_CATALOG_PUBLICATION_SCHEMA;
readonly entry: Readonly<ModelPriceCatalogEntry>;
readonly mutationId: string;
readonly publishedByUserId: string;
readonly commandDigest: string;
readonly publicationDigest: string;
}
export interface ModelPriceCatalogTransitionCommand {
readonly schema: typeof MODEL_PRICE_CATALOG_TRANSITION_COMMAND_SCHEMA;
readonly provider: string;
readonly model: string;
readonly expectedGeneration: number;
readonly expectedHeadDigest: string | null;
readonly action: ModelPriceCatalogAction;
readonly priceRevision: string | null;
readonly mutationId: string;
readonly changedByUserId: string;
readonly commandDigest: string;
}
export interface ModelPriceCatalogHead {
readonly schema: typeof MODEL_PRICE_CATALOG_HEAD_SCHEMA;
readonly provider: string;
readonly model: string;
readonly generation: number;
readonly previousHeadDigest: string | null;
readonly activePriceRevision: string | null;
readonly activeCatalogDigest: string | null;
readonly revokedPriceRevision: string | null;
readonly revokedCatalogDigest: string | null;
readonly action: ModelPriceCatalogAction;
readonly mutationId: string;
readonly changedByUserId: string;
readonly changedAtMs: number;
readonly commandDigest: string;
readonly headDigest: string;
}
export interface CommitModelPriceCatalogPublicationResult {
readonly status: 'created' | 'existing';
readonly publication: Readonly<ModelPriceCatalogPublication>;
}
export interface CommitModelPriceCatalogHeadResult {
readonly status: 'created' | 'existing';
readonly head: Readonly<ModelPriceCatalogHead>;
}
export interface ModelPriceCatalogReader extends ModelPriceCatalogResolver {
findPublication(
lookup: Omit<ModelPriceCatalogLookup, 'signal'>,
): Promise<Readonly<ModelPriceCatalogPublication> | null>;
findCurrent(
provider: string,
model: string,
): Promise<Readonly<ModelPriceCatalogHead> | null>;
}
export interface ModelPriceCatalogAdministrationRepository
extends ModelPriceCatalogReader {
publish(
command: Readonly<ModelPriceCatalogPublishCommand>,
): Promise<Readonly<CommitModelPriceCatalogPublicationResult>>;
transition(
command: Readonly<ModelPriceCatalogTransitionCommand>,
): Promise<Readonly<CommitModelPriceCatalogHeadResult>>;
}
export class InvalidModelPriceCatalogError extends TypeError {
readonly code = 'MODEL_PRICE_CATALOG_INVALID';
constructor(message: string) {
super(`Model price catalog is invalid: ${message}`);
this.name = 'InvalidModelPriceCatalogError';
}
}
export class ModelPriceCatalogConflictError extends Error {
readonly code = 'MODEL_PRICE_CATALOG_CONFLICT';
constructor() {
super('Model price catalog conflicts with durable state');
this.name = 'ModelPriceCatalogConflictError';
}
}
export class ModelPriceCatalogUnavailableError extends Error {
readonly code = 'MODEL_PRICE_CATALOG_UNAVAILABLE';
constructor(options?: ErrorOptions) {
super('Model price catalog is unavailable', options);
this.name = 'ModelPriceCatalogUnavailableError';
}
}
const IDENTITY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
const DIGEST_PATTERN = /^[0-9a-f]{64}$/;
const PUBLISH_COMMAND_DIGEST_DOMAIN = Buffer.from(
'qinglong/model-price-catalog-publish-command-digest@v1\0',
'utf8',
);
const PUBLICATION_DIGEST_DOMAIN = Buffer.from(
'qinglong/model-price-catalog-publication-digest@v1\0',
'utf8',
);
const TRANSITION_COMMAND_DIGEST_DOMAIN = Buffer.from(
'qinglong/model-price-catalog-transition-command-digest@v1\0',
'utf8',
);
const HEAD_DIGEST_DOMAIN = Buffer.from(
'qinglong/model-price-catalog-head-digest@v1\0',
'utf8',
);
function invalid(message: string): never {
throw new InvalidModelPriceCatalogError(message);
}
function record(value: unknown, label: string): Record<string, unknown> {
if (
!value ||
typeof value !== 'object' ||
Array.isArray(value) ||
Object.getPrototypeOf(value) !== Object.prototype
) {
return invalid(`${label} must be a plain object`);
}
return value as Record<string, unknown>;
}
function exactKeys(
value: object,
expected: readonly string[],
label: string,
): void {
const actual = Object.keys(value).sort();
const canonical = [...expected].sort();
if (
actual.length !== canonical.length ||
actual.some((key, index) => key !== canonical[index])
) {
invalid(`${label} shape is invalid`);
}
}
function identity(value: unknown, label: string): string {
if (typeof value !== 'string' || !IDENTITY_PATTERN.test(value)) {
return invalid(`${label} is invalid`);
}
return value;
}
function digest(value: unknown, label: string): string {
if (typeof value !== 'string' || !DIGEST_PATTERN.test(value)) {
return invalid(`${label} is invalid`);
}
return value;
}
function nullableDigest(value: unknown, label: string): string | null {
return value === null ? null : digest(value, label);
}
function integer(
value: unknown,
label: string,
minimum: number,
maximum = Number.MAX_SAFE_INTEGER,
): number {
if (
!Number.isSafeInteger(value) ||
(value as number) < minimum ||
(value as number) > maximum
) {
return invalid(`${label} is invalid`);
}
return value as number;
}
function action(value: unknown): ModelPriceCatalogAction {
if (value !== 'activate' && value !== 'deactivate' && value !== 'revoke') {
return invalid('action is invalid');
}
return value;
}
function hash(domain: Buffer, value: object): string {
return createHash('sha256')
.update(domain)
.update(JSON.stringify(value), 'utf8')
.digest('hex');
}
export function createModelPriceCatalogPublishCommand(
value: Omit<ModelPriceCatalogPublishCommand, 'schema' | 'commandDigest'>,
): Readonly<ModelPriceCatalogPublishCommand> {
const candidate = record(value, 'publish command');
exactKeys(
candidate,
[
'currency',
'inputMicrosPerMillionTokens',
'model',
'mutationId',
'outputMicrosPerMillionTokens',
'priceRevision',
'provider',
'publishedByUserId',
],
'publish command',
);
const semanticEntry = createModelPriceCatalogEntry({
provider: value.provider,
model: value.model,
priceRevision: value.priceRevision,
currency: value.currency,
inputMicrosPerMillionTokens: value.inputMicrosPerMillionTokens,
outputMicrosPerMillionTokens: value.outputMicrosPerMillionTokens,
publishedAtMs: 0,
});
const unsigned = Object.freeze({
schema: MODEL_PRICE_CATALOG_PUBLISH_COMMAND_SCHEMA,
provider: semanticEntry.provider,
model: semanticEntry.model,
priceRevision: semanticEntry.priceRevision,
currency: semanticEntry.currency,
inputMicrosPerMillionTokens: semanticEntry.inputMicrosPerMillionTokens,
outputMicrosPerMillionTokens: semanticEntry.outputMicrosPerMillionTokens,
mutationId: identity(value.mutationId, 'mutation id'),
publishedByUserId: identity(value.publishedByUserId, 'publisher user id'),
});
return Object.freeze({
...unsigned,
commandDigest: hash(PUBLISH_COMMAND_DIGEST_DOMAIN, unsigned),
});
}
export function normalizeModelPriceCatalogPublishCommand(
value: ModelPriceCatalogPublishCommand,
): Readonly<ModelPriceCatalogPublishCommand> {
const candidate = record(value, 'publish command');
exactKeys(
candidate,
[
'commandDigest',
'currency',
'inputMicrosPerMillionTokens',
'model',
'mutationId',
'outputMicrosPerMillionTokens',
'priceRevision',
'provider',
'publishedByUserId',
'schema',
],
'publish command',
);
if (value.schema !== MODEL_PRICE_CATALOG_PUBLISH_COMMAND_SCHEMA) {
invalid('publish command schema is invalid');
}
const normalized = createModelPriceCatalogPublishCommand({
provider: value.provider,
model: value.model,
priceRevision: value.priceRevision,
currency: value.currency,
inputMicrosPerMillionTokens: value.inputMicrosPerMillionTokens,
outputMicrosPerMillionTokens: value.outputMicrosPerMillionTokens,
mutationId: value.mutationId,
publishedByUserId: value.publishedByUserId,
});
if (
digest(value.commandDigest, 'publish command digest') !==
normalized.commandDigest
) {
invalid('publish command digest is inconsistent');
}
return normalized;
}
export function createModelPriceCatalogPublication(
commandValue: ModelPriceCatalogPublishCommand,
publishedAtMsValue: number,
): Readonly<ModelPriceCatalogPublication> {
const command = normalizeModelPriceCatalogPublishCommand(commandValue);
const entry = createModelPriceCatalogEntry({
provider: command.provider,
model: command.model,
priceRevision: command.priceRevision,
currency: command.currency,
inputMicrosPerMillionTokens: command.inputMicrosPerMillionTokens,
outputMicrosPerMillionTokens: command.outputMicrosPerMillionTokens,
publishedAtMs: integer(publishedAtMsValue, 'publish time', 0),
});
const unsigned = Object.freeze({
schema: MODEL_PRICE_CATALOG_PUBLICATION_SCHEMA,
entry,
mutationId: command.mutationId,
publishedByUserId: command.publishedByUserId,
commandDigest: command.commandDigest,
});
return Object.freeze({
...unsigned,
publicationDigest: hash(PUBLICATION_DIGEST_DOMAIN, unsigned),
});
}
export function normalizeModelPriceCatalogPublication(
value: ModelPriceCatalogPublication,
): Readonly<ModelPriceCatalogPublication> {
const candidate = record(value, 'publication');
exactKeys(
candidate,
[
'commandDigest',
'entry',
'mutationId',
'publicationDigest',
'publishedByUserId',
'schema',
],
'publication',
);
if (value.schema !== MODEL_PRICE_CATALOG_PUBLICATION_SCHEMA) {
invalid('publication schema is invalid');
}
const entry = normalizeModelPriceCatalogEntry(value.entry);
const command = createModelPriceCatalogPublishCommand({
provider: entry.provider,
model: entry.model,
priceRevision: entry.priceRevision,
currency: entry.currency,
inputMicrosPerMillionTokens: entry.inputMicrosPerMillionTokens,
outputMicrosPerMillionTokens: entry.outputMicrosPerMillionTokens,
mutationId: value.mutationId,
publishedByUserId: value.publishedByUserId,
});
if (
digest(value.commandDigest, 'publish command digest') !==
command.commandDigest
) {
invalid('publication command is inconsistent');
}
const normalized = createModelPriceCatalogPublication(
command,
entry.publishedAtMs,
);
if (
digest(value.publicationDigest, 'publication digest') !==
normalized.publicationDigest
) {
invalid('publication digest is inconsistent');
}
return normalized;
}
export function createModelPriceCatalogTransitionCommand(
value: Omit<ModelPriceCatalogTransitionCommand, 'schema' | 'commandDigest'>,
): Readonly<ModelPriceCatalogTransitionCommand> {
const candidate = record(value, 'transition command');
exactKeys(
candidate,
[
'action',
'changedByUserId',
'expectedGeneration',
'expectedHeadDigest',
'model',
'mutationId',
'priceRevision',
'provider',
],
'transition command',
);
const normalizedAction = action(value.action);
const expectedGeneration = integer(
value.expectedGeneration,
'expected generation',
0,
MAX_MODEL_PRICE_CATALOG_GENERATION - 1,
);
const expectedHeadDigest = nullableDigest(
value.expectedHeadDigest,
'expected head digest',
);
if ((expectedGeneration === 0) !== (expectedHeadDigest === null)) {
invalid('expected head fence is invalid');
}
const priceRevision =
value.priceRevision === null
? null
: identity(value.priceRevision, 'price revision');
if ((normalizedAction === 'deactivate') !== (priceRevision === null)) {
invalid('transition price revision is invalid');
}
const unsigned = Object.freeze({
schema: MODEL_PRICE_CATALOG_TRANSITION_COMMAND_SCHEMA,
provider: identity(value.provider, 'provider'),
model: identity(value.model, 'model'),
expectedGeneration,
expectedHeadDigest,
action: normalizedAction,
priceRevision,
mutationId: identity(value.mutationId, 'mutation id'),
changedByUserId: identity(value.changedByUserId, 'actor user id'),
});
return Object.freeze({
...unsigned,
commandDigest: hash(TRANSITION_COMMAND_DIGEST_DOMAIN, unsigned),
});
}
export function normalizeModelPriceCatalogTransitionCommand(
value: ModelPriceCatalogTransitionCommand,
): Readonly<ModelPriceCatalogTransitionCommand> {
const candidate = record(value, 'transition command');
exactKeys(
candidate,
[
'action',
'changedByUserId',
'commandDigest',
'expectedGeneration',
'expectedHeadDigest',
'model',
'mutationId',
'priceRevision',
'provider',
'schema',
],
'transition command',
);
if (value.schema !== MODEL_PRICE_CATALOG_TRANSITION_COMMAND_SCHEMA) {
invalid('transition command schema is invalid');
}
const normalized = createModelPriceCatalogTransitionCommand({
provider: value.provider,
model: value.model,
expectedGeneration: value.expectedGeneration,
expectedHeadDigest: value.expectedHeadDigest,
action: value.action,
priceRevision: value.priceRevision,
mutationId: value.mutationId,
changedByUserId: value.changedByUserId,
});
if (
digest(value.commandDigest, 'transition command digest') !==
normalized.commandDigest
) {
invalid('transition command digest is inconsistent');
}
return normalized;
}
export function createModelPriceCatalogHead(
previousValue: ModelPriceCatalogHead | null,
commandValue: ModelPriceCatalogTransitionCommand,
targetValue: ModelPriceCatalogPublication | null,
targetRevoked: boolean,
changedAtMsValue: number,
): Readonly<ModelPriceCatalogHead> {
const previous =
previousValue === null
? null
: normalizeModelPriceCatalogHead(previousValue);
const command = normalizeModelPriceCatalogTransitionCommand(commandValue);
const target =
targetValue === null
? null
: normalizeModelPriceCatalogPublication(targetValue);
if (
(previous?.generation ?? 0) !== command.expectedGeneration ||
(previous?.headDigest ?? null) !== command.expectedHeadDigest ||
(previous !== null &&
(previous.provider !== command.provider ||
previous.model !== command.model))
) {
invalid('transition fence is inconsistent');
}
if (
command.action === 'deactivate'
? target !== null
: !target ||
target.entry.provider !== command.provider ||
target.entry.model !== command.model ||
target.entry.priceRevision !== command.priceRevision
) {
invalid('transition target is inconsistent');
}
if (targetRevoked) invalid('transition target is revoked');
let activePriceRevision = previous?.activePriceRevision ?? null;
let activeCatalogDigest = previous?.activeCatalogDigest ?? null;
let revokedPriceRevision: string | null = null;
let revokedCatalogDigest: string | null = null;
if (command.action === 'activate') {
if (
activePriceRevision === target!.entry.priceRevision &&
activeCatalogDigest === target!.entry.catalogDigest
) {
invalid('transition does not change catalog state');
}
activePriceRevision = target!.entry.priceRevision;
activeCatalogDigest = target!.entry.catalogDigest;
} else if (command.action === 'deactivate') {
if (activePriceRevision === null) {
invalid('transition does not change catalog state');
}
activePriceRevision = null;
activeCatalogDigest = null;
} else {
revokedPriceRevision = target!.entry.priceRevision;
revokedCatalogDigest = target!.entry.catalogDigest;
if (activePriceRevision === revokedPriceRevision) {
activePriceRevision = null;
activeCatalogDigest = null;
}
}
const unsigned = Object.freeze({
schema: MODEL_PRICE_CATALOG_HEAD_SCHEMA,
provider: command.provider,
model: command.model,
generation: command.expectedGeneration + 1,
previousHeadDigest: command.expectedHeadDigest,
activePriceRevision,
activeCatalogDigest,
revokedPriceRevision,
revokedCatalogDigest,
action: command.action,
mutationId: command.mutationId,
changedByUserId: command.changedByUserId,
changedAtMs: integer(changedAtMsValue, 'change time', 0),
commandDigest: command.commandDigest,
});
return Object.freeze({
...unsigned,
headDigest: hash(HEAD_DIGEST_DOMAIN, unsigned),
});
}
export function normalizeModelPriceCatalogHead(
value: ModelPriceCatalogHead,
): Readonly<ModelPriceCatalogHead> {
const candidate = record(value, 'catalog head');
exactKeys(
candidate,
[
'action',
'activeCatalogDigest',
'activePriceRevision',
'changedAtMs',
'changedByUserId',
'commandDigest',
'generation',
'headDigest',
'model',
'mutationId',
'previousHeadDigest',
'provider',
'revokedCatalogDigest',
'revokedPriceRevision',
'schema',
],
'catalog head',
);
if (value.schema !== MODEL_PRICE_CATALOG_HEAD_SCHEMA) {
invalid('catalog head schema is invalid');
}
const generation = integer(
value.generation,
'generation',
1,
MAX_MODEL_PRICE_CATALOG_GENERATION,
);
const previousHeadDigest = nullableDigest(
value.previousHeadDigest,
'previous head digest',
);
if ((generation === 1) !== (previousHeadDigest === null)) {
invalid('catalog head generation fence is invalid');
}
const activePriceRevision =
value.activePriceRevision === null
? null
: identity(value.activePriceRevision, 'active price revision');
const activeCatalogDigest = nullableDigest(
value.activeCatalogDigest,
'active catalog digest',
);
const revokedPriceRevision =
value.revokedPriceRevision === null
? null
: identity(value.revokedPriceRevision, 'revoked price revision');
const revokedCatalogDigest = nullableDigest(
value.revokedCatalogDigest,
'revoked catalog digest',
);
if (
(activePriceRevision === null) !== (activeCatalogDigest === null) ||
(revokedPriceRevision === null) !== (revokedCatalogDigest === null) ||
(revokedPriceRevision !== null &&
revokedPriceRevision === activePriceRevision)
) {
invalid('catalog head projection is invalid');
}
const normalizedAction = action(value.action);
if ((normalizedAction === 'revoke') !== (revokedPriceRevision !== null)) {
invalid('catalog head revocation projection is invalid');
}
if (
(normalizedAction === 'activate' && activePriceRevision === null) ||
(normalizedAction === 'deactivate' && activePriceRevision !== null)
) {
invalid('catalog head action projection is invalid');
}
const unsigned = Object.freeze({
schema: MODEL_PRICE_CATALOG_HEAD_SCHEMA,
provider: identity(value.provider, 'provider'),
model: identity(value.model, 'model'),
generation,
previousHeadDigest,
activePriceRevision,
activeCatalogDigest,
revokedPriceRevision,
revokedCatalogDigest,
action: normalizedAction,
mutationId: identity(value.mutationId, 'mutation id'),
changedByUserId: identity(value.changedByUserId, 'actor user id'),
changedAtMs: integer(value.changedAtMs, 'change time', 0),
commandDigest: digest(value.commandDigest, 'transition command digest'),
});
const headDigest = digest(value.headDigest, 'head digest');
if (headDigest !== hash(HEAD_DIGEST_DOMAIN, unsigned)) {
invalid('catalog head digest is inconsistent');
}
return Object.freeze({ ...unsigned, headDigest });
}
@@ -0,0 +1,42 @@
// Human authorization is part of Pricing management, not Profile composition.
export {
MAX_MODEL_PRICE_CATALOG_PRINCIPAL_AGE_MS,
MODEL_PRICE_CATALOG_AUTHORIZATION_COMMAND_SCHEMA,
MODEL_PRICE_CATALOG_AUTHORIZATION_SCHEMA,
MODEL_PRICE_CATALOG_MANAGEMENT_DECISION_MODES,
MODEL_PRICE_CATALOG_MANAGEMENT_OPERATIONS,
MODEL_PRICE_CATALOG_POLICY_DECISION_SCHEMA,
InvalidModelPriceCatalogManagementValueError,
ModelPriceCatalogManagementAuthenticationError,
ModelPriceCatalogManagementAuthorizationError,
ModelPriceCatalogManagementQuotaExceededError,
ModelPriceCatalogManagementSeparationOfDutyError,
ModelPriceCatalogManagementUnavailableError,
type AuthorizeModelPriceCatalogManagementRequest,
type CommitAuthorizedModelPriceCatalogHeadResult,
type CommitAuthorizedModelPriceCatalogPublicationResult,
type ConsumeModelPriceCatalogManagementQuotaCommand,
type CreateModelPriceCatalogManagementServiceOptions,
type ModelPriceCatalogAuthorization,
type ModelPriceCatalogAuthorizationCommand,
type ModelPriceCatalogAuthorizedAdministrationRepository,
type ModelPriceCatalogManagementAuthorizer,
type ModelPriceCatalogManagementDecisionMode,
type ModelPriceCatalogManagementOperation,
type ModelPriceCatalogManagementQuota,
type ModelPriceCatalogManagementService,
type ModelPriceCatalogPolicyDecision,
type PublishModelPriceCatalogRequest,
type TransitionModelPriceCatalogRequest,
} from './model-price-catalog-management/contracts';
export {
createModelPriceCatalogAuthorization,
createModelPriceCatalogAuthorizationCommand,
createModelPriceCatalogPolicyDecision,
normalizeModelPriceCatalogAuthorization,
normalizeModelPriceCatalogAuthorizationCommand,
normalizeModelPriceCatalogPolicyDecision,
normalizeModelPriceCatalogPublishAuthorization,
normalizeModelPriceCatalogTransitionAuthorization,
} from './model-price-catalog-management/authorization';
export { createModelPriceCatalogManagementService } from './model-price-catalog-management/service';
+671
View File
@@ -0,0 +1,671 @@
import { Buffer } from 'node:buffer';
// Pricing owns immutable quotes and settlements; metering and quota remain sibling domains.
import { createHash } from 'node:crypto';
import type {
CommitModelInvocationResult,
ModelInvocationCompletionCommand,
ModelInvocationCompletionRecord,
ModelInvocationRepository,
ModelInvocationStartCommand,
ModelInvocationStartRecord,
} from '../model-invocation/modelInvocation';
import { normalizeModelInvocationCompletionRecord } from '../model-invocation/modelInvocation';
import type { ModelUsage } from '../model-gateway/model';
import { normalizeModelUsage } from '../model-gateway/validation';
import type { ModelInvocationQuotaAdmission } from '../usage/usageQuota';
export const MODEL_PRICE_CATALOG_ENTRY_SCHEMA =
'qinglong/model-price-catalog-entry@v1' as const;
export const MODEL_INVOCATION_PRICE_QUOTE_SCHEMA =
'qinglong/model-invocation-price-quote@v1' as const;
export const MODEL_INVOCATION_PRICE_SETTLEMENT_SCHEMA =
'qinglong/model-invocation-price-settlement@v1' as const;
export const MODEL_PRICE_CURRENCY = 'USD' as const;
export const MODEL_PRICE_RATE_UNIT_TOKENS = 1_000_000;
export const MAX_MODEL_PRICE_CATALOG_ENTRIES = 256;
export const MAX_MODEL_PRICE_RATE_MICROS = 1_000_000_000_000;
export const MAX_MODEL_PRICE_COST_MICROS = 1_000_000_000_000_000;
const IDENTITY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
const DIGEST_PATTERN = /^[0-9a-f]{64}$/;
const CATALOG_DIGEST_DOMAIN = Buffer.from(
'qinglong/model-price-catalog-entry-digest@v1\0',
'utf8',
);
const QUOTE_DIGEST_DOMAIN = Buffer.from(
'qinglong/model-invocation-price-quote-digest@v1\0',
'utf8',
);
const SETTLEMENT_DIGEST_DOMAIN = Buffer.from(
'qinglong/model-invocation-price-settlement-digest@v1\0',
'utf8',
);
export interface ModelPriceCatalogEntry {
readonly schema: typeof MODEL_PRICE_CATALOG_ENTRY_SCHEMA;
readonly provider: string;
readonly model: string;
readonly priceRevision: string;
readonly currency: typeof MODEL_PRICE_CURRENCY;
readonly inputMicrosPerMillionTokens: number;
readonly outputMicrosPerMillionTokens: number;
readonly publishedAtMs: number;
readonly catalogDigest: string;
}
export interface ModelPriceCatalogLookup {
readonly provider: string;
readonly model: string;
readonly priceRevision: string;
readonly signal?: AbortSignal;
}
export interface ModelPriceCatalogResolver {
resolve(
lookup: Readonly<ModelPriceCatalogLookup>,
): Promise<Readonly<ModelPriceCatalogEntry> | null>;
}
export interface ModelInvocationPriceQuote {
readonly schema: typeof MODEL_INVOCATION_PRICE_QUOTE_SCHEMA;
readonly invocationId: string;
readonly projectId: string;
readonly modelPolicyRevision: string;
readonly provider: string;
readonly model: string;
readonly priceRevision: string;
readonly currency: typeof MODEL_PRICE_CURRENCY;
readonly inputMicrosPerMillionTokens: number;
readonly outputMicrosPerMillionTokens: number;
readonly maxTotalTokens: number;
readonly maxOutputTokens: number;
readonly reservedCostMicros: number;
readonly catalogDigest: string;
readonly quoteDigest: string;
}
export interface ModelInvocationPriceSettlement {
readonly schema: typeof MODEL_INVOCATION_PRICE_SETTLEMENT_SCHEMA;
readonly invocationId: string;
readonly projectId: string;
readonly quoteDigest: string;
readonly completionDigest: string;
readonly currency: typeof MODEL_PRICE_CURRENCY;
readonly inputTokens: number;
readonly outputTokens: number;
readonly costMicros: number;
readonly settledAtMs: number;
readonly settlementDigest: string;
}
export interface ModelInvocationPricingRepository {
findPriceQuote(
invocationId: string,
): Promise<Readonly<ModelInvocationPriceQuote> | null>;
findPriceSettlement(
invocationId: string,
): Promise<Readonly<ModelInvocationPriceSettlement> | null>;
}
export interface PricingAwareModelInvocationRepository
extends ModelInvocationRepository,
ModelInvocationPricingRepository {
admitWithPricing(
command: ModelInvocationStartCommand,
quote: ModelInvocationPriceQuote,
quotaAdmission?: ModelInvocationQuotaAdmission,
): Promise<Readonly<CommitModelInvocationResult<ModelInvocationStartRecord>>>;
completeWithPricing(
command: ModelInvocationCompletionCommand,
): Promise<
Readonly<CommitModelInvocationResult<ModelInvocationCompletionRecord>>
>;
}
export class InvalidModelPricingError extends TypeError {
readonly code = 'MODEL_PRICING_INVALID';
constructor(message: string) {
super(`Model pricing is invalid: ${message}`);
this.name = 'InvalidModelPricingError';
}
}
export class ModelPriceUnavailableError extends Error {
readonly code = 'MODEL_PRICE_UNAVAILABLE';
constructor() {
super('The exact model price revision is unavailable');
this.name = 'ModelPriceUnavailableError';
}
}
export class ModelPricingConfigurationError extends Error {
readonly code = 'MODEL_PRICING_CONFIGURATION_INVALID';
constructor() {
super('The model pricing authority is not durably configured');
this.name = 'ModelPricingConfigurationError';
}
}
function invalid(message: string): never {
throw new InvalidModelPricingError(message);
}
function plainObject(value: unknown, label: string): Record<string, unknown> {
if (
!value ||
typeof value !== 'object' ||
Array.isArray(value) ||
Object.getPrototypeOf(value) !== Object.prototype
) {
return invalid(`${label} must be a plain object`);
}
return value as Record<string, unknown>;
}
function exactKeys(
value: object,
expected: readonly string[],
label: string,
): void {
const actual = Object.keys(value).sort();
const canonical = [...expected].sort();
if (
actual.length !== canonical.length ||
actual.some((key, index) => key !== canonical[index])
) {
invalid(`${label} shape is invalid`);
}
}
function identifier(value: unknown, label: string): string {
if (typeof value !== 'string' || !IDENTITY_PATTERN.test(value)) {
return invalid(`${label} is invalid`);
}
return value;
}
function integer(
value: unknown,
label: string,
minimum: number,
maximum = Number.MAX_SAFE_INTEGER,
): number {
if (
!Number.isSafeInteger(value) ||
(value as number) < minimum ||
(value as number) > maximum
) {
return invalid(`${label} is invalid`);
}
return value as number;
}
function digest(value: unknown, label: string): string {
if (typeof value !== 'string' || !DIGEST_PATTERN.test(value)) {
return invalid(`${label} is invalid`);
}
return value;
}
function hash(domain: Buffer, value: object): string {
return createHash('sha256')
.update(domain)
.update(JSON.stringify(value), 'utf8')
.digest('hex');
}
function rateCost(tokens: number, rateMicros: number): number {
const numerator = BigInt(tokens) * BigInt(rateMicros);
const unit = BigInt(MODEL_PRICE_RATE_UNIT_TOKENS);
const rounded = (numerator + unit - 1n) / unit;
if (rounded > BigInt(MAX_MODEL_PRICE_COST_MICROS)) {
return invalid('calculated cost exceeds its reviewed limit');
}
return Number(rounded);
}
function totalCost(
inputTokens: number,
outputTokens: number,
inputRate: number,
outputRate: number,
): number {
const cost =
rateCost(inputTokens, inputRate) + rateCost(outputTokens, outputRate);
return integer(cost, 'calculated cost', 0, MAX_MODEL_PRICE_COST_MICROS);
}
function catalogKey(
value: Pick<ModelPriceCatalogEntry, 'provider' | 'model' | 'priceRevision'>,
): string {
return JSON.stringify([value.provider, value.model, value.priceRevision]);
}
export function createModelPriceCatalogEntry(
value: Omit<ModelPriceCatalogEntry, 'schema' | 'catalogDigest'>,
): Readonly<ModelPriceCatalogEntry> {
const candidate = plainObject(value, 'catalog entry');
exactKeys(
candidate,
[
'currency',
'inputMicrosPerMillionTokens',
'model',
'outputMicrosPerMillionTokens',
'priceRevision',
'provider',
'publishedAtMs',
],
'catalog entry',
);
if (value.currency !== MODEL_PRICE_CURRENCY) {
invalid('catalog currency is unsupported');
}
const unsigned = Object.freeze({
schema: MODEL_PRICE_CATALOG_ENTRY_SCHEMA,
provider: identifier(value.provider, 'provider'),
model: identifier(value.model, 'model'),
priceRevision: identifier(value.priceRevision, 'price revision'),
currency: MODEL_PRICE_CURRENCY,
inputMicrosPerMillionTokens: integer(
value.inputMicrosPerMillionTokens,
'input price',
0,
MAX_MODEL_PRICE_RATE_MICROS,
),
outputMicrosPerMillionTokens: integer(
value.outputMicrosPerMillionTokens,
'output price',
0,
MAX_MODEL_PRICE_RATE_MICROS,
),
publishedAtMs: integer(value.publishedAtMs, 'publish time', 0),
});
return Object.freeze({
...unsigned,
catalogDigest: hash(CATALOG_DIGEST_DOMAIN, unsigned),
});
}
export function normalizeModelPriceCatalogEntry(
value: ModelPriceCatalogEntry,
): Readonly<ModelPriceCatalogEntry> {
const candidate = plainObject(value, 'catalog entry');
exactKeys(
candidate,
[
'catalogDigest',
'currency',
'inputMicrosPerMillionTokens',
'model',
'outputMicrosPerMillionTokens',
'priceRevision',
'provider',
'publishedAtMs',
'schema',
],
'catalog entry',
);
if (value.schema !== MODEL_PRICE_CATALOG_ENTRY_SCHEMA) {
invalid('catalog schema is invalid');
}
const normalized = createModelPriceCatalogEntry({
provider: value.provider,
model: value.model,
priceRevision: value.priceRevision,
currency: value.currency,
inputMicrosPerMillionTokens: value.inputMicrosPerMillionTokens,
outputMicrosPerMillionTokens: value.outputMicrosPerMillionTokens,
publishedAtMs: value.publishedAtMs,
});
if (
digest(value.catalogDigest, 'catalog digest') !== normalized.catalogDigest
) {
invalid('catalog digest is inconsistent');
}
return normalized;
}
export class StaticModelPriceCatalog implements ModelPriceCatalogResolver {
readonly #entries: ReadonlyMap<string, Readonly<ModelPriceCatalogEntry>>;
constructor(entries: readonly ModelPriceCatalogEntry[]) {
if (
!Array.isArray(entries) ||
entries.length < 1 ||
entries.length > MAX_MODEL_PRICE_CATALOG_ENTRIES
) {
invalid('catalog size is invalid');
}
const indexed = new Map<string, Readonly<ModelPriceCatalogEntry>>();
for (const value of entries) {
const entry = normalizeModelPriceCatalogEntry(value);
const key = catalogKey(entry);
if (indexed.has(key)) invalid('catalog identity is duplicated');
indexed.set(key, entry);
}
this.#entries = indexed;
}
async resolve(
lookupValue: Readonly<ModelPriceCatalogLookup>,
): Promise<Readonly<ModelPriceCatalogEntry> | null> {
const lookup = plainObject(lookupValue, 'catalog lookup');
exactKeys(
lookup,
lookupValue.signal === undefined
? ['model', 'priceRevision', 'provider']
: ['model', 'priceRevision', 'provider', 'signal'],
'catalog lookup',
);
if (
lookupValue.signal !== undefined &&
!(lookupValue.signal instanceof AbortSignal)
) {
invalid('catalog signal is invalid');
}
if (lookupValue.signal?.aborted) {
throw lookupValue.signal.reason;
}
return (
this.#entries.get(
catalogKey({
provider: identifier(lookupValue.provider, 'provider'),
model: identifier(lookupValue.model, 'model'),
priceRevision: identifier(
lookupValue.priceRevision,
'price revision',
),
}),
) ?? null
);
}
}
export function createModelInvocationPriceQuote(
entryValue: ModelPriceCatalogEntry,
options: Readonly<{
invocationId: string;
projectId: string;
modelPolicyRevision: string;
maxTotalTokens: number;
maxOutputTokens: number;
}>,
): Readonly<ModelInvocationPriceQuote> {
const entry = normalizeModelPriceCatalogEntry(entryValue);
const maxTotalTokens = integer(
options.maxTotalTokens,
'maximum total tokens',
1,
1_000_000_000_000,
);
const maxOutputTokens = integer(
options.maxOutputTokens,
'maximum output tokens',
1,
maxTotalTokens,
);
const expensiveOutput =
entry.outputMicrosPerMillionTokens > entry.inputMicrosPerMillionTokens;
const reservedOutputTokens = expensiveOutput ? maxOutputTokens : 0;
const reservedInputTokens = maxTotalTokens - reservedOutputTokens;
const unsigned = Object.freeze({
schema: MODEL_INVOCATION_PRICE_QUOTE_SCHEMA,
invocationId: identifier(options.invocationId, 'invocation id'),
projectId: identifier(options.projectId, 'Project id'),
modelPolicyRevision: identifier(
options.modelPolicyRevision,
'model policy revision',
),
provider: entry.provider,
model: entry.model,
priceRevision: entry.priceRevision,
currency: entry.currency,
inputMicrosPerMillionTokens: entry.inputMicrosPerMillionTokens,
outputMicrosPerMillionTokens: entry.outputMicrosPerMillionTokens,
maxTotalTokens,
maxOutputTokens,
reservedCostMicros: totalCost(
reservedInputTokens,
reservedOutputTokens,
entry.inputMicrosPerMillionTokens,
entry.outputMicrosPerMillionTokens,
),
catalogDigest: entry.catalogDigest,
});
return Object.freeze({
...unsigned,
quoteDigest: hash(QUOTE_DIGEST_DOMAIN, unsigned),
});
}
export function normalizeModelInvocationPriceQuote(
value: ModelInvocationPriceQuote,
): Readonly<ModelInvocationPriceQuote> {
const candidate = plainObject(value, 'price quote');
exactKeys(
candidate,
[
'catalogDigest',
'currency',
'inputMicrosPerMillionTokens',
'invocationId',
'maxOutputTokens',
'maxTotalTokens',
'model',
'modelPolicyRevision',
'outputMicrosPerMillionTokens',
'priceRevision',
'projectId',
'provider',
'quoteDigest',
'reservedCostMicros',
'schema',
],
'price quote',
);
if (
value.schema !== MODEL_INVOCATION_PRICE_QUOTE_SCHEMA ||
value.currency !== MODEL_PRICE_CURRENCY
) {
invalid('price quote schema or currency is invalid');
}
const maxTotalTokens = integer(
value.maxTotalTokens,
'maximum total tokens',
1,
1_000_000_000_000,
);
const maxOutputTokens = integer(
value.maxOutputTokens,
'maximum output tokens',
1,
maxTotalTokens,
);
const unsigned = Object.freeze({
schema: MODEL_INVOCATION_PRICE_QUOTE_SCHEMA,
invocationId: identifier(value.invocationId, 'invocation id'),
projectId: identifier(value.projectId, 'Project id'),
modelPolicyRevision: identifier(
value.modelPolicyRevision,
'model policy revision',
),
provider: identifier(value.provider, 'provider'),
model: identifier(value.model, 'model'),
priceRevision: identifier(value.priceRevision, 'price revision'),
currency: MODEL_PRICE_CURRENCY,
inputMicrosPerMillionTokens: integer(
value.inputMicrosPerMillionTokens,
'input price',
0,
MAX_MODEL_PRICE_RATE_MICROS,
),
outputMicrosPerMillionTokens: integer(
value.outputMicrosPerMillionTokens,
'output price',
0,
MAX_MODEL_PRICE_RATE_MICROS,
),
maxTotalTokens,
maxOutputTokens,
reservedCostMicros: integer(
value.reservedCostMicros,
'reserved cost',
0,
MAX_MODEL_PRICE_COST_MICROS,
),
catalogDigest: digest(value.catalogDigest, 'catalog digest'),
});
const expensiveOutput =
unsigned.outputMicrosPerMillionTokens >
unsigned.inputMicrosPerMillionTokens;
const expectedCost = totalCost(
unsigned.maxTotalTokens - (expensiveOutput ? unsigned.maxOutputTokens : 0),
expensiveOutput ? unsigned.maxOutputTokens : 0,
unsigned.inputMicrosPerMillionTokens,
unsigned.outputMicrosPerMillionTokens,
);
if (
unsigned.reservedCostMicros !== expectedCost ||
digest(value.quoteDigest, 'quote digest') !==
hash(QUOTE_DIGEST_DOMAIN, unsigned)
) {
invalid('price quote cost or digest is inconsistent');
}
return Object.freeze({
...unsigned,
quoteDigest: value.quoteDigest,
});
}
export function createModelInvocationPriceSettlement(
quoteValue: ModelInvocationPriceQuote,
completionValue: ModelInvocationCompletionRecord,
): Readonly<ModelInvocationPriceSettlement> | null {
const quote = normalizeModelInvocationPriceQuote(quoteValue);
const completion = normalizeModelInvocationCompletionRecord(completionValue);
if (
completion.invocationId !== quote.invocationId ||
completion.projectId !== quote.projectId
) {
invalid('completion is detached from its price quote');
}
if (!completion.usage) return null;
if (
completion.usage.totalTokens > quote.maxTotalTokens ||
completion.usage.outputTokens > quote.maxOutputTokens
) {
invalid('completion usage exceeds its price quote');
}
const costMicros = calculateModelInvocationPriceCost(quote, completion.usage);
if (completion.usage.costMicros !== costMicros) {
invalid('completion cost does not match its price quote');
}
const unsigned = Object.freeze({
schema: MODEL_INVOCATION_PRICE_SETTLEMENT_SCHEMA,
invocationId: quote.invocationId,
projectId: quote.projectId,
quoteDigest: quote.quoteDigest,
completionDigest: completion.completionDigest,
currency: quote.currency,
inputTokens: completion.usage.inputTokens,
outputTokens: completion.usage.outputTokens,
costMicros,
settledAtMs: completion.completedAtMs,
});
return Object.freeze({
...unsigned,
settlementDigest: hash(SETTLEMENT_DIGEST_DOMAIN, unsigned),
});
}
export function calculateModelInvocationPriceCost(
quoteValue: ModelInvocationPriceQuote,
usageValue: ModelUsage,
): number {
const quote = normalizeModelInvocationPriceQuote(quoteValue);
const usage = normalizeModelUsage(usageValue);
if (
usage.totalTokens > quote.maxTotalTokens ||
usage.outputTokens > quote.maxOutputTokens
) {
invalid('usage exceeds its price quote');
}
return totalCost(
usage.inputTokens,
usage.outputTokens,
quote.inputMicrosPerMillionTokens,
quote.outputMicrosPerMillionTokens,
);
}
export function priceModelUsage(
quoteValue: ModelInvocationPriceQuote,
usageValue: ModelUsage,
): Readonly<ModelUsage> {
const usage = normalizeModelUsage(usageValue);
return Object.freeze({
inputTokens: usage.inputTokens,
outputTokens: usage.outputTokens,
totalTokens: usage.totalTokens,
costMicros: calculateModelInvocationPriceCost(quoteValue, usage),
});
}
export function normalizeModelInvocationPriceSettlement(
value: ModelInvocationPriceSettlement,
quoteValue: ModelInvocationPriceQuote,
completionValue: ModelInvocationCompletionRecord,
): Readonly<ModelInvocationPriceSettlement> {
const record = plainObject(value, 'price settlement');
exactKeys(
record,
[
'completionDigest',
'costMicros',
'currency',
'inputTokens',
'invocationId',
'outputTokens',
'projectId',
'quoteDigest',
'schema',
'settledAtMs',
'settlementDigest',
],
'price settlement',
);
if (value.schema !== MODEL_INVOCATION_PRICE_SETTLEMENT_SCHEMA) {
invalid('price settlement schema is invalid');
}
const expected = createModelInvocationPriceSettlement(
quoteValue,
completionValue,
);
if (
!expected ||
Object.keys(expected).some(
(key) =>
record[key] !== expected[key as keyof ModelInvocationPriceSettlement],
)
) {
invalid('price settlement is inconsistent');
}
return expected;
}
export function isPricingAwareModelInvocationRepository(
value: ModelInvocationRepository,
): value is PricingAwareModelInvocationRepository {
const candidate = value as Partial<PricingAwareModelInvocationRepository>;
return (
typeof candidate.findPriceQuote === 'function' &&
typeof candidate.findPriceSettlement === 'function' &&
typeof candidate.admitWithPricing === 'function' &&
typeof candidate.completeWithPricing === 'function'
);
}
@@ -0,0 +1,101 @@
import type { DatabaseSync } from 'node:sqlite';
import type { LocalModelInvocationOperationAuthority } from '../../../model-invocation/localModelInvocationRepository';
import {
InvalidModelPriceCatalogError,
ModelPriceCatalogConflictError,
ModelPriceCatalogUnavailableError,
} from '../../modelPriceCatalog';
const IDENTITY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
export function unavailable(
cause?: unknown,
): ModelPriceCatalogUnavailableError {
return new ModelPriceCatalogUnavailableError({
cause: cause instanceof Error ? cause : undefined,
});
}
function sqliteConstraint(error: unknown): boolean {
if (!error || typeof error !== 'object') return false;
const code = (error as { code?: unknown }).code;
const number = (error as { errcode?: unknown }).errcode;
return (
(typeof code === 'string' && code.startsWith('ERR_SQLITE_CONSTRAINT')) ||
(typeof number === 'number' && (number & 0xff) === 19)
);
}
function mapStorageError(error: unknown): Error {
if (
error instanceof InvalidModelPriceCatalogError ||
error instanceof ModelPriceCatalogConflictError ||
error instanceof ModelPriceCatalogUnavailableError
) {
return error;
}
return sqliteConstraint(error)
? new ModelPriceCatalogConflictError()
: unavailable(error);
}
export function identifier(value: unknown, label: string): string {
if (typeof value !== 'string' || !IDENTITY_PATTERN.test(value)) {
throw new InvalidModelPriceCatalogError(`${label} is invalid`);
}
return value;
}
export class PrivateLocalAuthority
implements LocalModelInvocationOperationAuthority
{
readonly client: DatabaseSync;
#tail: Promise<void> = Promise.resolve();
#pending = 0;
constructor(client: DatabaseSync) {
this.client = client;
}
enqueue<T>(
work: () => Promise<T>,
rejection: (reason: 'closed' | 'busy') => Error,
): Promise<T> {
if (this.#pending >= 64) return Promise.reject(rejection('busy'));
this.#pending += 1;
const result = this.#tail.then(work, work);
this.#tail = result.then(
() => undefined,
() => undefined,
);
return result.finally(() => {
this.#pending -= 1;
});
}
}
export function isAuthority(
value: LocalModelInvocationOperationAuthority | DatabaseSync,
): value is LocalModelInvocationOperationAuthority {
return (
!!value &&
typeof value === 'object' &&
'client' in value &&
'enqueue' in value &&
typeof value.enqueue === 'function'
);
}
export function enqueueOperation<T>(
authority: LocalModelInvocationOperationAuthority,
work: () => T,
): Promise<T> {
return authority.enqueue(async () => {
try {
return work();
} catch (error) {
throw mapStorageError(error);
}
}, unavailable);
}
@@ -0,0 +1,316 @@
import type { DatabaseSync } from 'node:sqlite';
import type { LocalModelInvocationOperationAuthority } from '../../../model-invocation/localModelInvocationRepository';
import {
createModelPriceCatalogHead,
createModelPriceCatalogPublication,
InvalidModelPriceCatalogError,
ModelPriceCatalogConflictError,
normalizeModelPriceCatalogPublishCommand,
normalizeModelPriceCatalogTransitionCommand,
type ModelPriceCatalogHead,
type ModelPriceCatalogPublishCommand,
type ModelPriceCatalogTransitionCommand,
} from '../../modelPriceCatalog';
import {
createModelPriceCatalogAuthorization,
normalizeModelPriceCatalogPublishAuthorization,
normalizeModelPriceCatalogTransitionAuthorization,
type CommitAuthorizedModelPriceCatalogHeadResult,
type CommitAuthorizedModelPriceCatalogPublicationResult,
type ModelPriceCatalogAuthorizationCommand,
} from '../../modelPriceCatalogManagement';
import { enqueueOperation } from './authority';
import {
authorizationFromRow,
authorizationRows,
headFromRow,
headRows,
insertAuthorization,
integer,
isFreshReauthorizationReplay,
publicationFromRow,
publicationRows,
type Row,
} from './records';
export type BeforeAuthorizedMutation = (
client: DatabaseSync,
authorization: Readonly<ModelPriceCatalogAuthorizationCommand>,
) => void;
function rollback(client: DatabaseSync, began: boolean): void {
if (began && client.isTransaction) {
try {
client.exec('ROLLBACK');
} catch {
// Preserve the original failure; the shared authority owns close.
}
}
}
export function publishAuthorizedOperation(
authority: LocalModelInvocationOperationAuthority,
client: DatabaseSync,
beforeAuthorizedMutation: BeforeAuthorizedMutation | undefined,
commandValue: Readonly<ModelPriceCatalogPublishCommand>,
authorizationValue: Readonly<ModelPriceCatalogAuthorizationCommand>,
): Promise<Readonly<CommitAuthorizedModelPriceCatalogPublicationResult>> {
const command = normalizeModelPriceCatalogPublishCommand(commandValue);
const authorizationCommand = normalizeModelPriceCatalogPublishAuthorization(
command,
authorizationValue,
);
return enqueueOperation(authority, () => {
let began = false;
try {
client.exec('BEGIN IMMEDIATE');
began = true;
beforeAuthorizedMutation?.(client, authorizationCommand);
const rows = publicationRows(
client,
`mutation_id = ? OR (provider = ? AND model = ? AND price_revision = ?)`,
[
command.mutationId,
command.provider,
command.model,
command.priceRevision,
],
);
const storedAuthorizationRows = authorizationRows(
client,
'authorization_id = ? OR catalog_command_digest = ?',
[
authorizationCommand.authorizationId,
authorizationCommand.catalogCommandDigest,
],
);
if (rows.length > 1 || storedAuthorizationRows.length > 1)
throw new ModelPriceCatalogConflictError();
if (rows[0]) {
const publication = publicationFromRow(rows[0]);
const authorization = storedAuthorizationRows[0]
? authorizationFromRow(storedAuthorizationRows[0])
: null;
if (
publication.commandDigest !== command.commandDigest ||
!authorization ||
!isFreshReauthorizationReplay(authorization, authorizationCommand) ||
authorization.resultDigest !== publication.publicationDigest
)
throw new ModelPriceCatalogConflictError();
client.exec('COMMIT');
began = false;
return Object.freeze({
status: 'existing' as const,
publication,
authorization,
});
}
if (storedAuthorizationRows[0])
throw new ModelPriceCatalogConflictError();
const clock = client
.prepare(`SELECT CAST(unixepoch('subsec') * 1000 AS INTEGER) AS now`)
.get() as Row;
const nowMs = integer(clock, 'now');
const publication = createModelPriceCatalogPublication(command, nowMs);
const authorization = createModelPriceCatalogAuthorization(
authorizationCommand,
publication.publicationDigest,
nowMs,
);
client
.prepare(
`INSERT INTO "ModelPriceCatalogPublications" (provider, model, price_revision, catalog_digest, mutation_id, command_digest, publication_digest, published_at_ms, published_by_user_id, publication_json) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(
publication.entry.provider,
publication.entry.model,
publication.entry.priceRevision,
publication.entry.catalogDigest,
publication.mutationId,
publication.commandDigest,
publication.publicationDigest,
publication.entry.publishedAtMs,
publication.publishedByUserId,
JSON.stringify(publication),
);
insertAuthorization(client, authorization);
client.exec('COMMIT');
began = false;
return Object.freeze({
status: 'created' as const,
publication,
authorization,
});
} catch (error) {
rollback(client, began);
throw error;
}
});
}
export function transitionAuthorizedOperation(
authority: LocalModelInvocationOperationAuthority,
client: DatabaseSync,
beforeAuthorizedMutation: BeforeAuthorizedMutation | undefined,
commandValue: Readonly<ModelPriceCatalogTransitionCommand>,
authorizationValue: Readonly<ModelPriceCatalogAuthorizationCommand>,
): Promise<Readonly<CommitAuthorizedModelPriceCatalogHeadResult>> {
const command = normalizeModelPriceCatalogTransitionCommand(commandValue);
const authorizationCommand =
normalizeModelPriceCatalogTransitionAuthorization(
command,
authorizationValue,
);
return enqueueOperation(authority, () => {
let began = false;
try {
client.exec('BEGIN IMMEDIATE');
began = true;
beforeAuthorizedMutation?.(client, authorizationCommand);
const replayRows = headRows(
client,
`provider = ? AND model = ? AND (mutation_id = ? OR generation = ?)`,
[
command.provider,
command.model,
command.mutationId,
command.expectedGeneration + 1,
],
);
const storedAuthorizationRows = authorizationRows(
client,
'authorization_id = ? OR catalog_command_digest = ?',
[
authorizationCommand.authorizationId,
authorizationCommand.catalogCommandDigest,
],
);
if (replayRows.length > 1 || storedAuthorizationRows.length > 1)
throw new ModelPriceCatalogConflictError();
if (replayRows[0]) {
const head = headFromRow(replayRows[0]);
const authorization = storedAuthorizationRows[0]
? authorizationFromRow(storedAuthorizationRows[0])
: null;
if (
head.commandDigest !== command.commandDigest ||
!authorization ||
!isFreshReauthorizationReplay(authorization, authorizationCommand) ||
authorization.resultDigest !== head.headDigest
)
throw new ModelPriceCatalogConflictError();
client.exec('COMMIT');
began = false;
return Object.freeze({
status: 'existing' as const,
head,
authorization,
});
}
if (storedAuthorizationRows[0])
throw new ModelPriceCatalogConflictError();
const currentRows = headRows(client, 'provider = ? AND model = ?', [
command.provider,
command.model,
]);
if (
currentRows.length > 1 &&
integer(currentRows[0]!, 'generation') ===
integer(currentRows[1]!, 'generation')
)
throw new ModelPriceCatalogConflictError();
const current = currentRows[0] ? headFromRow(currentRows[0]) : null;
const targetRows =
command.priceRevision === null
? []
: publicationRows(
client,
'provider = ? AND model = ? AND price_revision = ?',
[command.provider, command.model, command.priceRevision],
);
if (targetRows.length > 1) throw new ModelPriceCatalogConflictError();
const target = targetRows[0] ? publicationFromRow(targetRows[0]) : null;
if (command.action === 'activate') {
if (!target) throw new ModelPriceCatalogConflictError();
const targetAuthorizationRows = authorizationRows(
client,
`operation = 'publish' AND result_digest = ?`,
[target.publicationDigest],
);
if (targetAuthorizationRows.length !== 1)
throw new ModelPriceCatalogConflictError();
const targetAuthorization = authorizationFromRow(
targetAuthorizationRows[0]!,
);
if (
authorizationCommand.decisionMode === 'separation_of_duty' &&
targetAuthorization.principal.subject.id ===
authorizationCommand.principal.subject.id
)
throw new ModelPriceCatalogConflictError();
}
const revoked =
command.priceRevision === null
? false
: !!client
.prepare(
`SELECT 1 FROM "ModelPriceCatalogHeads" WHERE provider = ? AND model = ? AND revoked_price_revision = ? LIMIT 1`,
)
.get(command.provider, command.model, command.priceRevision);
const clock = client
.prepare(`SELECT CAST(unixepoch('subsec') * 1000 AS INTEGER) AS now`)
.get() as Row;
const nowMs = integer(clock, 'now');
let head: Readonly<ModelPriceCatalogHead>;
try {
head = createModelPriceCatalogHead(
current,
command,
target,
revoked,
nowMs,
);
} catch (error) {
if (error instanceof InvalidModelPriceCatalogError)
throw new ModelPriceCatalogConflictError();
throw error;
}
const authorization = createModelPriceCatalogAuthorization(
authorizationCommand,
head.headDigest,
nowMs,
);
client
.prepare(
`INSERT INTO "ModelPriceCatalogHeads" (provider, model, generation, previous_head_digest, active_price_revision, active_catalog_digest, revoked_price_revision, revoked_catalog_digest, action, mutation_id, changed_by_user_id, changed_at_ms, command_digest, head_digest, head_json) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(
head.provider,
head.model,
head.generation,
head.previousHeadDigest,
head.activePriceRevision,
head.activeCatalogDigest,
head.revokedPriceRevision,
head.revokedCatalogDigest,
head.action,
head.mutationId,
head.changedByUserId,
head.changedAtMs,
head.commandDigest,
head.headDigest,
JSON.stringify(head),
);
insertAuthorization(client, authorization);
client.exec('COMMIT');
began = false;
return Object.freeze({ status: 'created' as const, head, authorization });
} catch (error) {
rollback(client, began);
throw error;
}
});
}
@@ -0,0 +1,209 @@
import type { DatabaseSync } from 'node:sqlite';
import type { LocalModelInvocationOperationAuthority } from '../../../model-invocation/localModelInvocationRepository';
import {
createModelPriceCatalogHead,
createModelPriceCatalogPublication,
InvalidModelPriceCatalogError,
ModelPriceCatalogConflictError,
normalizeModelPriceCatalogPublishCommand,
normalizeModelPriceCatalogTransitionCommand,
type CommitModelPriceCatalogHeadResult,
type CommitModelPriceCatalogPublicationResult,
type ModelPriceCatalogHead,
type ModelPriceCatalogPublishCommand,
type ModelPriceCatalogTransitionCommand,
} from '../../modelPriceCatalog';
import { enqueueOperation } from './authority';
import {
headFromRow,
headRows,
integer,
publicationFromRow,
publicationRows,
type Row,
} from './records';
function rollback(client: DatabaseSync, began: boolean): void {
if (began && client.isTransaction) {
try {
client.exec('ROLLBACK');
} catch {
// Preserve the original failure; the shared authority owns close.
}
}
}
export function publishOperation(
authority: LocalModelInvocationOperationAuthority,
client: DatabaseSync,
commandValue: Readonly<ModelPriceCatalogPublishCommand>,
): Promise<Readonly<CommitModelPriceCatalogPublicationResult>> {
const command = normalizeModelPriceCatalogPublishCommand(commandValue);
return enqueueOperation(authority, () => {
let began = false;
try {
client.exec('BEGIN IMMEDIATE');
began = true;
const rows = publicationRows(
client,
`mutation_id = ? OR (provider = ? AND model = ? AND price_revision = ?)`,
[
command.mutationId,
command.provider,
command.model,
command.priceRevision,
],
);
if (rows.length > 1) throw new ModelPriceCatalogConflictError();
if (rows[0]) {
const stored = publicationFromRow(rows[0]);
if (stored.commandDigest !== command.commandDigest)
throw new ModelPriceCatalogConflictError();
client.exec('COMMIT');
began = false;
return Object.freeze({
status: 'existing' as const,
publication: stored,
});
}
const clock = client
.prepare(`SELECT CAST(unixepoch('subsec') * 1000 AS INTEGER) AS now`)
.get() as Row;
const publication = createModelPriceCatalogPublication(
command,
integer(clock, 'now'),
);
client
.prepare(
`INSERT INTO "ModelPriceCatalogPublications" (provider, model, price_revision, catalog_digest, mutation_id, command_digest, publication_digest, published_at_ms, published_by_user_id, publication_json) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(
publication.entry.provider,
publication.entry.model,
publication.entry.priceRevision,
publication.entry.catalogDigest,
publication.mutationId,
publication.commandDigest,
publication.publicationDigest,
publication.entry.publishedAtMs,
publication.publishedByUserId,
JSON.stringify(publication),
);
client.exec('COMMIT');
began = false;
return Object.freeze({ status: 'created' as const, publication });
} catch (error) {
rollback(client, began);
throw error;
}
});
}
export function transitionOperation(
authority: LocalModelInvocationOperationAuthority,
client: DatabaseSync,
commandValue: Readonly<ModelPriceCatalogTransitionCommand>,
): Promise<Readonly<CommitModelPriceCatalogHeadResult>> {
const command = normalizeModelPriceCatalogTransitionCommand(commandValue);
return enqueueOperation(authority, () => {
let began = false;
try {
client.exec('BEGIN IMMEDIATE');
began = true;
const replayRows = headRows(
client,
`provider = ? AND model = ? AND (mutation_id = ? OR generation = ?)`,
[
command.provider,
command.model,
command.mutationId,
command.expectedGeneration + 1,
],
);
if (replayRows.length > 1) throw new ModelPriceCatalogConflictError();
if (replayRows[0]) {
const stored = headFromRow(replayRows[0]);
if (stored.commandDigest !== command.commandDigest)
throw new ModelPriceCatalogConflictError();
client.exec('COMMIT');
began = false;
return Object.freeze({ status: 'existing' as const, head: stored });
}
const currentRows = headRows(client, 'provider = ? AND model = ?', [
command.provider,
command.model,
]);
if (
currentRows.length > 1 &&
integer(currentRows[0]!, 'generation') ===
integer(currentRows[1]!, 'generation')
)
throw new ModelPriceCatalogConflictError();
const current = currentRows[0] ? headFromRow(currentRows[0]) : null;
const targetRows =
command.priceRevision === null
? []
: publicationRows(
client,
'provider = ? AND model = ? AND price_revision = ?',
[command.provider, command.model, command.priceRevision],
);
if (targetRows.length > 1) throw new ModelPriceCatalogConflictError();
const target = targetRows[0] ? publicationFromRow(targetRows[0]) : null;
const revoked =
command.priceRevision === null
? false
: !!client
.prepare(
`SELECT 1 FROM "ModelPriceCatalogHeads" WHERE provider = ? AND model = ? AND revoked_price_revision = ? LIMIT 1`,
)
.get(command.provider, command.model, command.priceRevision);
const clock = client
.prepare(`SELECT CAST(unixepoch('subsec') * 1000 AS INTEGER) AS now`)
.get() as Row;
let head: Readonly<ModelPriceCatalogHead>;
try {
head = createModelPriceCatalogHead(
current,
command,
target,
revoked,
integer(clock, 'now'),
);
} catch (error) {
if (error instanceof InvalidModelPriceCatalogError)
throw new ModelPriceCatalogConflictError();
throw error;
}
client
.prepare(
`INSERT INTO "ModelPriceCatalogHeads" (provider, model, generation, previous_head_digest, active_price_revision, active_catalog_digest, revoked_price_revision, revoked_catalog_digest, action, mutation_id, changed_by_user_id, changed_at_ms, command_digest, head_digest, head_json) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(
head.provider,
head.model,
head.generation,
head.previousHeadDigest,
head.activePriceRevision,
head.activeCatalogDigest,
head.revokedPriceRevision,
head.revokedCatalogDigest,
head.action,
head.mutationId,
head.changedByUserId,
head.changedAtMs,
head.commandDigest,
head.headDigest,
JSON.stringify(head),
);
client.exec('COMMIT');
began = false;
return Object.freeze({ status: 'created' as const, head });
} catch (error) {
rollback(client, began);
throw error;
}
});
}
@@ -0,0 +1,112 @@
import type { DatabaseSync } from 'node:sqlite';
import type { LocalModelInvocationOperationAuthority } from '../../../model-invocation/localModelInvocationRepository';
import {
InvalidModelPriceCatalogError,
type ModelPriceCatalogHead,
type ModelPriceCatalogPublication,
} from '../../modelPriceCatalog';
import type { ModelPriceCatalogAuthorization } from '../../modelPriceCatalogManagement';
import type {
ModelPriceCatalogEntry,
ModelPriceCatalogLookup,
} from '../../pricing';
import { enqueueOperation, identifier, unavailable } from './authority';
import {
authorizationFromRow,
authorizationRows,
headFromRow,
headRows,
integer,
publicationFromRow,
publicationRows,
type Row,
} from './records';
export function findAuthorizationOperation(
authority: LocalModelInvocationOperationAuthority,
client: DatabaseSync,
authorizationIdValue: string,
): Promise<Readonly<ModelPriceCatalogAuthorization> | null> {
const authorizationId = identifier(
authorizationIdValue,
'authorization identity',
);
return enqueueOperation(authority, () => {
const rows = authorizationRows(client, 'authorization_id = ?', [
authorizationId,
]);
if (rows.length > 1) throw unavailable();
return rows[0] ? authorizationFromRow(rows[0]) : null;
});
}
export function findPublicationOperation(
authority: LocalModelInvocationOperationAuthority,
client: DatabaseSync,
lookupValue: Omit<ModelPriceCatalogLookup, 'signal'>,
): Promise<Readonly<ModelPriceCatalogPublication> | null> {
const provider = identifier(lookupValue.provider, 'provider');
const model = identifier(lookupValue.model, 'model');
const priceRevision = identifier(lookupValue.priceRevision, 'price revision');
return enqueueOperation(authority, () => {
const rows = publicationRows(
client,
'provider = ? AND model = ? AND price_revision = ?',
[provider, model, priceRevision],
);
if (rows.length > 1) throw unavailable();
return rows[0] ? publicationFromRow(rows[0]) : null;
});
}
export function findCurrentOperation(
authority: LocalModelInvocationOperationAuthority,
client: DatabaseSync,
providerValue: string,
modelValue: string,
): Promise<Readonly<ModelPriceCatalogHead> | null> {
const provider = identifier(providerValue, 'provider');
const model = identifier(modelValue, 'model');
return enqueueOperation(authority, () => {
const rows = headRows(client, 'provider = ? AND model = ?', [
provider,
model,
]);
if (
rows.length > 1 &&
integer(rows[0]!, 'generation') === integer(rows[1]!, 'generation')
)
throw unavailable();
return rows[0] ? headFromRow(rows[0]) : null;
});
}
export async function resolveOperation(
authority: LocalModelInvocationOperationAuthority,
client: DatabaseSync,
lookupValue: Readonly<ModelPriceCatalogLookup>,
): Promise<Readonly<ModelPriceCatalogEntry> | null> {
if (
lookupValue.signal !== undefined &&
!(lookupValue.signal instanceof AbortSignal)
) {
throw new InvalidModelPriceCatalogError('catalog signal is invalid');
}
if (lookupValue.signal?.aborted) throw lookupValue.signal.reason;
const provider = identifier(lookupValue.provider, 'provider');
const model = identifier(lookupValue.model, 'model');
const priceRevision = identifier(lookupValue.priceRevision, 'price revision');
const entry = await enqueueOperation(authority, () => {
const rows = client
.prepare(
`SELECT publication.provider, publication.model, publication.price_revision AS "priceRevision", publication.catalog_digest AS "catalogDigest", publication.mutation_id AS "mutationId", publication.command_digest AS "commandDigest", publication.publication_digest AS "publicationDigest", publication.published_at_ms AS "publishedAtMs", publication.published_by_user_id AS "publishedByUserId", publication.publication_json AS "publicationJson" FROM "ModelPriceCatalogHeads" AS head JOIN "ModelPriceCatalogPublications" AS publication ON publication.provider = head.provider AND publication.model = head.model AND publication.price_revision = head.active_price_revision AND publication.catalog_digest = head.active_catalog_digest WHERE head.provider = ? AND head.model = ? AND head.active_price_revision = ? AND head.generation = (SELECT max(latest.generation) FROM "ModelPriceCatalogHeads" AS latest WHERE latest.provider = ? AND latest.model = ?) LIMIT 2`,
)
.all(provider, model, priceRevision, provider, model) as Row[];
if (rows.length > 1) throw unavailable();
return rows[0] ? publicationFromRow(rows[0]).entry : null;
});
if (lookupValue.signal?.aborted) throw lookupValue.signal.reason;
return entry;
}
@@ -0,0 +1,257 @@
import type { DatabaseSync, SQLInputValue } from 'node:sqlite';
import {
ModelPriceCatalogUnavailableError,
normalizeModelPriceCatalogHead,
normalizeModelPriceCatalogPublication,
type ModelPriceCatalogHead,
type ModelPriceCatalogPublication,
} from '../../modelPriceCatalog';
import {
normalizeModelPriceCatalogAuthorization,
type ModelPriceCatalogAuthorization,
type ModelPriceCatalogAuthorizationCommand,
} from '../../modelPriceCatalogManagement';
import { unavailable } from './authority';
export type Row = Record<string, unknown>;
export function text(row: Row, key: string): string {
const value = row[key];
if (typeof value !== 'string') throw unavailable();
return value;
}
export function integer(row: Row, key: string): number {
const value = row[key];
if (!Number.isSafeInteger(value) || (value as number) < 0) {
throw unavailable();
}
return value as number;
}
function nullableText(row: Row, key: string): string | null {
const value = row[key];
if (value !== null && typeof value !== 'string') throw unavailable();
return value;
}
function parsedJson(row: Row, key: string): Record<string, unknown> {
const value = text(row, key);
if (Buffer.byteLength(value, 'utf8') > 24 * 1024) throw unavailable();
try {
const parsed: unknown = JSON.parse(value);
if (
!parsed ||
typeof parsed !== 'object' ||
Array.isArray(parsed) ||
Object.getPrototypeOf(parsed) !== Object.prototype
) {
throw unavailable();
}
return parsed as Record<string, unknown>;
} catch (error) {
if (error instanceof ModelPriceCatalogUnavailableError) {
throw error;
}
throw unavailable(error);
}
}
export function publicationFromRow(
row: Row,
): Readonly<ModelPriceCatalogPublication> {
let publication: Readonly<ModelPriceCatalogPublication>;
try {
publication = normalizeModelPriceCatalogPublication(
parsedJson(
row,
'publicationJson',
) as unknown as ModelPriceCatalogPublication,
);
} catch {
throw unavailable();
}
if (
publication.entry.provider !== text(row, 'provider') ||
publication.entry.model !== text(row, 'model') ||
publication.entry.priceRevision !== text(row, 'priceRevision') ||
publication.entry.catalogDigest !== text(row, 'catalogDigest') ||
publication.mutationId !== text(row, 'mutationId') ||
publication.commandDigest !== text(row, 'commandDigest') ||
publication.publicationDigest !== text(row, 'publicationDigest') ||
publication.entry.publishedAtMs !== integer(row, 'publishedAtMs') ||
publication.publishedByUserId !== text(row, 'publishedByUserId') ||
JSON.stringify(publication) !== text(row, 'publicationJson')
) {
throw unavailable();
}
return publication;
}
export function headFromRow(row: Row): Readonly<ModelPriceCatalogHead> {
let head: Readonly<ModelPriceCatalogHead>;
try {
head = normalizeModelPriceCatalogHead(
parsedJson(row, 'headJson') as unknown as ModelPriceCatalogHead,
);
} catch {
throw unavailable();
}
if (
head.provider !== text(row, 'provider') ||
head.model !== text(row, 'model') ||
head.generation !== integer(row, 'generation') ||
head.previousHeadDigest !== nullableText(row, 'previousHeadDigest') ||
head.activePriceRevision !== nullableText(row, 'activePriceRevision') ||
head.activeCatalogDigest !== nullableText(row, 'activeCatalogDigest') ||
head.revokedPriceRevision !== nullableText(row, 'revokedPriceRevision') ||
head.revokedCatalogDigest !== nullableText(row, 'revokedCatalogDigest') ||
head.action !== text(row, 'action') ||
head.mutationId !== text(row, 'mutationId') ||
head.changedByUserId !== text(row, 'changedByUserId') ||
head.changedAtMs !== integer(row, 'changedAtMs') ||
head.commandDigest !== text(row, 'commandDigest') ||
head.headDigest !== text(row, 'headDigest') ||
JSON.stringify(head) !== text(row, 'headJson')
) {
throw unavailable();
}
return head;
}
export function authorizationFromRow(
row: Row,
): Readonly<ModelPriceCatalogAuthorization> {
const value = text(row, 'authorizationJson');
if (Buffer.byteLength(value, 'utf8') > 32 * 1024) throw unavailable();
let authorization: Readonly<ModelPriceCatalogAuthorization>;
try {
authorization = normalizeModelPriceCatalogAuthorization(
JSON.parse(value) as ModelPriceCatalogAuthorization,
);
} catch {
throw unavailable();
}
if (
authorization.authorizationId !== text(row, 'authorizationId') ||
authorization.requestId !== text(row, 'requestId') ||
authorization.operation !== text(row, 'operation') ||
authorization.provider !== text(row, 'provider') ||
authorization.model !== text(row, 'model') ||
authorization.priceRevision !== nullableText(row, 'priceRevision') ||
authorization.catalogCommandDigest !== text(row, 'catalogCommandDigest') ||
authorization.resultDigest !== text(row, 'resultDigest') ||
authorization.principal.subject.id !== text(row, 'userId') ||
authorization.principal.authenticationId !==
text(row, 'authenticationId') ||
authorization.principal.assurance !== text(row, 'assurance') ||
authorization.principal.authenticatedAtMs !==
integer(row, 'authenticatedAtMs') ||
authorization.principal.expiresAtMs !== integer(row, 'expiresAtMs') ||
authorization.policy.revision !== text(row, 'policyRevision') ||
authorization.policy.decisionDigest !== text(row, 'policyDecisionDigest') ||
authorization.decisionMode !== text(row, 'decisionMode') ||
authorization.commandDigest !== text(row, 'commandDigest') ||
authorization.committedAtMs !== integer(row, 'committedAtMs') ||
authorization.authorizationDigest !== text(row, 'authorizationDigest') ||
JSON.stringify(authorization.policy.reasons) !== text(row, 'reasonsJson') ||
JSON.stringify(authorization) !== value
) {
throw unavailable();
}
return authorization;
}
export function isFreshReauthorizationReplay(
stored: Readonly<ModelPriceCatalogAuthorization>,
incoming: Readonly<ModelPriceCatalogAuthorizationCommand>,
): boolean {
return (
stored.authorizationId === incoming.authorizationId &&
stored.requestId === incoming.requestId &&
stored.operation === incoming.operation &&
stored.provider === incoming.provider &&
stored.model === incoming.model &&
stored.priceRevision === incoming.priceRevision &&
stored.catalogCommandDigest === incoming.catalogCommandDigest &&
stored.principal.subject.type === incoming.principal.subject.type &&
stored.principal.subject.id === incoming.principal.subject.id &&
stored.principal.authenticationId === incoming.principal.authenticationId &&
stored.principal.assurance === incoming.principal.assurance &&
stored.policy.decisionDigest === incoming.policy.decisionDigest &&
stored.decisionMode === incoming.decisionMode
);
}
export function publicationRows(
client: DatabaseSync,
where: string,
values: readonly SQLInputValue[],
): readonly Row[] {
return client
.prepare(
`SELECT provider, model, price_revision AS "priceRevision", catalog_digest AS "catalogDigest", mutation_id AS "mutationId", command_digest AS "commandDigest", publication_digest AS "publicationDigest", published_at_ms AS "publishedAtMs", published_by_user_id AS "publishedByUserId", publication_json AS "publicationJson" FROM "ModelPriceCatalogPublications" WHERE ${where} ORDER BY provider, model, price_revision LIMIT 2`,
)
.all(...values) as Row[];
}
export function headRows(
client: DatabaseSync,
where: string,
values: readonly SQLInputValue[],
): readonly Row[] {
return client
.prepare(
`SELECT provider, model, generation, previous_head_digest AS "previousHeadDigest", active_price_revision AS "activePriceRevision", active_catalog_digest AS "activeCatalogDigest", revoked_price_revision AS "revokedPriceRevision", revoked_catalog_digest AS "revokedCatalogDigest", action, mutation_id AS "mutationId", changed_by_user_id AS "changedByUserId", changed_at_ms AS "changedAtMs", command_digest AS "commandDigest", head_digest AS "headDigest", head_json AS "headJson" FROM "ModelPriceCatalogHeads" WHERE ${where} ORDER BY generation DESC LIMIT 2`,
)
.all(...values) as Row[];
}
export function authorizationRows(
client: DatabaseSync,
where: string,
values: readonly SQLInputValue[],
): readonly Row[] {
return client
.prepare(
`SELECT authorization_id AS "authorizationId", request_id AS "requestId", operation, provider, model, price_revision AS "priceRevision", catalog_command_digest AS "catalogCommandDigest", result_digest AS "resultDigest", user_id AS "userId", authentication_id AS "authenticationId", assurance, authenticated_at_ms AS "authenticatedAtMs", expires_at_ms AS "expiresAtMs", policy_revision AS "policyRevision", policy_decision_digest AS "policyDecisionDigest", decision_mode AS "decisionMode", command_digest AS "commandDigest", committed_at_ms AS "committedAtMs", authorization_digest AS "authorizationDigest", reasons_json AS "reasonsJson", authorization_json AS "authorizationJson" FROM "ModelPriceCatalogAuthorizations" WHERE ${where} ORDER BY committed_at_ms DESC LIMIT 2`,
)
.all(...values) as Row[];
}
export function insertAuthorization(
client: DatabaseSync,
authorization: Readonly<ModelPriceCatalogAuthorization>,
): void {
client
.prepare(
`INSERT INTO "ModelPriceCatalogAuthorizations" (authorization_id, request_id, operation, provider, model, price_revision, catalog_command_digest, publication_digest, head_digest, result_digest, user_id, authentication_id, assurance, authenticated_at_ms, expires_at_ms, policy_revision, policy_decision_digest, decision_mode, command_digest, committed_at_ms, authorization_digest, reasons_json, authorization_json) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(
authorization.authorizationId,
authorization.requestId,
authorization.operation,
authorization.provider,
authorization.model,
authorization.priceRevision,
authorization.catalogCommandDigest,
authorization.operation === 'publish' ? authorization.resultDigest : null,
authorization.operation === 'publish' ? null : authorization.resultDigest,
authorization.resultDigest,
authorization.principal.subject.id,
authorization.principal.authenticationId,
authorization.principal.assurance,
authorization.principal.authenticatedAtMs,
authorization.principal.expiresAtMs,
authorization.policy.revision,
authorization.policy.decisionDigest,
authorization.decisionMode,
authorization.commandDigest,
authorization.committedAtMs,
authorization.authorizationDigest,
JSON.stringify(authorization.policy.reasons),
JSON.stringify(authorization),
);
}
@@ -0,0 +1,156 @@
import type { DatabaseSync } from 'node:sqlite';
import type { LocalModelInvocationOperationAuthority } from '../../../model-invocation/localModelInvocationRepository';
import {
InvalidModelPriceCatalogError,
type CommitModelPriceCatalogHeadResult,
type CommitModelPriceCatalogPublicationResult,
type ModelPriceCatalogAdministrationRepository,
type ModelPriceCatalogHead,
type ModelPriceCatalogPublication,
type ModelPriceCatalogPublishCommand,
type ModelPriceCatalogTransitionCommand,
} from '../../modelPriceCatalog';
import {
type CommitAuthorizedModelPriceCatalogHeadResult,
type CommitAuthorizedModelPriceCatalogPublicationResult,
type ModelPriceCatalogAuthorization,
type ModelPriceCatalogAuthorizationCommand,
type ModelPriceCatalogAuthorizedAdministrationRepository,
} from '../../modelPriceCatalogManagement';
import type {
ModelPriceCatalogEntry,
ModelPriceCatalogLookup,
} from '../../pricing';
import {
publishAuthorizedOperation,
transitionAuthorizedOperation,
} from './authorizedMutationOperations';
import { PrivateLocalAuthority, isAuthority } from './authority';
import {
publishOperation,
transitionOperation,
} from './catalogMutationOperations';
import {
findAuthorizationOperation,
findCurrentOperation,
findPublicationOperation,
resolveOperation,
} from './readOperations';
export interface LocalModelPriceCatalogRepositoryOptions {
readonly beforeAuthorizedMutation?: (
client: DatabaseSync,
authorization: Readonly<ModelPriceCatalogAuthorizationCommand>,
) => void;
}
export class LocalModelPriceCatalogRepository
implements
ModelPriceCatalogAdministrationRepository,
ModelPriceCatalogAuthorizedAdministrationRepository
{
readonly #authority: LocalModelInvocationOperationAuthority;
readonly #client: DatabaseSync;
readonly #beforeAuthorizedMutation:
| NonNullable<
LocalModelPriceCatalogRepositoryOptions['beforeAuthorizedMutation']
>
| undefined;
constructor(
authority: LocalModelInvocationOperationAuthority | DatabaseSync,
options: LocalModelPriceCatalogRepositoryOptions = {},
) {
if (
!options ||
typeof options !== 'object' ||
Array.isArray(options) ||
Object.keys(options).some((key) => key !== 'beforeAuthorizedMutation') ||
(options.beforeAuthorizedMutation !== undefined &&
typeof options.beforeAuthorizedMutation !== 'function')
) {
throw new InvalidModelPriceCatalogError(
'local repository options are invalid',
);
}
this.#authority = isAuthority(authority)
? authority
: new PrivateLocalAuthority(authority);
this.#client = this.#authority.client;
this.#beforeAuthorizedMutation = options.beforeAuthorizedMutation;
}
findAuthorization(
authorizationIdValue: string,
): Promise<Readonly<ModelPriceCatalogAuthorization> | null> {
return findAuthorizationOperation(
this.#authority,
this.#client,
authorizationIdValue,
);
}
findPublication(
lookupValue: Omit<ModelPriceCatalogLookup, 'signal'>,
): Promise<Readonly<ModelPriceCatalogPublication> | null> {
return findPublicationOperation(this.#authority, this.#client, lookupValue);
}
findCurrent(
providerValue: string,
modelValue: string,
): Promise<Readonly<ModelPriceCatalogHead> | null> {
return findCurrentOperation(
this.#authority,
this.#client,
providerValue,
modelValue,
);
}
resolve(
lookupValue: Readonly<ModelPriceCatalogLookup>,
): Promise<Readonly<ModelPriceCatalogEntry> | null> {
return resolveOperation(this.#authority, this.#client, lookupValue);
}
publish(
commandValue: Readonly<ModelPriceCatalogPublishCommand>,
): Promise<Readonly<CommitModelPriceCatalogPublicationResult>> {
return publishOperation(this.#authority, this.#client, commandValue);
}
transition(
commandValue: Readonly<ModelPriceCatalogTransitionCommand>,
): Promise<Readonly<CommitModelPriceCatalogHeadResult>> {
return transitionOperation(this.#authority, this.#client, commandValue);
}
publishAuthorized(
commandValue: Readonly<ModelPriceCatalogPublishCommand>,
authorizationValue: Readonly<ModelPriceCatalogAuthorizationCommand>,
): Promise<Readonly<CommitAuthorizedModelPriceCatalogPublicationResult>> {
return publishAuthorizedOperation(
this.#authority,
this.#client,
this.#beforeAuthorizedMutation,
commandValue,
authorizationValue,
);
}
transitionAuthorized(
commandValue: Readonly<ModelPriceCatalogTransitionCommand>,
authorizationValue: Readonly<ModelPriceCatalogAuthorizationCommand>,
): Promise<Readonly<CommitAuthorizedModelPriceCatalogHeadResult>> {
return transitionAuthorizedOperation(
this.#authority,
this.#client,
this.#beforeAuthorizedMutation,
commandValue,
authorizationValue,
);
}
}
@@ -0,0 +1,4 @@
export {
LocalModelPriceCatalogRepository,
type LocalModelPriceCatalogRepositoryOptions,
} from './local-model-price-catalog-repository/repository';
@@ -0,0 +1,93 @@
import type { PostgresClient, PostgresPool } from '@qinglong/runtime-core';
import {
InvalidModelPriceCatalogError,
ModelPriceCatalogConflictError,
ModelPriceCatalogUnavailableError,
} from '../../modelPriceCatalog';
const IDENTITY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
export function unavailable(
cause?: unknown,
): ModelPriceCatalogUnavailableError {
return new ModelPriceCatalogUnavailableError({
cause: cause instanceof Error ? cause : undefined,
});
}
function sqlState(error: unknown): string {
if (!error || typeof error !== 'object') return '';
const value = (error as { code?: unknown }).code;
return typeof value === 'string' ? value : '';
}
export function mapStorageError(error: unknown): Error {
if (
error instanceof InvalidModelPriceCatalogError ||
error instanceof ModelPriceCatalogConflictError ||
error instanceof ModelPriceCatalogUnavailableError
)
return error;
const state = sqlState(error);
if (
state === '23503' ||
state === '23505' ||
state === '23514' ||
state === '40001' ||
state === '40P01'
)
return new ModelPriceCatalogConflictError();
return unavailable(error);
}
export function identifier(value: unknown, label: string): string {
if (typeof value !== 'string' || !IDENTITY_PATTERN.test(value))
throw new InvalidModelPriceCatalogError(`${label} is invalid`);
return value;
}
export function assertPool(pool: PostgresPool): void {
if (
!pool ||
typeof pool.query !== 'function' ||
typeof pool.connect !== 'function'
)
throw new TypeError('PostgreSQL Model price catalog pool is invalid');
}
export async function acquireClient(
pool: PostgresPool,
): Promise<PostgresClient> {
try {
return await pool.connect();
} catch (error) {
throw unavailable(error);
}
}
export async function rollback(client: PostgresClient): Promise<void> {
await client.query('ROLLBACK').catch(() => undefined);
}
export async function runTransaction<T>(
pool: PostgresPool,
provider: string,
model: string,
work: (client: PostgresClient) => Promise<T>,
): Promise<T> {
const client = await acquireClient(pool);
try {
await client.query('BEGIN ISOLATION LEVEL SERIALIZABLE');
await client.query(
`SELECT pg_advisory_xact_lock(hashtextextended($1, 0))`,
[JSON.stringify([provider, model])],
);
return await work(client);
} catch (error) {
await rollback(client);
throw mapStorageError(error);
} finally {
client.release();
}
}
@@ -0,0 +1,244 @@
import type { PostgresPool } from '@qinglong/runtime-core';
import {
createModelPriceCatalogHead,
createModelPriceCatalogPublication,
InvalidModelPriceCatalogError,
ModelPriceCatalogConflictError,
normalizeModelPriceCatalogPublishCommand,
normalizeModelPriceCatalogTransitionCommand,
type ModelPriceCatalogHead,
type ModelPriceCatalogPublishCommand,
type ModelPriceCatalogTransitionCommand,
} from '../../modelPriceCatalog';
import {
createModelPriceCatalogAuthorization,
normalizeModelPriceCatalogPublishAuthorization,
normalizeModelPriceCatalogTransitionAuthorization,
type CommitAuthorizedModelPriceCatalogHeadResult,
type CommitAuthorizedModelPriceCatalogPublicationResult,
type ModelPriceCatalogAuthorizationCommand,
} from '../../modelPriceCatalogManagement';
import { runTransaction } from './authority';
import {
authorizationFromRow,
authorizationRows,
databaseClock,
headFromRow,
headRows,
insertAuthorization,
insertHead,
insertPublication,
integer,
publicationFromRow,
publicationRows,
wasRevoked,
} from './records';
export function publishAuthorizedOperation(
pool: PostgresPool,
commandValue: Readonly<ModelPriceCatalogPublishCommand>,
authorizationValue: Readonly<ModelPriceCatalogAuthorizationCommand>,
): Promise<Readonly<CommitAuthorizedModelPriceCatalogPublicationResult>> {
const command = normalizeModelPriceCatalogPublishCommand(commandValue);
const authorizationCommand = normalizeModelPriceCatalogPublishAuthorization(
command,
authorizationValue,
);
return runTransaction(
pool,
command.provider,
command.model,
async (client) => {
const rows = await publicationRows(
client,
`mutation_id = $1 OR (provider = $2 AND model = $3 AND price_revision = $4)`,
[
command.mutationId,
command.provider,
command.model,
command.priceRevision,
],
);
const storedAuthorizationRows = await authorizationRows(
client,
'authorization_id = $1 OR catalog_command_digest = $2',
[
authorizationCommand.authorizationId,
authorizationCommand.catalogCommandDigest,
],
);
if (rows.length > 1 || storedAuthorizationRows.length > 1)
throw new ModelPriceCatalogConflictError();
if (rows[0]) {
const publication = publicationFromRow(rows[0]);
const authorization = storedAuthorizationRows[0]
? authorizationFromRow(storedAuthorizationRows[0])
: null;
if (
publication.commandDigest !== command.commandDigest ||
!authorization ||
authorization.commandDigest !== authorizationCommand.commandDigest ||
authorization.resultDigest !== publication.publicationDigest
)
throw new ModelPriceCatalogConflictError();
await client.query('COMMIT');
return Object.freeze({
status: 'existing' as const,
publication,
authorization,
});
}
if (storedAuthorizationRows[0])
throw new ModelPriceCatalogConflictError();
const nowMs = await databaseClock(client);
const publication = createModelPriceCatalogPublication(command, nowMs);
const authorization = createModelPriceCatalogAuthorization(
authorizationCommand,
publication.publicationDigest,
nowMs,
);
await insertPublication(client, publication);
await insertAuthorization(client, authorization);
await client.query('COMMIT');
return Object.freeze({
status: 'created' as const,
publication,
authorization,
});
},
);
}
export function transitionAuthorizedOperation(
pool: PostgresPool,
commandValue: Readonly<ModelPriceCatalogTransitionCommand>,
authorizationValue: Readonly<ModelPriceCatalogAuthorizationCommand>,
): Promise<Readonly<CommitAuthorizedModelPriceCatalogHeadResult>> {
const command = normalizeModelPriceCatalogTransitionCommand(commandValue);
const authorizationCommand =
normalizeModelPriceCatalogTransitionAuthorization(
command,
authorizationValue,
);
return runTransaction(
pool,
command.provider,
command.model,
async (client) => {
const replayRows = await headRows(
client,
`provider = $1 AND model = $2 AND (mutation_id = $3 OR generation = $4)`,
[
command.provider,
command.model,
command.mutationId,
command.expectedGeneration + 1,
],
);
const storedAuthorizationRows = await authorizationRows(
client,
'authorization_id = $1 OR catalog_command_digest = $2',
[
authorizationCommand.authorizationId,
authorizationCommand.catalogCommandDigest,
],
);
if (replayRows.length > 1 || storedAuthorizationRows.length > 1)
throw new ModelPriceCatalogConflictError();
if (replayRows[0]) {
const head = headFromRow(replayRows[0]);
const authorization = storedAuthorizationRows[0]
? authorizationFromRow(storedAuthorizationRows[0])
: null;
if (
head.commandDigest !== command.commandDigest ||
!authorization ||
authorization.commandDigest !== authorizationCommand.commandDigest ||
authorization.resultDigest !== head.headDigest
)
throw new ModelPriceCatalogConflictError();
await client.query('COMMIT');
return Object.freeze({
status: 'existing' as const,
head,
authorization,
});
}
if (storedAuthorizationRows[0])
throw new ModelPriceCatalogConflictError();
const currentRows = await headRows(
client,
'provider = $1 AND model = $2',
[command.provider, command.model],
);
if (
currentRows.length > 1 &&
integer(currentRows[0]!, 'generation') ===
integer(currentRows[1]!, 'generation')
)
throw new ModelPriceCatalogConflictError();
const current = currentRows[0] ? headFromRow(currentRows[0]) : null;
const targetRows =
command.priceRevision === null
? []
: await publicationRows(
client,
'provider = $1 AND model = $2 AND price_revision = $3',
[command.provider, command.model, command.priceRevision],
);
if (targetRows.length > 1) throw new ModelPriceCatalogConflictError();
const target = targetRows[0] ? publicationFromRow(targetRows[0]) : null;
if (command.action === 'activate') {
if (!target) throw new ModelPriceCatalogConflictError();
const targetAuthorizationRows = await authorizationRows(
client,
`operation = 'publish' AND result_digest = $1`,
[target.publicationDigest],
);
if (targetAuthorizationRows.length !== 1)
throw new ModelPriceCatalogConflictError();
const targetAuthorization = authorizationFromRow(
targetAuthorizationRows[0]!,
);
if (
authorizationCommand.decisionMode === 'separation_of_duty' &&
targetAuthorization.principal.subject.id ===
authorizationCommand.principal.subject.id
)
throw new ModelPriceCatalogConflictError();
}
const revoked = await wasRevoked(
client,
command.provider,
command.model,
command.priceRevision,
);
const nowMs = await databaseClock(client);
let head: Readonly<ModelPriceCatalogHead>;
try {
head = createModelPriceCatalogHead(
current,
command,
target,
revoked,
nowMs,
);
} catch (error) {
if (error instanceof InvalidModelPriceCatalogError)
throw new ModelPriceCatalogConflictError();
throw error;
}
const authorization = createModelPriceCatalogAuthorization(
authorizationCommand,
head.headDigest,
nowMs,
);
await insertHead(client, head);
await insertAuthorization(client, authorization);
await client.query('COMMIT');
return Object.freeze({ status: 'created' as const, head, authorization });
},
);
}
@@ -0,0 +1,146 @@
import type { PostgresPool } from '@qinglong/runtime-core';
import {
createModelPriceCatalogHead,
createModelPriceCatalogPublication,
InvalidModelPriceCatalogError,
ModelPriceCatalogConflictError,
normalizeModelPriceCatalogPublishCommand,
normalizeModelPriceCatalogTransitionCommand,
type CommitModelPriceCatalogHeadResult,
type CommitModelPriceCatalogPublicationResult,
type ModelPriceCatalogHead,
type ModelPriceCatalogPublishCommand,
type ModelPriceCatalogTransitionCommand,
} from '../../modelPriceCatalog';
import { runTransaction } from './authority';
import {
databaseClock,
headFromRow,
headRows,
insertHead,
insertPublication,
integer,
publicationFromRow,
publicationRows,
wasRevoked,
} from './records';
export function publishOperation(
pool: PostgresPool,
commandValue: Readonly<ModelPriceCatalogPublishCommand>,
): Promise<Readonly<CommitModelPriceCatalogPublicationResult>> {
const command = normalizeModelPriceCatalogPublishCommand(commandValue);
return runTransaction(
pool,
command.provider,
command.model,
async (client) => {
const rows = await publicationRows(
client,
`mutation_id = $1 OR (provider = $2 AND model = $3 AND price_revision = $4)`,
[
command.mutationId,
command.provider,
command.model,
command.priceRevision,
],
);
if (rows.length > 1) throw new ModelPriceCatalogConflictError();
if (rows[0]) {
const stored = publicationFromRow(rows[0]);
if (stored.commandDigest !== command.commandDigest)
throw new ModelPriceCatalogConflictError();
await client.query('COMMIT');
return Object.freeze({
status: 'existing' as const,
publication: stored,
});
}
const publication = createModelPriceCatalogPublication(
command,
await databaseClock(client),
);
await insertPublication(client, publication);
await client.query('COMMIT');
return Object.freeze({ status: 'created' as const, publication });
},
);
}
export function transitionOperation(
pool: PostgresPool,
commandValue: Readonly<ModelPriceCatalogTransitionCommand>,
): Promise<Readonly<CommitModelPriceCatalogHeadResult>> {
const command = normalizeModelPriceCatalogTransitionCommand(commandValue);
return runTransaction(
pool,
command.provider,
command.model,
async (client) => {
const replayRows = await headRows(
client,
`provider = $1 AND model = $2 AND (mutation_id = $3 OR generation = $4)`,
[
command.provider,
command.model,
command.mutationId,
command.expectedGeneration + 1,
],
);
if (replayRows.length > 1) throw new ModelPriceCatalogConflictError();
if (replayRows[0]) {
const stored = headFromRow(replayRows[0]);
if (stored.commandDigest !== command.commandDigest)
throw new ModelPriceCatalogConflictError();
await client.query('COMMIT');
return Object.freeze({ status: 'existing' as const, head: stored });
}
const currentRows = await headRows(
client,
'provider = $1 AND model = $2',
[command.provider, command.model],
);
if (
currentRows.length > 1 &&
integer(currentRows[0]!, 'generation') ===
integer(currentRows[1]!, 'generation')
)
throw new ModelPriceCatalogConflictError();
const current = currentRows[0] ? headFromRow(currentRows[0]) : null;
const targetRows =
command.priceRevision === null
? []
: await publicationRows(
client,
'provider = $1 AND model = $2 AND price_revision = $3',
[command.provider, command.model, command.priceRevision],
);
if (targetRows.length > 1) throw new ModelPriceCatalogConflictError();
const target = targetRows[0] ? publicationFromRow(targetRows[0]) : null;
let head: Readonly<ModelPriceCatalogHead>;
try {
head = createModelPriceCatalogHead(
current,
command,
target,
await wasRevoked(
client,
command.provider,
command.model,
command.priceRevision,
),
await databaseClock(client),
);
} catch (error) {
if (error instanceof InvalidModelPriceCatalogError)
throw new ModelPriceCatalogConflictError();
throw error;
}
await insertHead(client, head);
await client.query('COMMIT');
return Object.freeze({ status: 'created' as const, head });
},
);
}
@@ -0,0 +1,137 @@
import type { PostgresPool } from '@qinglong/runtime-core';
import { POSTGRES_MODEL_INVOCATION_SCHEMA } from '../../../migration/modelInvocationMigration';
import {
InvalidModelPriceCatalogError,
type ModelPriceCatalogHead,
type ModelPriceCatalogPublication,
} from '../../modelPriceCatalog';
import type { ModelPriceCatalogAuthorization } from '../../modelPriceCatalogManagement';
import type {
ModelPriceCatalogEntry,
ModelPriceCatalogLookup,
} from '../../pricing';
import { identifier, mapStorageError, unavailable } from './authority';
import {
authorizationFromRow,
authorizationRows,
headFromRow,
headRows,
integer,
publicationFromRow,
publicationRows,
type Row,
} from './records';
export async function findAuthorizationOperation(
pool: PostgresPool,
authorizationIdValue: string,
): Promise<Readonly<ModelPriceCatalogAuthorization> | null> {
const authorizationId = identifier(
authorizationIdValue,
'authorization identity',
);
try {
const rows = await authorizationRows(pool, 'authorization_id = $1', [
authorizationId,
]);
if (rows.length > 1) throw unavailable();
return rows[0] ? authorizationFromRow(rows[0]) : null;
} catch (error) {
throw mapStorageError(error);
}
}
export async function findPublicationOperation(
pool: PostgresPool,
lookupValue: Omit<ModelPriceCatalogLookup, 'signal'>,
): Promise<Readonly<ModelPriceCatalogPublication> | null> {
const provider = identifier(lookupValue.provider, 'provider');
const model = identifier(lookupValue.model, 'model');
const priceRevision = identifier(lookupValue.priceRevision, 'price revision');
try {
const rows = await publicationRows(
pool,
'provider = $1 AND model = $2 AND price_revision = $3',
[provider, model, priceRevision],
);
if (rows.length > 1) throw unavailable();
return rows[0] ? publicationFromRow(rows[0]) : null;
} catch (error) {
throw mapStorageError(error);
}
}
export async function findCurrentOperation(
pool: PostgresPool,
providerValue: string,
modelValue: string,
): Promise<Readonly<ModelPriceCatalogHead> | null> {
const provider = identifier(providerValue, 'provider');
const model = identifier(modelValue, 'model');
try {
const rows = await headRows(pool, 'provider = $1 AND model = $2', [
provider,
model,
]);
if (
rows.length > 1 &&
integer(rows[0]!, 'generation') === integer(rows[1]!, 'generation')
)
throw unavailable();
return rows[0] ? headFromRow(rows[0]) : null;
} catch (error) {
throw mapStorageError(error);
}
}
export async function resolveOperation(
pool: PostgresPool,
lookupValue: Readonly<ModelPriceCatalogLookup>,
): Promise<Readonly<ModelPriceCatalogEntry> | null> {
if (
lookupValue.signal !== undefined &&
!(lookupValue.signal instanceof AbortSignal)
)
throw new InvalidModelPriceCatalogError('catalog signal is invalid');
if (lookupValue.signal?.aborted) throw lookupValue.signal.reason;
const provider = identifier(lookupValue.provider, 'provider');
const model = identifier(lookupValue.model, 'model');
const priceRevision = identifier(lookupValue.priceRevision, 'price revision');
let entry: Readonly<ModelPriceCatalogEntry> | null;
try {
const result = await pool.query<Row>(
`SELECT publication.provider, publication.model,
publication.price_revision AS "priceRevision",
publication.catalog_digest AS "catalogDigest",
publication.mutation_id AS "mutationId",
publication.command_digest AS "commandDigest",
publication.publication_digest AS "publicationDigest",
publication.published_at_ms AS "publishedAtMs",
publication.published_by_user_id AS "publishedByUserId",
publication.publication_json AS "publicationJson"
FROM "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_price_catalog_heads" AS head
JOIN "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_price_catalog_publications" AS publication
ON publication.provider = head.provider
AND publication.model = head.model
AND publication.price_revision = head.active_price_revision
AND publication.catalog_digest = head.active_catalog_digest
WHERE head.provider = $1 AND head.model = $2
AND head.active_price_revision = $3
AND head.generation = (
SELECT max(latest.generation)
FROM "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_price_catalog_heads" AS latest
WHERE latest.provider = $1 AND latest.model = $2
)
LIMIT 2`,
[provider, model, priceRevision],
);
if (result.rows.length > 1) throw unavailable();
entry = result.rows[0] ? publicationFromRow(result.rows[0]).entry : null;
} catch (error) {
throw mapStorageError(error);
}
if (lookupValue.signal?.aborted) throw lookupValue.signal.reason;
return entry;
}
@@ -0,0 +1,335 @@
import type { PostgresClient, PostgresQueryable } from '@qinglong/runtime-core';
import { POSTGRES_MODEL_INVOCATION_SCHEMA } from '../../../migration/modelInvocationMigration';
import {
normalizeModelPriceCatalogHead,
normalizeModelPriceCatalogPublication,
type ModelPriceCatalogHead,
type ModelPriceCatalogPublication,
} from '../../modelPriceCatalog';
import {
normalizeModelPriceCatalogAuthorization,
type ModelPriceCatalogAuthorization,
} from '../../modelPriceCatalogManagement';
import { unavailable } from './authority';
export type Row = Record<string, unknown>;
type Queryable = Pick<PostgresQueryable, 'query'>;
export function text(row: Row, key: string): string {
const value = row[key];
if (typeof value !== 'string') throw unavailable();
return value;
}
export function integer(row: Row, key: string): number {
const value = row[key];
if (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0)
return value;
if (typeof value === 'string' && /^(0|[1-9]\d*)$/.test(value)) {
const parsed = Number(value);
if (Number.isSafeInteger(parsed)) return parsed;
}
throw unavailable();
}
function nullableText(row: Row, key: string): string | null {
const value = row[key];
if (value !== null && typeof value !== 'string') throw unavailable();
return value;
}
function jsonObject(row: Row, key: string): Record<string, unknown> {
const value = row[key];
if (
value &&
typeof value === 'object' &&
!Array.isArray(value) &&
Object.getPrototypeOf(value) === Object.prototype
)
return value as Record<string, unknown>;
if (typeof value === 'string') {
try {
const parsed: unknown = JSON.parse(value);
if (
parsed &&
typeof parsed === 'object' &&
!Array.isArray(parsed) &&
Object.getPrototypeOf(parsed) === Object.prototype
)
return parsed as Record<string, unknown>;
} catch {
// Mapped below.
}
}
throw unavailable();
}
export function publicationFromRow(
row: Row,
): Readonly<ModelPriceCatalogPublication> {
let publication: Readonly<ModelPriceCatalogPublication>;
try {
publication = normalizeModelPriceCatalogPublication(
jsonObject(
row,
'publicationJson',
) as unknown as ModelPriceCatalogPublication,
);
} catch {
throw unavailable();
}
if (
publication.entry.provider !== text(row, 'provider') ||
publication.entry.model !== text(row, 'model') ||
publication.entry.priceRevision !== text(row, 'priceRevision') ||
publication.entry.catalogDigest !== text(row, 'catalogDigest') ||
publication.mutationId !== text(row, 'mutationId') ||
publication.commandDigest !== text(row, 'commandDigest') ||
publication.publicationDigest !== text(row, 'publicationDigest') ||
publication.entry.publishedAtMs !== integer(row, 'publishedAtMs') ||
publication.publishedByUserId !== text(row, 'publishedByUserId')
)
throw unavailable();
return publication;
}
export function headFromRow(row: Row): Readonly<ModelPriceCatalogHead> {
let head: Readonly<ModelPriceCatalogHead>;
try {
head = normalizeModelPriceCatalogHead(
jsonObject(row, 'headJson') as unknown as ModelPriceCatalogHead,
);
} catch {
throw unavailable();
}
if (
head.provider !== text(row, 'provider') ||
head.model !== text(row, 'model') ||
head.generation !== integer(row, 'generation') ||
head.previousHeadDigest !== nullableText(row, 'previousHeadDigest') ||
head.activePriceRevision !== nullableText(row, 'activePriceRevision') ||
head.activeCatalogDigest !== nullableText(row, 'activeCatalogDigest') ||
head.revokedPriceRevision !== nullableText(row, 'revokedPriceRevision') ||
head.revokedCatalogDigest !== nullableText(row, 'revokedCatalogDigest') ||
head.action !== text(row, 'action') ||
head.mutationId !== text(row, 'mutationId') ||
head.changedByUserId !== text(row, 'changedByUserId') ||
head.changedAtMs !== integer(row, 'changedAtMs') ||
head.commandDigest !== text(row, 'commandDigest') ||
head.headDigest !== text(row, 'headDigest')
)
throw unavailable();
return head;
}
export function authorizationFromRow(
row: Row,
): Readonly<ModelPriceCatalogAuthorization> {
let authorization: Readonly<ModelPriceCatalogAuthorization>;
try {
authorization = normalizeModelPriceCatalogAuthorization(
jsonObject(
row,
'authorizationJson',
) as unknown as ModelPriceCatalogAuthorization,
);
} catch {
throw unavailable();
}
let normalizedReasons: unknown;
try {
const reasons = row.reasonsJson;
normalizedReasons =
typeof reasons === 'string' ? JSON.parse(reasons) : reasons;
} catch {
throw unavailable();
}
if (
authorization.authorizationId !== text(row, 'authorizationId') ||
authorization.requestId !== text(row, 'requestId') ||
authorization.operation !== text(row, 'operation') ||
authorization.provider !== text(row, 'provider') ||
authorization.model !== text(row, 'model') ||
authorization.priceRevision !== nullableText(row, 'priceRevision') ||
authorization.catalogCommandDigest !== text(row, 'catalogCommandDigest') ||
authorization.resultDigest !== text(row, 'resultDigest') ||
authorization.principal.subject.id !== text(row, 'userId') ||
authorization.principal.authenticationId !==
text(row, 'authenticationId') ||
authorization.principal.assurance !== text(row, 'assurance') ||
authorization.principal.authenticatedAtMs !==
integer(row, 'authenticatedAtMs') ||
authorization.principal.expiresAtMs !== integer(row, 'expiresAtMs') ||
authorization.policy.revision !== text(row, 'policyRevision') ||
authorization.policy.decisionDigest !== text(row, 'policyDecisionDigest') ||
authorization.decisionMode !== text(row, 'decisionMode') ||
authorization.commandDigest !== text(row, 'commandDigest') ||
authorization.committedAtMs !== integer(row, 'committedAtMs') ||
authorization.authorizationDigest !== text(row, 'authorizationDigest') ||
JSON.stringify(authorization.policy.reasons) !==
JSON.stringify(normalizedReasons)
)
throw unavailable();
return authorization;
}
export async function publicationRows(
queryable: Queryable,
where: string,
values: readonly unknown[],
): Promise<readonly Row[]> {
const result = await queryable.query<Row>(
`SELECT provider, model, price_revision AS "priceRevision", catalog_digest AS "catalogDigest", mutation_id AS "mutationId", command_digest AS "commandDigest", publication_digest AS "publicationDigest", published_at_ms AS "publishedAtMs", published_by_user_id AS "publishedByUserId", publication_json AS "publicationJson" FROM "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_price_catalog_publications" WHERE ${where} ORDER BY provider, model, price_revision LIMIT 2`,
values,
);
return result.rows;
}
export async function headRows(
queryable: Queryable,
where: string,
values: readonly unknown[],
): Promise<readonly Row[]> {
const result = await queryable.query<Row>(
`SELECT provider, model, generation, previous_head_digest AS "previousHeadDigest", active_price_revision AS "activePriceRevision", active_catalog_digest AS "activeCatalogDigest", revoked_price_revision AS "revokedPriceRevision", revoked_catalog_digest AS "revokedCatalogDigest", action, mutation_id AS "mutationId", changed_by_user_id AS "changedByUserId", changed_at_ms AS "changedAtMs", command_digest AS "commandDigest", head_digest AS "headDigest", head_json AS "headJson" FROM "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_price_catalog_heads" WHERE ${where} ORDER BY generation DESC LIMIT 2`,
values,
);
return result.rows;
}
export async function authorizationRows(
queryable: Queryable,
where: string,
values: readonly unknown[],
): Promise<readonly Row[]> {
const result = await queryable.query<Row>(
`SELECT authorization_id AS "authorizationId", request_id AS "requestId", operation, provider, model, price_revision AS "priceRevision", catalog_command_digest AS "catalogCommandDigest", result_digest AS "resultDigest", user_id AS "userId", authentication_id AS "authenticationId", assurance, authenticated_at_ms AS "authenticatedAtMs", expires_at_ms AS "expiresAtMs", policy_revision AS "policyRevision", policy_decision_digest AS "policyDecisionDigest", decision_mode AS "decisionMode", command_digest AS "commandDigest", committed_at_ms AS "committedAtMs", authorization_digest AS "authorizationDigest", reasons_json AS "reasonsJson", authorization_json AS "authorizationJson" FROM "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_price_catalog_authorizations" WHERE ${where} ORDER BY committed_at_ms DESC LIMIT 2`,
values,
);
return result.rows;
}
export async function insertAuthorization(
client: PostgresClient,
authorization: Readonly<ModelPriceCatalogAuthorization>,
): Promise<void> {
await client.query(
`INSERT INTO "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_price_catalog_authorizations" (authorization_id, request_id, operation, provider, model, price_revision, catalog_command_digest, publication_digest, head_digest, result_digest, user_id, authentication_id, assurance, authenticated_at_ms, expires_at_ms, policy_revision, policy_decision_digest, decision_mode, command_digest, committed_at_ms, authorization_digest, reasons_json, authorization_json) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22::jsonb, $23::jsonb)`,
[
authorization.authorizationId,
authorization.requestId,
authorization.operation,
authorization.provider,
authorization.model,
authorization.priceRevision,
authorization.catalogCommandDigest,
authorization.operation === 'publish' ? authorization.resultDigest : null,
authorization.operation === 'publish' ? null : authorization.resultDigest,
authorization.resultDigest,
authorization.principal.subject.id,
authorization.principal.authenticationId,
authorization.principal.assurance,
authorization.principal.authenticatedAtMs,
authorization.principal.expiresAtMs,
authorization.policy.revision,
authorization.policy.decisionDigest,
authorization.decisionMode,
authorization.commandDigest,
authorization.committedAtMs,
authorization.authorizationDigest,
JSON.stringify(authorization.policy.reasons),
JSON.stringify(authorization),
],
);
}
export async function databaseClock(client: PostgresClient): Promise<number> {
const clock = await client.query<Row>(
`SELECT floor(extract(epoch FROM clock_timestamp()) * 1000)::bigint
AS now`,
);
if (clock.rows.length !== 1 || !clock.rows[0]) throw unavailable();
return integer(clock.rows[0], 'now');
}
export async function insertPublication(
client: PostgresClient,
publication: Readonly<ModelPriceCatalogPublication>,
): Promise<void> {
await client.query(
`INSERT INTO "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_price_catalog_publications" (
provider, model, price_revision, catalog_digest,
mutation_id, command_digest, publication_digest,
published_at_ms, published_by_user_id, publication_json
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10::jsonb
)`,
[
publication.entry.provider,
publication.entry.model,
publication.entry.priceRevision,
publication.entry.catalogDigest,
publication.mutationId,
publication.commandDigest,
publication.publicationDigest,
publication.entry.publishedAtMs,
publication.publishedByUserId,
JSON.stringify(publication),
],
);
}
export async function insertHead(
client: PostgresClient,
head: Readonly<ModelPriceCatalogHead>,
): Promise<void> {
await client.query(
`INSERT INTO "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_price_catalog_heads" (
provider, model, generation, previous_head_digest,
active_price_revision, active_catalog_digest,
revoked_price_revision, revoked_catalog_digest,
action, mutation_id, changed_by_user_id, changed_at_ms,
command_digest, head_digest, head_json
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12,
$13, $14, $15::jsonb
)`,
[
head.provider,
head.model,
head.generation,
head.previousHeadDigest,
head.activePriceRevision,
head.activeCatalogDigest,
head.revokedPriceRevision,
head.revokedCatalogDigest,
head.action,
head.mutationId,
head.changedByUserId,
head.changedAtMs,
head.commandDigest,
head.headDigest,
JSON.stringify(head),
],
);
}
export async function wasRevoked(
client: PostgresClient,
provider: string,
model: string,
priceRevision: string | null,
): Promise<boolean> {
if (priceRevision === null) return false;
const result = await client.query<Row>(
`SELECT 1
FROM "${POSTGRES_MODEL_INVOCATION_SCHEMA}"."model_price_catalog_heads"
WHERE provider = $1 AND model = $2
AND revoked_price_revision = $3
LIMIT 1`,
[provider, model, priceRevision],
);
return result.rows.length !== 0;
}
@@ -0,0 +1,157 @@
import type {
PostgresClient,
PostgresPool,
PostgresQueryable,
} from '@qinglong/runtime-core';
import {
type CommitModelPriceCatalogHeadResult,
type CommitModelPriceCatalogPublicationResult,
type ModelPriceCatalogAdministrationRepository,
type ModelPriceCatalogHead,
type ModelPriceCatalogPublication,
type ModelPriceCatalogPublishCommand,
type ModelPriceCatalogReader,
type ModelPriceCatalogTransitionCommand,
} from '../../modelPriceCatalog';
import {
type CommitAuthorizedModelPriceCatalogHeadResult,
type CommitAuthorizedModelPriceCatalogPublicationResult,
type ModelPriceCatalogAuthorization,
type ModelPriceCatalogAuthorizationCommand,
type ModelPriceCatalogAuthorizedAdministrationRepository,
} from '../../modelPriceCatalogManagement';
import type {
ModelPriceCatalogEntry,
ModelPriceCatalogLookup,
} from '../../pricing';
import {
publishAuthorizedOperation,
transitionAuthorizedOperation,
} from './authorizedMutationOperations';
import { assertPool } from './authority';
import {
publishOperation,
transitionOperation,
} from './catalogMutationOperations';
import {
findAuthorizationOperation,
findCurrentOperation,
findPublicationOperation,
resolveOperation,
} from './readOperations';
import {
authorizationRows,
headRows,
insertAuthorization,
publicationRows,
type Row,
} from './records';
type Queryable = Pick<PostgresQueryable, 'query'>;
export class PostgresModelPriceCatalogReader
implements ModelPriceCatalogReader
{
constructor(protected readonly pool: PostgresPool) {
assertPool(pool);
}
protected publicationRows(
queryable: Queryable,
where: string,
values: readonly unknown[],
): Promise<readonly Row[]> {
return publicationRows(queryable, where, values);
}
protected headRows(
queryable: Queryable,
where: string,
values: readonly unknown[],
): Promise<readonly Row[]> {
return headRows(queryable, where, values);
}
protected authorizationRows(
queryable: Queryable,
where: string,
values: readonly unknown[],
): Promise<readonly Row[]> {
return authorizationRows(queryable, where, values);
}
findPublication(
lookupValue: Omit<ModelPriceCatalogLookup, 'signal'>,
): Promise<Readonly<ModelPriceCatalogPublication> | null> {
return findPublicationOperation(this.pool, lookupValue);
}
findCurrent(
providerValue: string,
modelValue: string,
): Promise<Readonly<ModelPriceCatalogHead> | null> {
return findCurrentOperation(this.pool, providerValue, modelValue);
}
resolve(
lookupValue: Readonly<ModelPriceCatalogLookup>,
): Promise<Readonly<ModelPriceCatalogEntry> | null> {
return resolveOperation(this.pool, lookupValue);
}
}
export class PostgresModelPriceCatalogRepository
extends PostgresModelPriceCatalogReader
implements
ModelPriceCatalogAdministrationRepository,
ModelPriceCatalogAuthorizedAdministrationRepository
{
private insertAuthorization(
client: PostgresClient,
authorization: Readonly<ModelPriceCatalogAuthorization>,
): Promise<void> {
return insertAuthorization(client, authorization);
}
findAuthorization(
authorizationIdValue: string,
): Promise<Readonly<ModelPriceCatalogAuthorization> | null> {
return findAuthorizationOperation(this.pool, authorizationIdValue);
}
publish(
commandValue: Readonly<ModelPriceCatalogPublishCommand>,
): Promise<Readonly<CommitModelPriceCatalogPublicationResult>> {
return publishOperation(this.pool, commandValue);
}
transition(
commandValue: Readonly<ModelPriceCatalogTransitionCommand>,
): Promise<Readonly<CommitModelPriceCatalogHeadResult>> {
return transitionOperation(this.pool, commandValue);
}
publishAuthorized(
commandValue: Readonly<ModelPriceCatalogPublishCommand>,
authorizationValue: Readonly<ModelPriceCatalogAuthorizationCommand>,
): Promise<Readonly<CommitAuthorizedModelPriceCatalogPublicationResult>> {
return publishAuthorizedOperation(
this.pool,
commandValue,
authorizationValue,
);
}
transitionAuthorized(
commandValue: Readonly<ModelPriceCatalogTransitionCommand>,
authorizationValue: Readonly<ModelPriceCatalogAuthorizationCommand>,
): Promise<Readonly<CommitAuthorizedModelPriceCatalogHeadResult>> {
return transitionAuthorizedOperation(
this.pool,
commandValue,
authorizationValue,
);
}
}
@@ -0,0 +1,4 @@
export {
PostgresModelPriceCatalogReader,
PostgresModelPriceCatalogRepository,
} from './postgres-model-price-catalog-repository/repository';
@@ -0,0 +1,265 @@
import type {
GenerateRequest,
GenerateResult,
ModelChunk,
ModelInvocationContext,
ModelInvocationPolicyProvider,
ModelProvider,
} from '../../model-gateway/model';
import type {
DurableModelInvocationCoordinator,
ModelInvocationRecoverySummary,
} from '../../model-invocation/durableModelInvocationCoordinator';
import type { ModelInvocationSuccessfulCompletionSink } from '../../model-gateway/gateway';
import type {
ModelInvocationResolutionDecision,
ModelInvocationResolutionRecord,
ModelInvocationResolutionRepository,
ResolveModelInvocationOptions,
} from '../../model-invocation/modelInvocationResolution';
import type {
ModelInvocationUsageLedgerPage,
ModelInvocationUsageLedgerQuery,
ModelInvocationUsageLedgerRepository,
ModelInvocationUsageLedgerSummary,
ModelInvocationUsageLedgerSummaryQuery,
} from '../../usage/usageLedger';
import type {
ModelInvocationQuotaRepository,
ModelInvocationQuotaWindowUsage,
} from '../../usage/usageQuota';
import type {
ModelInvocationPriceQuote,
ModelInvocationPriceSettlement,
ModelInvocationPricingRepository,
ModelPriceCatalogResolver,
} from '../../pricing/pricing';
import type {
CommitAuthorizedModelPriceCatalogHeadResult,
CommitAuthorizedModelPriceCatalogPublicationResult,
ModelPriceCatalogAuthorizedAdministrationRepository,
ModelPriceCatalogManagementAuthorizer,
ModelPriceCatalogManagementDecisionMode,
ModelPriceCatalogManagementQuota,
PublishModelPriceCatalogRequest,
TransitionModelPriceCatalogRequest,
} from '../../pricing/modelPriceCatalogManagement';
export const MODEL_GATEWAY_PROFILES = [
'edge',
'standalone',
'cluster',
] as const;
export const MODEL_GATEWAY_PROFILE_STATES = [
'disabled',
'storage_ready',
'recovery_ready',
'active',
'draining',
'stopped',
'failed',
] as const;
export type ModelGatewayProfile = (typeof MODEL_GATEWAY_PROFILES)[number];
export type ModelGatewayProfileState =
(typeof MODEL_GATEWAY_PROFILE_STATES)[number];
export interface ModelGatewayProfileAudit {
readonly profile: ModelGatewayProfile;
readonly state: ModelGatewayProfileState;
readonly maxConcurrent?: number;
readonly recoveryLimit?: number;
readonly recovered?: number;
readonly alreadyCompleted?: number;
}
export interface ModelGatewayStorageAuthority {
readonly repository: ModelInvocationResolutionRepository &
ModelInvocationUsageLedgerRepository &
ModelInvocationQuotaRepository &
ModelInvocationPricingRepository;
readonly pricing: ModelPriceCatalogResolver;
close?(): void | Promise<void>;
}
export interface ModelGatewayProviderAuthority {
readonly providers: readonly ModelProvider[];
readonly policies: ModelInvocationPolicyProvider;
dispose?(): void | Promise<void>;
}
export interface BootstrapModelGatewayProfileOptions {
readonly enabled?: boolean;
readonly profile: ModelGatewayProfile;
readonly loadStorage: () => Promise<ModelGatewayStorageAuthority>;
readonly loadProviders: () => Promise<ModelGatewayProviderAuthority>;
readonly confirmActive?: () => void | Promise<void>;
readonly createSuccessfulCompletion?: (
coordinator: DurableModelInvocationCoordinator,
) => ModelInvocationSuccessfulCompletionSink;
readonly audit: (
record: Readonly<ModelGatewayProfileAudit>,
) => void | Promise<void>;
readonly maxConcurrent?: number;
readonly recoveryLimit?: number;
readonly now?: () => number;
}
export interface ActiveModelGatewayCapability {
readonly profile: ModelGatewayProfile;
readonly recovery: Readonly<ModelInvocationRecoverySummary>;
readonly maxConcurrent: number;
readonly recoveryLimit: number;
readonly accepting: boolean;
readonly activeOperations: number;
supportsSuccessfulCompletionSink(
sink: ModelInvocationSuccessfulCompletionSink,
): boolean;
generate(
request: GenerateRequest,
context: ModelInvocationContext,
): Promise<Readonly<GenerateResult>>;
stream(
request: GenerateRequest,
context: ModelInvocationContext,
): AsyncIterable<Readonly<ModelChunk>>;
resolveUnknown(options: {
readonly invocationId: string;
readonly decision: ModelInvocationResolutionDecision;
readonly resolvedByUserId: string;
readonly resolvedAtMs: number;
}): Promise<
Readonly<{
status: 'created' | 'existing';
record: Readonly<ModelInvocationResolutionRecord>;
}>
>;
listProjectUsage(
query: ModelInvocationUsageLedgerQuery,
): Promise<Readonly<ModelInvocationUsageLedgerPage>>;
summarizeProjectUsage(
query: ModelInvocationUsageLedgerSummaryQuery,
): Promise<Readonly<ModelInvocationUsageLedgerSummary>>;
readQuotaWindowUsage(
projectId: string,
atMs?: number,
): Promise<Readonly<ModelInvocationQuotaWindowUsage> | null>;
findPriceQuote(
invocationId: string,
): Promise<Readonly<ModelInvocationPriceQuote> | null>;
findPriceSettlement(
invocationId: string,
): Promise<Readonly<ModelInvocationPriceSettlement> | null>;
stop(): Promise<'draining' | 'stopped'>;
}
export type BootstrapModelGatewayProfileResult =
| {
readonly status: 'disabled';
readonly profile: ModelGatewayProfile;
stop(): Promise<'stopped'>;
}
| {
readonly status: 'active';
readonly profile: ModelGatewayProfile;
readonly capability: ActiveModelGatewayCapability;
};
export class ModelGatewayProfileUnavailableError extends Error {
readonly code = 'MODEL_GATEWAY_PROFILE_UNAVAILABLE';
constructor(options?: ErrorOptions) {
super('The model gateway Profile is unavailable', options);
this.name = 'ModelGatewayProfileUnavailableError';
}
}
export class ModelGatewayProfileDrainingError extends Error {
readonly code = 'MODEL_GATEWAY_PROFILE_DRAINING';
constructor() {
super('The model gateway Profile is draining');
this.name = 'ModelGatewayProfileDrainingError';
}
}
export const MODEL_PRICE_CATALOG_MANAGEMENT_PROFILE_STATES = [
'disabled',
'authority_ready',
'active',
'draining',
'stopped',
'failed',
] as const;
export type ModelPriceCatalogManagementProfileState =
(typeof MODEL_PRICE_CATALOG_MANAGEMENT_PROFILE_STATES)[number];
export interface ModelPriceCatalogManagementProfileAudit {
readonly profile: ModelGatewayProfile;
readonly state: ModelPriceCatalogManagementProfileState;
readonly decisionMode: ModelPriceCatalogManagementDecisionMode;
}
export interface ModelPriceCatalogManagementAuthority {
readonly repository: ModelPriceCatalogAuthorizedAdministrationRepository;
readonly authorizer: ModelPriceCatalogManagementAuthorizer;
readonly quota?: ModelPriceCatalogManagementQuota;
close?(): void | Promise<void>;
}
export interface BootstrapModelPriceCatalogManagementProfileOptions {
readonly enabled?: boolean;
readonly profile: ModelGatewayProfile;
readonly loadAuthority: () => Promise<ModelPriceCatalogManagementAuthority>;
readonly audit: (
record: Readonly<ModelPriceCatalogManagementProfileAudit>,
) => void | Promise<void>;
readonly now?: () => number;
}
export interface ActiveModelPriceCatalogManagementCapability {
readonly profile: ModelGatewayProfile;
readonly decisionMode: ModelPriceCatalogManagementDecisionMode;
readonly accepting: boolean;
readonly activeOperations: number;
publish(
request: Readonly<PublishModelPriceCatalogRequest>,
): Promise<Readonly<CommitAuthorizedModelPriceCatalogPublicationResult>>;
transition(
request: Readonly<TransitionModelPriceCatalogRequest>,
): Promise<Readonly<CommitAuthorizedModelPriceCatalogHeadResult>>;
stop(): Promise<'draining' | 'stopped'>;
}
export type BootstrapModelPriceCatalogManagementProfileResult =
| {
readonly status: 'disabled';
readonly profile: ModelGatewayProfile;
readonly decisionMode: ModelPriceCatalogManagementDecisionMode;
stop(): Promise<'stopped'>;
}
| {
readonly status: 'active';
readonly profile: ModelGatewayProfile;
readonly decisionMode: ModelPriceCatalogManagementDecisionMode;
readonly capability: ActiveModelPriceCatalogManagementCapability;
};
export class ModelPriceCatalogManagementProfileUnavailableError extends Error {
readonly code = 'MODEL_PRICE_CATALOG_MANAGEMENT_PROFILE_UNAVAILABLE';
constructor(options?: ErrorOptions) {
super('The model price catalog management Profile is unavailable', options);
this.name = 'ModelPriceCatalogManagementProfileUnavailableError';
}
}
export class ModelPriceCatalogManagementProfileDrainingError extends Error {
readonly code = 'MODEL_PRICE_CATALOG_MANAGEMENT_PROFILE_DRAINING';
constructor() {
super('The model price catalog management Profile is draining');
this.name = 'ModelPriceCatalogManagementProfileDrainingError';
}
}
@@ -0,0 +1,33 @@
import type {
ModelGatewayProviderAuthority,
ModelGatewayStorageAuthority,
ModelPriceCatalogManagementAuthority,
} from './contracts';
export async function bestEffortAudit<T>(
audit: (record: Readonly<T>) => void | Promise<void>,
record: Readonly<T>,
): Promise<void> {
try {
await audit(record);
} catch {
// Diagnostic failure cannot replace the activation failure.
}
}
export async function dispose(
authority: ModelGatewayProviderAuthority | ModelGatewayStorageAuthority,
method: 'dispose' | 'close',
): Promise<void> {
const operation =
method === 'dispose'
? (authority as ModelGatewayProviderAuthority).dispose
: (authority as ModelGatewayStorageAuthority).close;
if (operation) await operation.call(authority);
}
export async function closeModelPriceCatalogManagementAuthority(
authority: ModelPriceCatalogManagementAuthority,
): Promise<void> {
if (authority.close) await authority.close.call(authority);
}
@@ -0,0 +1,401 @@
import type {
GenerateRequest,
ModelInvocationContext,
} from '../../model-gateway/model';
import type { ModelInvocationSuccessfulCompletionSink } from '../../model-gateway/gateway';
import type { ResolveModelInvocationOptions } from '../../model-invocation/modelInvocationResolution';
import type {
ModelInvocationUsageLedgerQuery,
ModelInvocationUsageLedgerSummaryQuery,
} from '../../usage/usageLedger';
import {
MODEL_GATEWAY_PROFILES,
ModelGatewayProfileDrainingError,
ModelGatewayProfileUnavailableError,
type ActiveModelGatewayCapability,
type BootstrapModelGatewayProfileOptions,
type BootstrapModelGatewayProfileResult,
type ModelGatewayProviderAuthority,
type ModelGatewayStorageAuthority,
} from './contracts';
import { bestEffortAudit, dispose } from './lifecycle';
const DEFAULT_PROFILE_BUDGETS = Object.freeze({
edge: Object.freeze({ maxConcurrent: 1, recoveryLimit: 4 }),
standalone: Object.freeze({ maxConcurrent: 4, recoveryLimit: 32 }),
cluster: Object.freeze({ maxConcurrent: 32, recoveryLimit: 128 }),
});
function integer(
value: unknown,
minimum: number,
maximum: number,
label: string,
): number {
if (
!Number.isSafeInteger(value) ||
(value as number) < minimum ||
(value as number) > maximum
) {
throw new TypeError(`${label} is invalid`);
}
return value as number;
}
function assertOptions(options: BootstrapModelGatewayProfileOptions): void {
if (
!options ||
typeof options !== 'object' ||
Array.isArray(options) ||
!MODEL_GATEWAY_PROFILES.includes(options.profile) ||
(options.enabled !== undefined && typeof options.enabled !== 'boolean') ||
typeof options.loadStorage !== 'function' ||
typeof options.loadProviders !== 'function' ||
(options.confirmActive !== undefined &&
typeof options.confirmActive !== 'function') ||
(options.createSuccessfulCompletion !== undefined &&
typeof options.createSuccessfulCompletion !== 'function') ||
typeof options.audit !== 'function' ||
(options.now !== undefined && typeof options.now !== 'function')
) {
throw new TypeError('Model gateway Profile options are invalid');
}
}
function assertStorage(
value: ModelGatewayStorageAuthority,
): ModelGatewayStorageAuthority {
const repository = value?.repository;
if (
!value ||
typeof value !== 'object' ||
!repository ||
typeof repository.findStart !== 'function' ||
typeof repository.findCompletion !== 'function' ||
typeof repository.findResolution !== 'function' ||
typeof repository.readAuthority !== 'function' ||
typeof repository.listIncomplete !== 'function' ||
typeof repository.admit !== 'function' ||
typeof repository.complete !== 'function' ||
typeof repository.resolve !== 'function' ||
typeof repository.findUsage !== 'function' ||
typeof repository.listProjectUsage !== 'function' ||
typeof repository.summarizeProjectUsage !== 'function' ||
typeof repository.findQuotaReservation !== 'function' ||
typeof repository.findQuotaSettlement !== 'function' ||
typeof repository.readQuotaWindowUsage !== 'function' ||
typeof repository.findPriceQuote !== 'function' ||
typeof repository.findPriceSettlement !== 'function' ||
!value.pricing ||
typeof value.pricing.resolve !== 'function' ||
(value.close !== undefined && typeof value.close !== 'function')
) {
throw new TypeError('Model gateway storage authority is invalid');
}
return value;
}
function assertProviders(
value: ModelGatewayProviderAuthority,
): ModelGatewayProviderAuthority {
if (
!value ||
typeof value !== 'object' ||
!Array.isArray(value.providers) ||
!value.policies ||
typeof value.policies.resolve !== 'function' ||
(value.dispose !== undefined && typeof value.dispose !== 'function')
) {
throw new TypeError('Model gateway provider authority is invalid');
}
return value;
}
/**
* Profile-gated optional AI composition root. Disabled mode never invokes the
* storage/provider loaders. Enabled mode proves durable storage and bounded
* recovery before provider credentials become reachable.
*/
export async function bootstrapModelGatewayProfile(
options: BootstrapModelGatewayProfileOptions,
): Promise<BootstrapModelGatewayProfileResult> {
assertOptions(options);
if (!(options.enabled ?? false)) {
await options.audit({ profile: options.profile, state: 'disabled' });
return Object.freeze({
status: 'disabled',
profile: options.profile,
stop: async () => 'stopped' as const,
});
}
const defaults = DEFAULT_PROFILE_BUDGETS[options.profile];
const maxConcurrent = integer(
options.maxConcurrent ?? defaults.maxConcurrent,
1,
64,
'Model gateway Profile concurrency',
);
const recoveryLimit = integer(
options.recoveryLimit ?? defaults.recoveryLimit,
1,
128,
'Model gateway Profile recovery limit',
);
let storage: ModelGatewayStorageAuthority | undefined;
let providers: ModelGatewayProviderAuthority | undefined;
try {
storage = await options.loadStorage();
storage = assertStorage(storage);
await options.audit({
profile: options.profile,
state: 'storage_ready',
maxConcurrent,
recoveryLimit,
});
const [coordinatorModule, { BoundedModelGateway }] = await Promise.all([
import('../../model-invocation/durableModelInvocationCoordinator.js'),
import('../../model-gateway/gateway.js'),
]);
const recovery = await new coordinatorModule.DurableModelInvocationRecovery(
storage.repository,
).recover(recoveryLimit);
if (recovery.failed !== 0 || recovery.hasMore) {
throw new ModelGatewayProfileUnavailableError();
}
await options.audit({
profile: options.profile,
state: 'recovery_ready',
maxConcurrent,
recoveryLimit,
recovered: recovery.recovered,
alreadyCompleted: recovery.alreadyCompleted,
});
providers = await options.loadProviders();
providers = assertProviders(providers);
const { DurableModelInvocationResolutionCoordinator } = await import(
'../../model-invocation/modelInvocationResolution.js'
);
const coordinator = new coordinatorModule.DurableModelInvocationCoordinator(
storage.repository,
);
const successfulCompletion =
options.createSuccessfulCompletion?.(coordinator);
if (
successfulCompletion !== undefined &&
(!successfulCompletion ||
typeof successfulCompletion.record !== 'function')
) {
throw new ModelGatewayProfileUnavailableError();
}
const gateway = new BoundedModelGateway({
providers: providers.providers,
policies: providers.policies,
pricing: storage.pricing,
audit: coordinator,
...(successfulCompletion === undefined ? {} : { successfulCompletion }),
maxConcurrent,
...(options.now === undefined ? {} : { now: options.now }),
});
const resolver = new DurableModelInvocationResolutionCoordinator(
storage.repository,
);
let accepting = true;
let activeOperations = 0;
let stopPromise: Promise<'stopped'> | undefined;
let drainingAudited = false;
const auditDraining = async (): Promise<void> => {
if (drainingAudited) return;
drainingAudited = true;
await options.audit({
profile: options.profile,
state: 'draining',
maxConcurrent,
recoveryLimit,
});
};
const finalizeStop = (): Promise<'stopped'> => {
if (stopPromise) return stopPromise;
stopPromise = (async () => {
await dispose(providers!, 'dispose');
await dispose(storage!, 'close');
await options.audit({
profile: options.profile,
state: 'stopped',
maxConcurrent,
recoveryLimit,
});
return 'stopped' as const;
})();
return stopPromise;
};
const beginOperation = async (): Promise<void> => {
if (!accepting) throw new ModelGatewayProfileDrainingError();
if (options.confirmActive) {
try {
await options.confirmActive();
} catch {
accepting = false;
await auditDraining();
if (activeOperations === 0) await finalizeStop();
throw new ModelGatewayProfileDrainingError();
}
}
if (!accepting) throw new ModelGatewayProfileDrainingError();
activeOperations += 1;
};
const finishOperation = (): void => {
activeOperations -= 1;
if (!accepting && activeOperations === 0) {
void finalizeStop().catch(async () => {
await bestEffortAudit(options.audit, {
profile: options.profile,
state: 'failed',
maxConcurrent,
recoveryLimit,
});
});
}
};
const capability: ActiveModelGatewayCapability = Object.freeze({
profile: options.profile,
recovery,
maxConcurrent,
recoveryLimit,
get accepting() {
return accepting;
},
get activeOperations() {
return activeOperations;
},
supportsSuccessfulCompletionSink(
sink: ModelInvocationSuccessfulCompletionSink,
) {
return gateway.supportsSuccessfulCompletionSink(sink);
},
async generate(
request: GenerateRequest,
context: ModelInvocationContext,
) {
await beginOperation();
try {
return await gateway.generate(request, context);
} finally {
finishOperation();
}
},
async *stream(request: GenerateRequest, context: ModelInvocationContext) {
await beginOperation();
try {
yield* gateway.stream(request, context);
} finally {
finishOperation();
}
},
async resolveUnknown(resolutionOptions: ResolveModelInvocationOptions) {
await beginOperation();
try {
return await resolver.resolve(resolutionOptions);
} finally {
finishOperation();
}
},
async listProjectUsage(query: ModelInvocationUsageLedgerQuery) {
await beginOperation();
try {
return await storage!.repository.listProjectUsage(query);
} finally {
finishOperation();
}
},
async summarizeProjectUsage(
query: ModelInvocationUsageLedgerSummaryQuery,
) {
await beginOperation();
try {
return await storage!.repository.summarizeProjectUsage(query);
} finally {
finishOperation();
}
},
async readQuotaWindowUsage(projectId: string, atMs?: number) {
await beginOperation();
try {
return await storage!.repository.readQuotaWindowUsage(
projectId,
atMs,
);
} finally {
finishOperation();
}
},
async findPriceQuote(invocationId: string) {
await beginOperation();
try {
return await storage!.repository.findPriceQuote(invocationId);
} finally {
finishOperation();
}
},
async findPriceSettlement(invocationId: string) {
await beginOperation();
try {
return await storage!.repository.findPriceSettlement(invocationId);
} finally {
finishOperation();
}
},
async stop() {
accepting = false;
if (activeOperations !== 0) {
await auditDraining();
return 'draining';
}
return finalizeStop();
},
});
await options.audit({
profile: options.profile,
state: 'active',
maxConcurrent,
recoveryLimit,
recovered: recovery.recovered,
alreadyCompleted: recovery.alreadyCompleted,
});
return Object.freeze({
status: 'active',
profile: options.profile,
capability,
});
} catch (cause) {
if (providers) {
try {
await dispose(providers, 'dispose');
} catch {
// Preserve the activation failure.
}
}
if (storage) {
try {
await dispose(storage, 'close');
} catch {
// Preserve the activation failure.
}
}
await bestEffortAudit(options.audit, {
profile: options.profile,
state: 'failed',
maxConcurrent,
recoveryLimit,
});
throw cause instanceof ModelGatewayProfileUnavailableError
? cause
: new ModelGatewayProfileUnavailableError({
cause: cause instanceof Error ? cause : undefined,
});
}
}

Some files were not shown because too many files have changed in this diff Show More