feat(ql3): expose copilot diagnosis read model

This commit is contained in:
whyour
2026-08-16 00:41:34 +08:00
parent 774af4a4c9
commit f77b19e615
16 changed files with 2526 additions and 48 deletions
@@ -31,6 +31,10 @@ import {
type ClusterCopilotFailureDiagnosisProjection,
type CreateProductionClusterCopilotFailureDiagnosisOptions,
} from './copilot/failureDiagnosisComposition';
import {
createProductionClusterCopilotFailureDiagnosisReadService,
type CreateProductionClusterCopilotFailureDiagnosisReadServiceOptions,
} from './copilot/failureDiagnosisReadComposition';
export interface EnabledProductionClusterAiConfig {
readonly enabled: true;
@@ -54,6 +58,11 @@ export interface ProductionClusterAiControlApplicationOptions {
readonly createCopilot?: (
options: CreateProductionClusterCopilotFailureDiagnosisOptions,
) => Promise<Readonly<CopilotFailureDiagnosisApplicationService>>;
readonly createCopilotRead?: (
options: CreateProductionClusterCopilotFailureDiagnosisReadServiceOptions,
) => ReturnType<
typeof createProductionClusterCopilotFailureDiagnosisReadService
>;
readonly openAiDatabase?: ReturnType<typeof createPostgresDatabaseOpener>;
}
@@ -241,7 +250,9 @@ export async function startProductionClusterAiControlApplication(
Array.isArray(options) ||
typeof options.audit !== 'function'
) {
throw new TypeError('Production Cluster AI application options are invalid');
throw new TypeError(
'Production Cluster AI application options are invalid',
);
}
const startControl =
options.startControl ?? startProductionClusterControlApplication;
@@ -249,14 +260,20 @@ export async function startProductionClusterAiControlApplication(
options.bootstrapPrompt ?? bootstrapPostgresPluginPackagePromptApplication;
const createCopilot =
options.createCopilot ?? createProductionClusterCopilotFailureDiagnosis;
const createCopilotRead =
options.createCopilotRead ??
createProductionClusterCopilotFailureDiagnosisReadService;
if (
typeof startControl !== 'function' ||
typeof bootstrapPrompt !== 'function' ||
typeof createCopilot !== 'function' ||
typeof createCopilotRead !== 'function' ||
(options.openAiDatabase !== undefined &&
typeof options.openAiDatabase !== 'function')
) {
throw new TypeError('Production Cluster AI application factories are invalid');
throw new TypeError(
'Production Cluster AI application factories are invalid',
);
}
const copilotArtifactStore = options.control.workerIngress?.artifactStore;
if (
@@ -303,18 +320,27 @@ export async function startProductionClusterAiControlApplication(
let copilotApplication:
| Readonly<CopilotFailureDiagnosisApplicationService>
| undefined;
let copilotReadApplication:
| ReturnType<
typeof createProductionClusterCopilotFailureDiagnosisReadService
>
| undefined;
let copilotSuccessfulCompletion:
| CopilotFailureDiagnosisModelCompletionCoordinator
| undefined;
let stopPromise: Promise<ClusterControlStopResult> | undefined;
let promptOutputPolicy: ProjectPolicyEngine | undefined;
const promptOutputReadAuthorizer = Object.freeze({
async authorize(request: Readonly<{
principal: Parameters<ProjectPolicyEngine['authorize']>[0];
projectId: string;
}>) {
async authorize(
request: Readonly<{
principal: Parameters<ProjectPolicyEngine['authorize']>[0];
projectId: string;
}>,
) {
if (!aiDatabase) {
throw new Error('Cluster AI database is unavailable during output read');
throw new Error(
'Cluster AI database is unavailable during output read',
);
}
promptOutputPolicy ??= new ProjectPolicyEngine(
new PostgresProjectPolicyRepository(aiDatabase.pool),
@@ -361,7 +387,9 @@ export async function startProductionClusterAiControlApplication(
},
async loadProviders() {
if (!aiDatabase) {
throw new Error('Cluster AI database is unavailable during provider load');
throw new Error(
'Cluster AI database is unavailable during provider load',
);
}
const credentialStorage = new PostgresModelProviderCredentialReader(
aiDatabase.pool,
@@ -409,7 +437,11 @@ export async function startProductionClusterAiControlApplication(
throw new Error('Cluster AI Prompt application did not activate');
}
if (preparedCopilot !== undefined) {
if (!copilotSuccessfulCompletion || !aiDatabase || !copilotArtifactStore) {
if (
!copilotSuccessfulCompletion ||
!aiDatabase ||
!copilotArtifactStore
) {
throw new Error('Cluster Copilot shared authorities did not activate');
}
copilotApplication = await createCopilot({
@@ -419,6 +451,10 @@ export async function startProductionClusterAiControlApplication(
successfulCompletion: copilotSuccessfulCompletion,
artifactStore: copilotArtifactStore,
});
copilotReadApplication = createCopilotRead({
pool: aiDatabase.pool,
prepared: preparedCopilot,
});
}
controlApplication = await startControl({
...options.control,
@@ -445,11 +481,13 @@ export async function startProductionClusterAiControlApplication(
capability: promptApplication.promptExecutionOutputs,
},
}),
...(copilotApplication === undefined
...(copilotApplication === undefined ||
copilotReadApplication === undefined
? {}
: {
copilotFailureDiagnosis: {
capability: copilotApplication,
readCapability: copilotReadApplication,
},
}),
});
@@ -0,0 +1,57 @@
import { CopilotFailureDiagnosisReadService } from '@qinglong/ai/failure-diagnosis-read-model';
import { PostgresCopilotFailureDiagnosisAdmissionRepository } from '@qinglong/ai/postgres-failure-diagnosis-admission-storage';
import { PostgresCopilotFailureDiagnosisModelRepository } from '@qinglong/ai/postgres-failure-diagnosis-model-execution-storage';
import { PostgresCopilotFailureDiagnosisPreModelTerminalizationRepository } from '@qinglong/ai/failure-diagnosis-pre-model-terminalization';
import {
PostgresProjectPolicyRepository,
type QingLongPostgresPool,
} from '@qinglong/cluster-postgres/runtime';
import { ProjectPolicyEngine } from '@qinglong/runtime-core/project-policy';
import type { PreparedClusterCopilotFailureDiagnosisProjection } from './failureDiagnosisComposition';
export interface CreateProductionClusterCopilotFailureDiagnosisReadServiceOptions {
readonly pool: QingLongPostgresPool;
readonly prepared: PreparedClusterCopilotFailureDiagnosisProjection;
}
/**
* Reuses the execution Pool, durable repositories and projected output keys;
* creating this service owns no connection, listener or background lifecycle.
*/
export function createProductionClusterCopilotFailureDiagnosisReadService(
options: CreateProductionClusterCopilotFailureDiagnosisReadServiceOptions,
): Readonly<CopilotFailureDiagnosisReadService> {
if (
!options ||
typeof options !== 'object' ||
Array.isArray(options) ||
typeof options.pool?.query !== 'function' ||
typeof options.pool?.connect !== 'function' ||
typeof options.prepared?.outputKeys?.resolve !== 'function'
) {
throw new TypeError(
'Production Cluster Copilot failure diagnosis read dependencies are invalid',
);
}
const admissions = new PostgresCopilotFailureDiagnosisAdmissionRepository(
options.pool,
);
const models = new PostgresCopilotFailureDiagnosisModelRepository(
options.pool,
);
const terminalizations =
new PostgresCopilotFailureDiagnosisPreModelTerminalizationRepository(
options.pool,
);
return new CopilotFailureDiagnosisReadService({
admissions,
terminalizations,
finalizations: models,
models,
authorizer: new ProjectPolicyEngine(
new PostgresProjectPolicyRepository(options.pool),
),
keys: options.prepared.outputKeys,
});
}
@@ -65,6 +65,12 @@ import {
createClusterControlCopilotFailureDiagnosisRoute,
type ClusterCopilotFailureDiagnosisCapability,
} from '../copilot/failure-diagnosis/failureDiagnosisRoute';
import {
createClusterControlCopilotFailureDiagnosisInspectionRoute,
createClusterControlCopilotFailureDiagnosisOutputReadRoute,
type ClusterCopilotFailureDiagnosisInspectionCapability,
type ClusterCopilotFailureDiagnosisOutputReadCapability,
} from '../copilot/failure-diagnosis/failureDiagnosisReadRoutes';
export const PRODUCTION_CLUSTER_CONTROL_ROUTE_OPERATIONS = Object.freeze([
'task.get',
@@ -92,8 +98,14 @@ export const PRODUCTION_CLUSTER_CONTROL_OPTIONAL_ROUTE_OPERATIONS =
'prompt.execution.output.read',
'prompt.output.read',
'copilot.failure_diagnosis.execute',
'copilot.failure_diagnosis.read',
'copilot.failure_diagnosis.output.read',
] as const);
export interface ClusterCopilotFailureDiagnosisReadCapability
extends ClusterCopilotFailureDiagnosisInspectionCapability,
ClusterCopilotFailureDiagnosisOutputReadCapability {}
export interface ProductionClusterControlAssemblyOptions {
readonly createEventId?: ClusterRunCancellationEventIdFactory;
readonly promptCatalog?: Readonly<{
@@ -116,6 +128,7 @@ export interface ProductionClusterControlAssemblyOptions {
}>;
readonly copilotFailureDiagnosis?: Readonly<{
readonly capability: ClusterCopilotFailureDiagnosisCapability;
readonly readCapability?: ClusterCopilotFailureDiagnosisReadCapability;
}>;
readonly workerIngress?: Readonly<{
readonly config: EnabledClusterWorkerIngressConfig;
@@ -164,6 +177,7 @@ export interface ProductionClusterControlApplicationOptions
}>;
readonly copilotFailureDiagnosis?: Readonly<{
readonly capability: ClusterCopilotFailureDiagnosisCapability;
readonly readCapability?: ClusterCopilotFailureDiagnosisReadCapability;
}>;
readonly workerIngress?: ProductionClusterWorkerIngressOptions;
}
@@ -279,6 +293,16 @@ export function createProductionClusterControlApplicationStack(
options.copilotFailureDiagnosis.capability,
),
]),
...(options.copilotFailureDiagnosis?.readCapability === undefined
? []
: [
createClusterControlCopilotFailureDiagnosisInspectionRoute(
options.copilotFailureDiagnosis.readCapability,
),
createClusterControlCopilotFailureDiagnosisOutputReadRoute(
options.copilotFailureDiagnosis.readCapability,
),
]),
];
const routes = createClusterControlRouteRegistry(routeDefinitions);
const expectedRouteCount =
@@ -288,7 +312,8 @@ export function createProductionClusterControlApplicationStack(
(options.promptExecutionInspection === undefined ? 0 : 1) +
(options.promptOutputRead === undefined ? 0 : 1) +
(options.promptExecutionOutputRead === undefined ? 0 : 1) +
(options.copilotFailureDiagnosis === undefined ? 0 : 1);
(options.copilotFailureDiagnosis === undefined ? 0 : 1) +
(options.copilotFailureDiagnosis?.readCapability === undefined ? 0 : 2);
if (routes.size !== expectedRouteCount) {
throw new Error('Production cluster-control route allowlist is incomplete');
}