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
@@ -0,0 +1,635 @@
import { LocalExecutionCoordinator } from '@qinglong/local-execution/execution';
import {
LocalCompletionReceiptProcessor,
LocalExecutionControlCoordinator,
LocalExecutionControlLifecycle,
LocalExecutionControlScanner,
type LocalExecutionControlCycleSummary,
} from '@qinglong/local-execution/control';
import {
LocalDispatchPlanMaterializer,
LocalFileArtifactAllocator,
LocalRunDispatcher,
localArtifactCapacityPolicyForProfile,
} from '@qinglong/local-execution/dispatch';
import {
LocalRunStartupRecoveryCoordinator,
LocalWorkflowTaskStartupRecoveryCoordinator,
type LocalRunStartupRecoverySummary,
type LocalWorkflowTaskStartupRecoverySummary,
} from '@qinglong/local-execution/recovery';
import {
LocalSchedulerCoordinator,
LocalSchedulerLifecycle,
LocalWorkflowSchedulerCoordinator,
} from '@qinglong/local-execution/scheduler';
import {
CompletionReceiptFileStore,
LocalCompletionReceiptCleanupScanner,
LocalProcessController,
LocalProcessPersistedExecutionInspector,
LocalProcessLauncher,
type LocalCompletionReceiptCleanupSummary,
} from '@qinglong/local-process';
import {
EncryptedLocalSecretService,
LocalSecretKeyringFileProvider,
} from '@qinglong/local-secret';
import { MAX_PLUGIN_PACKAGE_INSTALL_RECOVERY_PAGE_SIZE } from '@qinglong/runtime-core/plugin-package-install';
import { MAX_PLUGIN_PACKAGE_RECOVERY_PAGES } from '@qinglong/runtime-core/plugin-package-recovery';
import {
MAX_PLUGIN_PACKAGE_TASK_PUBLICATION_RECOVERY_PAGES,
MAX_PLUGIN_PACKAGE_TASK_PUBLICATION_RECOVERY_PAGE_SIZE,
} from '@qinglong/runtime-core/plugin-package-task-publication';
import {
type LocalApplicationActivationAudit,
type LocalApplicationBootstrapOptions,
type LocalApplicationBootstrapResult,
type LocalApplicationEnabledBootstrapOptions,
type LocalApplicationProfile,
type LocalApplicationProductSurfaceLifecycle,
type LocalApplicationStopResult,
} from './contract';
import {
recoverLocalApplicationPluginPackages,
type LocalApplicationPluginPackageStartup,
} from './pluginPackageStartup';
import {
openLocalApplicationStorage,
type LocalApplicationReadyStorage,
} from './storageActivation';
import { LocalApplicationStartupRecoveryRequiredError } from './startupErrors';
export {
LocalApplicationPluginPackageAutomationPublicationRequiredError,
LocalApplicationPluginPackageRecoveryRequiredError,
LocalApplicationPluginPackageTaskPublicationRequiredError,
LocalApplicationPluginPackageToolSnapshotRequiredError,
LocalApplicationStartupRecoveryRequiredError,
} from './startupErrors';
const EXECUTION_CONTROL_POLICIES = Object.freeze({
edge: Object.freeze({
cleanupIntervalMs: 5 * 60_000,
cleanupPageSize: 8,
controlIntervalMs: 5_000,
controlPageSize: 4,
maxDrainPages: 2,
retentionMs: 24 * 60 * 60_000,
stopTimeoutMs: 5_000,
}),
standalone: Object.freeze({
cleanupIntervalMs: 60_000,
cleanupPageSize: 32,
controlIntervalMs: 1_000,
controlPageSize: 32,
maxDrainPages: 8,
retentionMs: 60 * 60_000,
stopTimeoutMs: 10_000,
}),
});
function assertProfile(
profile: unknown,
): asserts profile is LocalApplicationProfile {
if (profile !== 'edge' && profile !== 'standalone') {
throw new TypeError('Local application Profile is invalid');
}
}
function assertEnabledBoundary(
options: LocalApplicationBootstrapOptions,
): void {
if (!options || typeof options !== 'object' || Array.isArray(options)) {
throw new TypeError('Local application bootstrap options are invalid');
}
if (options.enabled !== undefined && typeof options.enabled !== 'boolean') {
throw new TypeError('Local application enabled flag is invalid');
}
assertProfile(options.profile);
if (typeof options.applicationAudit !== 'function') {
throw new TypeError('Local application audit sink is invalid');
}
}
function assertActiveBoundary(
options: LocalApplicationBootstrapOptions,
): asserts options is LocalApplicationEnabledBootstrapOptions {
if (options.enabled !== true) {
throw new TypeError('Local application enabled configuration is invalid');
}
if (typeof options.audit !== 'function') {
throw new TypeError('Local application storage audit sink is invalid');
}
if (
options.storageMode !== 'fresh' &&
typeof options.adoptionAudit !== 'function'
) {
throw new TypeError('Local application adoption audit sink is invalid');
}
if (
options.storageMode === 'fresh' &&
typeof options.databasePath !== 'string'
) {
throw new TypeError('Local application fresh database path is invalid');
}
if (typeof options.receiptRoot !== 'string') {
throw new TypeError('Local application receipt root is invalid');
}
if (typeof options.artifactRoot !== 'string') {
throw new TypeError('Local application Artifact root is invalid');
}
if (typeof options.secretKeyringPath !== 'string') {
throw new TypeError('Local application Secret keyring path is invalid');
}
if (
options.productSurface !== undefined &&
typeof options.productSurface?.start !== 'function'
) {
throw new TypeError('Local application product surface is invalid');
}
const pluginPackages = options.pluginPackages;
if (
!pluginPackages ||
typeof pluginPackages !== 'object' ||
Array.isArray(pluginPackages) ||
Object.keys(pluginPackages).some(
(key) =>
![
'stageProvider',
'stagingRoot',
'activationRoot',
'now',
'pageSize',
'maxPages',
'taskPublicationPageSize',
'taskPublicationMaxPages',
].includes(key),
) ||
!pluginPackages.stageProvider ||
typeof pluginPackages.stageProvider.stage !== 'function' ||
typeof pluginPackages.stagingRoot !== 'string' ||
typeof pluginPackages.activationRoot !== 'string' ||
typeof pluginPackages.now !== 'function' ||
(pluginPackages.pageSize !== undefined &&
(!Number.isSafeInteger(pluginPackages.pageSize) ||
pluginPackages.pageSize < 1 ||
pluginPackages.pageSize >
MAX_PLUGIN_PACKAGE_INSTALL_RECOVERY_PAGE_SIZE)) ||
(pluginPackages.maxPages !== undefined &&
(!Number.isSafeInteger(pluginPackages.maxPages) ||
pluginPackages.maxPages < 1 ||
pluginPackages.maxPages > MAX_PLUGIN_PACKAGE_RECOVERY_PAGES)) ||
(pluginPackages.taskPublicationPageSize !== undefined &&
(!Number.isSafeInteger(pluginPackages.taskPublicationPageSize) ||
pluginPackages.taskPublicationPageSize < 1 ||
pluginPackages.taskPublicationPageSize >
MAX_PLUGIN_PACKAGE_TASK_PUBLICATION_RECOVERY_PAGE_SIZE)) ||
(pluginPackages.taskPublicationMaxPages !== undefined &&
(!Number.isSafeInteger(pluginPackages.taskPublicationMaxPages) ||
pluginPackages.taskPublicationMaxPages < 1 ||
pluginPackages.taskPublicationMaxPages >
MAX_PLUGIN_PACKAGE_TASK_PUBLICATION_RECOVERY_PAGES))
) {
throw new TypeError(
'Local application Plugin Package recovery configuration is invalid',
);
}
}
function oneOrAggregate(errors: unknown[], message: string): unknown {
return errors.length === 1 ? errors[0] : new AggregateError(errors, message);
}
async function bestEffortAudit(
options: LocalApplicationBootstrapOptions,
record: LocalApplicationActivationAudit,
): Promise<void> {
try {
await options.applicationAudit(record);
} catch {
// Diagnostics cannot replace the activation or shutdown result.
}
}
export async function bootstrapLocalApplication(
options: LocalApplicationBootstrapOptions,
): Promise<LocalApplicationBootstrapResult> {
assertEnabledBoundary(options);
if (options.enabled !== true) {
await options.applicationAudit({
profile: options.profile,
state: 'disabled',
});
return Object.freeze({
status: 'disabled' as const,
profile: options.profile,
stop: async () => 'stopped' as const,
});
}
assertActiveBoundary(options);
let storage: LocalApplicationReadyStorage | undefined;
let schedulerLifecycle: LocalSchedulerLifecycle | undefined;
let executionControlLifecycle: LocalExecutionControlLifecycle | undefined;
let runRecovery: LocalRunStartupRecoverySummary | undefined;
let workflowTaskRecovery: LocalWorkflowTaskStartupRecoverySummary | undefined;
let receiptCleanup: LocalCompletionReceiptCleanupSummary | undefined;
let executionControl: LocalExecutionControlCycleSummary | undefined;
let pluginPackageStartup: LocalApplicationPluginPackageStartup | undefined;
let productSurfaceLifecycle:
| Readonly<LocalApplicationProductSurfaceLifecycle>
| undefined;
try {
storage = await openLocalApplicationStorage(options);
await options.applicationAudit({
profile: options.profile,
state: 'storage_ready',
});
pluginPackageStartup = await recoverLocalApplicationPluginPackages(
options,
storage,
);
const secretKeys = new LocalSecretKeyringFileProvider(
options.secretKeyringPath,
);
const activeSecretKey = await secretKeys.active();
activeSecretKey.key.fill(0);
const localSecrets = new EncryptedLocalSecretService(
storage.localSecrets,
secretKeys,
);
await options.applicationAudit({
profile: options.profile,
state: 'secrets_ready',
});
const executionPolicy = EXECUTION_CONTROL_POLICIES[options.profile];
const receipts = new CompletionReceiptFileStore(options.receiptRoot);
const localProcessLauncher = new LocalProcessLauncher(
storage.completionReceipts,
{
receiptRoot: options.receiptRoot,
},
);
const localProcessController = new LocalProcessController();
const workflowRuntime = await storage.pluginPackageWorkflowRuntime();
const localProcess = new LocalExecutionCoordinator(
storage.runs,
localProcessLauncher,
localProcessController,
{ workflowTasks: workflowRuntime.executions },
);
const completionProcessor = new LocalCompletionReceiptProcessor(
storage.runs,
receipts,
{
journal: storage.completionReceipts,
quarantineRetentionMs: executionPolicy.retentionMs,
workflowTasks: workflowRuntime.executions,
},
);
executionControlLifecycle = new LocalExecutionControlLifecycle(
completionProcessor,
new LocalExecutionControlScanner(
storage.executionControl,
new LocalExecutionControlCoordinator(
storage.runs,
completionProcessor,
localProcessController,
{ workflowTasks: workflowRuntime.executions },
),
),
new LocalCompletionReceiptCleanupScanner(
storage.completionReceipts,
receipts,
{
terminalMissingRetentionMs: executionPolicy.retentionMs,
},
),
{
intervalMs: executionPolicy.controlIntervalMs,
pageSize: executionPolicy.controlPageSize,
cleanupIntervalMs: executionPolicy.cleanupIntervalMs,
cleanupPageSize: executionPolicy.cleanupPageSize,
stopTimeoutMs: executionPolicy.stopTimeoutMs,
maxDrainPages: executionPolicy.maxDrainPages,
onDiagnostic: async (error) => {
if (error === undefined) return;
await bestEffortAudit(options, {
profile: options.profile,
state: 'execution_control_degraded',
});
},
},
);
const localProcessDispatcher = new LocalRunDispatcher(
storage.dispatch,
new LocalDispatchPlanMaterializer(
storage.dispatch,
new LocalFileArtifactAllocator(
options.artifactRoot,
localArtifactCapacityPolicyForProfile(options.profile),
),
localSecrets,
),
localProcess,
{
pageSize: options.profile === 'edge' ? 4 : 16,
maxPages: 1,
onCompletion: (attemptId) => {
executionControlLifecycle?.notifyCompletion(attemptId);
},
},
);
const scheduler = new LocalSchedulerCoordinator(storage.schedules, {
pageSize: options.profile === 'edge' ? 4 : 16,
misfireGraceMs: options.profile === 'edge' ? 30_000 : 5_000,
});
const workflowScheduler = new LocalWorkflowSchedulerCoordinator(
scheduler,
workflowRuntime.cancellation,
workflowRuntime.frontier,
workflowRuntime.taskAttempts,
localProcessDispatcher,
{
cancellationPageSize: options.profile === 'edge' ? 4 : 32,
cancellationMaxPages: options.profile === 'edge' ? 1 : 4,
frontierPageSize: options.profile === 'edge' ? 1 : 16,
frontierMaxPages: options.profile === 'edge' ? 1 : 4,
taskAttemptPageSize: options.profile === 'edge' ? 1 : 16,
taskAttemptMaxPages: options.profile === 'edge' ? 1 : 4,
maxDispatches: options.profile === 'edge' ? 1 : 4,
},
);
schedulerLifecycle = new LocalSchedulerLifecycle(workflowScheduler, {
intervalMs: options.profile === 'edge' ? 5_000 : 1_000,
stopTimeoutMs: executionPolicy.stopTimeoutMs,
onDiagnostic: async (error) => {
if (error === undefined) return;
await bestEffortAudit(options, {
profile: options.profile,
state: 'scheduler_degraded',
});
},
});
runRecovery = await new LocalRunStartupRecoveryCoordinator(
storage.runs,
storage.startupRecovery,
receipts,
new LocalProcessPersistedExecutionInspector(),
{
receiptPublishGraceMs: options.profile === 'edge' ? 50 : 100,
journal: storage.completionReceipts,
quarantineRetentionMs: executionPolicy.retentionMs,
completionProcessor,
},
).recover();
if (!runRecovery.safe) {
throw new LocalApplicationStartupRecoveryRequiredError(
Math.max(
runRecovery.remaining + runRecovery.failed,
runRecovery.scanned - runRecovery.recovered,
),
runRecovery.truncated,
);
}
workflowTaskRecovery =
await new LocalWorkflowTaskStartupRecoveryCoordinator(
storage.runs,
workflowRuntime.executions,
workflowRuntime.executions,
completionProcessor,
new LocalProcessPersistedExecutionInspector(),
{
receiptPublishGraceMs: options.profile === 'edge' ? 50 : 100,
},
).recover();
if (!workflowTaskRecovery.safe) {
throw new LocalApplicationStartupRecoveryRequiredError(
workflowTaskRecovery.remaining + workflowTaskRecovery.failed,
workflowTaskRecovery.truncated,
);
}
await options.applicationAudit({
profile: options.profile,
state: 'runs_recovered',
runRecovery,
workflowTaskRecovery,
});
executionControl = await executionControlLifecycle.runOnce(true);
receiptCleanup = executionControl.cleanup;
if (!receiptCleanup) {
throw new Error('Local completion receipt cleanup did not run');
}
await options.applicationAudit({
profile: options.profile,
state: 'receipts_reconciled',
runRecovery,
workflowTaskRecovery,
receiptCleanup,
executionControl,
});
await schedulerLifecycle.runOnce();
await options.applicationAudit({
profile: options.profile,
state: 'recovered',
runRecovery,
workflowTaskRecovery,
receiptCleanup,
executionControl,
});
executionControlLifecycle.start();
schedulerLifecycle.start();
await options.applicationAudit({
profile: options.profile,
state: 'lifecycles_started',
runRecovery,
workflowTaskRecovery,
receiptCleanup,
executionControl,
});
if (options.productSurface) {
const [stepRuns, runCancellation, taskStart] = await Promise.all([
storage.stepRunReader(),
storage.runCancellationRepository(),
storage.taskStartRepository(),
]);
productSurfaceLifecycle = await options.productSurface.start(
Object.freeze({
profile: options.profile,
runs: storage.runs,
stepRuns,
runCancellation,
taskStart,
taskDefinitions: storage.taskDefinitions,
apiCredentials: storage.apiCredentials,
ownerPepper: storage.ownerPepper,
projectPolicy: storage.projectPolicy,
securityAudit: storage.securityAudit,
}),
);
if (
!productSurfaceLifecycle ||
typeof productSurfaceLifecycle.stopAndDrain !== 'function'
) {
throw new TypeError(
'Local application product surface lifecycle is invalid',
);
}
}
await options.applicationAudit({
profile: options.profile,
state: 'active',
runRecovery,
workflowTaskRecovery,
receiptCleanup,
executionControl,
});
let stopPromise: Promise<LocalApplicationStopResult> | undefined;
const activeStorage = storage;
const activeSchedulerLifecycle = schedulerLifecycle;
const activeExecutionControlLifecycle = executionControlLifecycle;
const activeRunRecovery = runRecovery;
const activeWorkflowTaskRecovery = workflowTaskRecovery;
const activeReceiptCleanup = receiptCleanup;
const activeExecutionControl = executionControl;
const activePluginPackageStartup = pluginPackageStartup;
const activeProductSurfaceLifecycle = productSurfaceLifecycle;
return Object.freeze({
status: 'active' as const,
profile: options.profile,
evidence: activeStorage.evidence,
runs: activeStorage.runs,
runRecovery: activeRunRecovery,
workflowTaskRecovery: activeWorkflowTaskRecovery,
receiptCleanup: activeReceiptCleanup,
executionControl: activeExecutionControl,
pluginPackageRecovery: activePluginPackageStartup.pluginPackageRecovery,
pluginPackageTaskPublicationRecovery:
activePluginPackageStartup.pluginPackageTaskPublicationRecovery,
pluginPackageAutomationPublicationRecovery:
activePluginPackageStartup.pluginPackageAutomationPublicationRecovery,
pluginPackageToolSnapshotRecovery:
activePluginPackageStartup.pluginPackageToolSnapshotRecovery,
stop() {
if (stopPromise) return stopPromise;
stopPromise = (async () => {
const errors: unknown[] = [];
let timedOut = false;
await bestEffortAudit(options, {
profile: options.profile,
state: 'draining',
runRecovery: activeRunRecovery,
workflowTaskRecovery: activeWorkflowTaskRecovery,
receiptCleanup: activeReceiptCleanup,
executionControl: activeExecutionControl,
});
if (activeProductSurfaceLifecycle) {
try {
const surfaceStop =
await activeProductSurfaceLifecycle.stopAndDrain();
timedOut = surfaceStop === 'timed_out' || timedOut;
} catch (error) {
errors.push(error);
}
}
try {
const schedulerStop = await activeSchedulerLifecycle.stopAndDrain();
timedOut = schedulerStop.status === 'timed_out' || timedOut;
} catch (error) {
errors.push(error);
}
try {
const executionStop =
await activeExecutionControlLifecycle.stopAndDrain();
timedOut = executionStop.status === 'timed_out' || timedOut;
await bestEffortAudit(options, {
profile: options.profile,
state: 'draining',
runRecovery: activeRunRecovery,
workflowTaskRecovery: activeWorkflowTaskRecovery,
receiptCleanup: executionStop.cleanup ?? activeReceiptCleanup,
executionControl: activeExecutionControl,
...(executionStop.drain === undefined
? {}
: { executionDrain: executionStop.drain }),
});
} catch (error) {
errors.push(error);
}
try {
await activeStorage.stop();
} catch (error) {
errors.push(error);
}
await bestEffortAudit(options, {
profile: options.profile,
state: 'stopped',
runRecovery: activeRunRecovery,
workflowTaskRecovery: activeWorkflowTaskRecovery,
receiptCleanup: activeReceiptCleanup,
executionControl: activeExecutionControl,
});
if (errors.length > 0) {
throw oneOrAggregate(errors, 'Local application stop failed');
}
return timedOut ? 'timed_out' : 'stopped';
})();
return stopPromise;
},
});
} catch (error) {
const cleanupErrors: unknown[] = [];
if (productSurfaceLifecycle) {
try {
await productSurfaceLifecycle.stopAndDrain();
} catch (cleanupError) {
cleanupErrors.push(cleanupError);
}
}
if (schedulerLifecycle) {
try {
await schedulerLifecycle.stopAndDrain();
} catch (cleanupError) {
cleanupErrors.push(cleanupError);
}
}
if (executionControlLifecycle) {
try {
await executionControlLifecycle.stopAndDrain();
} catch (cleanupError) {
cleanupErrors.push(cleanupError);
}
}
if (storage) {
try {
await storage.stop();
} catch (cleanupError) {
cleanupErrors.push(cleanupError);
}
}
await bestEffortAudit(options, {
profile: options.profile,
state: 'failed',
...(runRecovery ? { runRecovery } : {}),
...(workflowTaskRecovery ? { workflowTaskRecovery } : {}),
...(receiptCleanup ? { receiptCleanup } : {}),
...(executionControl ? { executionControl } : {}),
});
if (cleanupErrors.length > 0) {
throw new AggregateError(
[error, ...cleanupErrors],
'Local application activation failed and cleanup was incomplete',
);
}
throw error;
}
}
@@ -0,0 +1,727 @@
import { performance } from 'node:perf_hooks';
import type {
ActiveModelGatewayCapability,
ModelGatewayProfileAudit,
ModelGatewayProviderAuthority,
} from '@qinglong/ai/profile';
import type { LocalModelInvocationFeatureTransition } from '@qinglong/ai/local-feature-activation';
import type { PluginPackagePromptExecutor } from '@qinglong/ai/plugin-package-prompt-executor';
import type {
PluginPackagePromptOutputArtifactKeyProvider,
PluginPackagePromptOutputArtifactReadAuthorizer,
} from '@qinglong/ai/plugin-package-prompt-output-artifact';
import type { PluginPackagePromptOutputCompletionCapability } from '@qinglong/ai/plugin-package-prompt-output-completion';
import type {
PluginPackagePromptOutputArtifactRetentionStateReader,
PluginPackagePromptOutputReadService,
} from '@qinglong/ai/plugin-package-prompt-output-read';
import type { PluginPackagePromptExecutionOutputReadService } from '@qinglong/ai/plugin-package-prompt-execution-output-read';
import { bootstrapLocalApplication } from './activation';
import type {
LocalApplicationBootstrapOptions,
LocalApplicationBootstrapResult,
LocalApplicationProfile,
LocalApplicationStopResult,
} from './contract';
const MIN_DRAIN_TIMEOUT_MS = 100;
const MAX_DRAIN_TIMEOUT_MS = 60_000;
const MIN_DRAIN_POLL_MS = 10;
const MAX_DRAIN_POLL_MS = 1_000;
export const LOCAL_AI_FEATURE_APPLICATION_STATES = [
'application_disabled',
'deployment_excluded',
'schema_absent',
'feature_inactive',
'feature_active',
'storage_ready',
'recovery_ready',
'active',
'draining',
'drain_timed_out',
'stopped',
'failed',
] as const;
export type LocalAiFeatureApplicationState =
(typeof LOCAL_AI_FEATURE_APPLICATION_STATES)[number];
export interface LocalAiFeatureApplicationAudit {
readonly profile: LocalApplicationProfile;
readonly state: LocalAiFeatureApplicationState;
readonly generation?: number;
readonly recovered?: number;
readonly alreadyCompleted?: number;
}
export type LocalAiFeatureDeploymentOptions =
| Readonly<{
deployment: 'excluded';
audit: (
record: Readonly<LocalAiFeatureApplicationAudit>,
) => void | Promise<void>;
}>
| Readonly<{
deployment: 'installed';
loadProviders: () => Promise<ModelGatewayProviderAuthority>;
audit: (
record: Readonly<LocalAiFeatureApplicationAudit>,
) => void | Promise<void>;
maxConcurrent?: number;
recoveryLimit?: number;
drainTimeoutMs?: number;
drainPollMs?: number;
now?: () => number;
promptOutputKeys?: PluginPackagePromptOutputArtifactKeyProvider;
promptOutputRead?: Readonly<{
authorizer: PluginPackagePromptOutputArtifactReadAuthorizer;
retention: PluginPackagePromptOutputArtifactRetentionStateReader;
}>;
}>;
export interface BootstrapLocalAiFeatureApplicationOptions {
readonly application: LocalApplicationBootstrapOptions;
readonly ai: LocalAiFeatureDeploymentOptions;
}
type ActiveLocalApplication = Extract<
LocalApplicationBootstrapResult,
{ status: 'active' }
>;
export type LocalAiFeatureStartupResult =
| Readonly<{
status: 'deployment_excluded' | 'schema_absent';
}>
| Readonly<{
status: 'inactive';
generation: number;
}>
| Readonly<{
status: 'active';
generation: number;
capability: ActiveModelGatewayCapability;
prompts: PluginPackagePromptExecutor;
promptOutputs?: PluginPackagePromptOutputReadService;
promptExecutionOutputs?: PluginPackagePromptExecutionOutputReadService;
}>;
export type BootstrapLocalAiFeatureApplicationResult =
| Readonly<{
status: 'disabled';
profile: LocalApplicationProfile;
ai: Readonly<{ status: 'application_disabled' }>;
stop(): Promise<'stopped'>;
}>
| Readonly<{
status: 'active';
profile: LocalApplicationProfile;
application: Readonly<Omit<ActiveLocalApplication, 'stop'>>;
ai: LocalAiFeatureStartupResult;
stop(): Promise<LocalApplicationStopResult>;
}>;
export class LocalAiFeatureApplicationUnavailableError extends Error {
readonly code = 'LOCAL_AI_FEATURE_APPLICATION_UNAVAILABLE';
constructor(options?: ErrorOptions) {
super('The local AI feature application is unavailable', options);
this.name = 'LocalAiFeatureApplicationUnavailableError';
}
}
function exactObject(
value: unknown,
expectedKeys: readonly string[],
label: string,
): asserts value is Record<string, unknown> {
if (
!value ||
typeof value !== 'object' ||
Array.isArray(value) ||
Object.keys(value).sort().join('\0') !== [...expectedKeys].sort().join('\0')
) {
throw new TypeError(`${label} shape is invalid`);
}
}
function boundedInteger(
value: unknown,
fallback: number,
minimum: number,
maximum: number,
label: string,
): number {
if (value === undefined) return fallback;
if (
!Number.isSafeInteger(value) ||
(value as number) < minimum ||
(value as number) > maximum
) {
throw new TypeError(`${label} is invalid`);
}
return value as number;
}
function assertOptions(value: BootstrapLocalAiFeatureApplicationOptions): void {
exactObject(value, ['ai', 'application'], 'Local AI feature application');
const ai = value.ai;
if (!ai || typeof ai !== 'object' || Array.isArray(ai)) {
throw new TypeError('Local AI feature deployment is invalid');
}
if (ai.deployment === 'excluded') {
exactObject(ai, ['audit', 'deployment'], 'Excluded local AI feature');
} else if (ai.deployment === 'installed') {
const optionalKeys = [
'drainPollMs',
'drainTimeoutMs',
'maxConcurrent',
'now',
'promptOutputKeys',
'promptOutputRead',
'recoveryLimit',
].filter((key) => Object.hasOwn(ai, key));
exactObject(
ai,
['audit', 'deployment', 'loadProviders', ...optionalKeys],
'Installed local AI feature',
);
if (typeof ai.loadProviders !== 'function') {
throw new TypeError('Local AI provider loader is invalid');
}
boundedInteger(
ai.drainTimeoutMs,
5_000,
MIN_DRAIN_TIMEOUT_MS,
MAX_DRAIN_TIMEOUT_MS,
'Local AI drain timeout',
);
boundedInteger(
ai.drainPollMs,
25,
MIN_DRAIN_POLL_MS,
MAX_DRAIN_POLL_MS,
'Local AI drain poll interval',
);
if (
ai.maxConcurrent !== undefined &&
(!Number.isSafeInteger(ai.maxConcurrent) ||
ai.maxConcurrent < 1 ||
ai.maxConcurrent > 64)
) {
throw new TypeError('Local AI concurrency is invalid');
}
if (
ai.recoveryLimit !== undefined &&
(!Number.isSafeInteger(ai.recoveryLimit) ||
ai.recoveryLimit < 1 ||
ai.recoveryLimit > 128)
) {
throw new TypeError('Local AI recovery limit is invalid');
}
if (ai.now !== undefined && typeof ai.now !== 'function') {
throw new TypeError('Local AI clock is invalid');
}
if (
ai.promptOutputKeys !== undefined &&
(!ai.promptOutputKeys ||
typeof ai.promptOutputKeys !== 'object' ||
typeof ai.promptOutputKeys.active !== 'function' ||
typeof ai.promptOutputKeys.resolve !== 'function')
) {
throw new TypeError('Local Prompt output key provider is invalid');
}
if (ai.promptOutputRead !== undefined) {
exactObject(
ai.promptOutputRead,
['authorizer', 'retention'],
'Local Prompt output read capability',
);
if (
ai.promptOutputKeys === undefined ||
!ai.promptOutputRead.authorizer ||
typeof ai.promptOutputRead.authorizer.authorize !== 'function' ||
!ai.promptOutputRead.retention ||
typeof ai.promptOutputRead.retention.inspect !== 'function'
) {
throw new TypeError('Local Prompt output read capability is invalid');
}
}
} else {
throw new TypeError('Local AI deployment state is invalid');
}
if (typeof ai.audit !== 'function') {
throw new TypeError('Local AI application audit sink is invalid');
}
}
async function bestEffortAudit(
audit: LocalAiFeatureDeploymentOptions['audit'],
record: Readonly<LocalAiFeatureApplicationAudit>,
): Promise<void> {
try {
await audit(record);
} catch {
// Diagnostics cannot replace activation or shutdown results.
}
}
interface RawFeatureHead {
readonly state: 'schema_absent' | 'inactive' | 'active';
readonly generation: number;
readonly transitionDigest: string | null;
}
function rawFeatureHead(client: {
prepare(sql: string): {
all(...values: unknown[]): Record<string, unknown>[];
get(...values: unknown[]): Record<string, unknown> | undefined;
};
}): Readonly<RawFeatureHead> {
const schema = client
.prepare(
`SELECT name
FROM sqlite_schema
WHERE type = 'table'
AND name IN (
'ModelInvocationFeatureHead',
'ModelInvocationFeatureTransitions'
)
ORDER BY name`,
)
.all();
if (schema.length === 0) {
return Object.freeze({
state: 'schema_absent',
generation: 0,
transitionDigest: null,
});
}
if (
schema.length !== 2 ||
schema[0]?.name !== 'ModelInvocationFeatureHead' ||
schema[1]?.name !== 'ModelInvocationFeatureTransitions'
) {
throw new LocalAiFeatureApplicationUnavailableError();
}
const rows = client
.prepare(
`SELECT generation, state, transition_digest AS "transitionDigest"
FROM "ModelInvocationFeatureHead"
WHERE feature_id = 'model-invocation'
LIMIT 2`,
)
.all();
if (rows.length === 0) {
return Object.freeze({
state: 'schema_absent',
generation: 0,
transitionDigest: null,
});
}
const row = rows[0];
if (
rows.length !== 1 ||
!row ||
!Number.isSafeInteger(row.generation) ||
(row.generation as number) < 1 ||
(row.state !== 'active' && row.state !== 'inactive') ||
typeof row.transitionDigest !== 'string' ||
!/^[0-9a-f]{64}$/.test(row.transitionDigest)
) {
throw new LocalAiFeatureApplicationUnavailableError();
}
return Object.freeze({
state: row.state,
generation: row.generation as number,
transitionDigest: row.transitionDigest,
});
}
function sameActivation(
expected: Readonly<LocalModelInvocationFeatureTransition>,
observed: Readonly<LocalModelInvocationFeatureTransition> | null,
): boolean {
return (
observed?.state === 'active' &&
observed.generation === expected.generation &&
observed.transitionDigest === expected.transitionDigest
);
}
function applicationView(
application: ActiveLocalApplication,
): Readonly<Omit<ActiveLocalApplication, 'stop'>> {
const { stop: _stop, ...view } = application;
void _stop;
return Object.freeze(view);
}
function oneOrAggregate(errors: unknown[], message: string): unknown {
return errors.length === 1 ? errors[0] : new AggregateError(errors, message);
}
function delay(milliseconds: number): Promise<void> {
return new Promise((resolve) => {
setTimeout(resolve, milliseconds);
});
}
/**
* Product composition for an optional local AI deployment. The base
* application remains AI-free. Only an installed deployment with a durable
* active head imports the AI runtime, recovers it, and reaches providers.
*/
export async function bootstrapLocalAiFeatureApplication(
options: BootstrapLocalAiFeatureApplicationOptions,
): Promise<BootstrapLocalAiFeatureApplicationResult> {
assertOptions(options);
const application = await bootstrapLocalApplication(options.application);
if (application.status === 'disabled') {
await options.ai.audit({
profile: application.profile,
state: 'application_disabled',
});
return Object.freeze({
status: 'disabled',
profile: application.profile,
ai: Object.freeze({ status: 'application_disabled' as const }),
stop: application.stop,
});
}
if (options.application.enabled !== true) {
await application.stop();
throw new LocalAiFeatureApplicationUnavailableError();
}
const applicationOptions = options.application;
if (options.ai.deployment === 'excluded') {
await options.ai.audit({
profile: application.profile,
state: 'deployment_excluded',
});
return Object.freeze({
status: 'active',
profile: application.profile,
application: applicationView(application),
ai: Object.freeze({ status: 'deployment_excluded' as const }),
stop: application.stop,
});
}
const aiOptions = options.ai;
let featureDatabase:
| Awaited<
ReturnType<
typeof import('@qinglong/local-sqlite/optional-feature-runtime')['openLocalSqliteOptionalFeatureRuntimeDatabase']
>
>
| undefined;
let featureOwnedByProfile = false;
try {
const { openLocalSqliteOptionalFeatureRuntimeDatabase } = await import(
'@qinglong/local-sqlite/optional-feature-runtime'
);
featureDatabase = await openLocalSqliteOptionalFeatureRuntimeDatabase({
databasePath:
applicationOptions.storageMode === 'fresh'
? applicationOptions.databasePath
: applicationOptions.targetPath,
profile: applicationOptions.profile,
...(applicationOptions.busyTimeoutMs === undefined
? {}
: { busyTimeoutMs: applicationOptions.busyTimeoutMs }),
});
const head = rawFeatureHead(featureDatabase.authority.client);
if (head.state !== 'active') {
await featureDatabase.close();
featureDatabase = undefined;
await aiOptions.audit({
profile: application.profile,
state:
head.state === 'schema_absent' ? 'schema_absent' : 'feature_inactive',
...(head.generation === 0 ? {} : { generation: head.generation }),
});
const ai: LocalAiFeatureStartupResult =
head.state === 'schema_absent'
? Object.freeze({ status: 'schema_absent' as const })
: Object.freeze({
status: 'inactive' as const,
generation: head.generation,
});
return Object.freeze({
status: 'active',
profile: application.profile,
application: applicationView(application),
ai,
stop: application.stop,
});
}
const [
{ LocalModelInvocationFeatureActivationRepository },
{ LocalModelInvocationRepository },
{ LocalModelPriceCatalogRepository },
{ bootstrapModelGatewayProfile },
{ LocalPluginPackagePromptAdmissionRepository },
{ PluginPackagePromptExecutor },
{ PluginPackagePromptOutputCompletionCoordinator },
] = await Promise.all([
import('@qinglong/ai/local-feature-activation'),
import('@qinglong/ai/local-model-invocation-storage'),
import('@qinglong/ai/local-price-catalog-storage'),
import('@qinglong/ai/profile'),
import('@qinglong/ai/local-plugin-package-prompt-admission-storage'),
import('@qinglong/ai/plugin-package-prompt-executor'),
import('@qinglong/ai/plugin-package-prompt-output-completion'),
]);
const activationRepository =
new LocalModelInvocationFeatureActivationRepository(
featureDatabase.authority.client,
);
const activation = activationRepository.findCurrent();
if (
!activation ||
activation.state !== 'active' ||
activation.generation !== head.generation ||
activation.transitionDigest !== head.transitionDigest
) {
throw new LocalAiFeatureApplicationUnavailableError();
}
await aiOptions.audit({
profile: application.profile,
state: 'feature_active',
generation: activation.generation,
});
const repository = new LocalModelInvocationRepository(
featureDatabase.authority,
);
const pricing = new LocalModelPriceCatalogRepository(
featureDatabase.authority,
);
const activeDatabase = featureDatabase;
let durableOutput:
| PluginPackagePromptOutputCompletionCapability
| undefined;
const gateway = await bootstrapModelGatewayProfile({
enabled: true,
profile: application.profile,
loadStorage: async () => {
featureOwnedByProfile = true;
return Object.freeze({
repository,
pricing,
close: () => activeDatabase.close(),
});
},
loadProviders: aiOptions.loadProviders,
...(aiOptions.promptOutputKeys === undefined
? {}
: {
createSuccessfulCompletion: (coordinator) => {
durableOutput =
new PluginPackagePromptOutputCompletionCoordinator({
coordinator,
keys: aiOptions.promptOutputKeys!,
...(aiOptions.now === undefined ? {} : { now: aiOptions.now }),
});
return durableOutput;
},
}),
confirmActive: async () => {
await activeDatabase.authority.enqueue(
async () => {
if (
!sameActivation(activation, activationRepository.findCurrent())
) {
throw new LocalAiFeatureApplicationUnavailableError();
}
},
() => new LocalAiFeatureApplicationUnavailableError(),
);
},
audit: async (record: Readonly<ModelGatewayProfileAudit>) => {
if (record.state === 'disabled') {
throw new LocalAiFeatureApplicationUnavailableError();
}
await aiOptions.audit({
profile: application.profile,
state: record.state,
generation: activation.generation,
...(record.recovered === undefined
? {}
: { recovered: record.recovered }),
...(record.alreadyCompleted === undefined
? {}
: { alreadyCompleted: record.alreadyCompleted }),
});
},
...(aiOptions.maxConcurrent === undefined
? {}
: { maxConcurrent: aiOptions.maxConcurrent }),
...(aiOptions.recoveryLimit === undefined
? {}
: { recoveryLimit: aiOptions.recoveryLimit }),
...(aiOptions.now === undefined ? {} : { now: aiOptions.now }),
});
if (gateway.status !== 'active') {
throw new LocalAiFeatureApplicationUnavailableError();
}
const capability = gateway.capability;
const prompts = new PluginPackagePromptExecutor({
admissions: new LocalPluginPackagePromptAdmissionRepository(
activeDatabase.authority,
),
invocations: repository,
gateway: capability,
...(durableOutput === undefined ? {} : { durableOutput }),
});
let promptOutputs: PluginPackagePromptOutputReadService | undefined;
let promptExecutionOutputs:
| PluginPackagePromptExecutionOutputReadService
| undefined;
if (aiOptions.promptOutputRead !== undefined) {
const [
{ LocalPluginPackagePromptOutputArtifactRepository },
{ PluginPackagePromptOutputReadService },
{ PluginPackagePromptExecutionOutputReadService },
{ LocalPluginPackagePromptExecutionOutputReferenceRepository },
] = await Promise.all([
import(
'@qinglong/ai/local-plugin-package-prompt-output-artifact-storage'
),
import('@qinglong/ai/plugin-package-prompt-output-read'),
import('@qinglong/ai/plugin-package-prompt-execution-output-read'),
import(
'@qinglong/ai/local-plugin-package-prompt-execution-output-reference-storage'
),
]);
promptOutputs = new PluginPackagePromptOutputReadService({
artifacts: new LocalPluginPackagePromptOutputArtifactRepository(
activeDatabase.authority,
),
authorizer: aiOptions.promptOutputRead.authorizer,
retention: aiOptions.promptOutputRead.retention,
keys: aiOptions.promptOutputKeys!,
...(aiOptions.now === undefined ? {} : { now: aiOptions.now }),
});
promptExecutionOutputs =
new PluginPackagePromptExecutionOutputReadService({
references:
new LocalPluginPackagePromptExecutionOutputReferenceRepository(
activeDatabase.authority,
),
outputs: promptOutputs,
});
}
const drainTimeoutMs = boundedInteger(
aiOptions.drainTimeoutMs,
5_000,
MIN_DRAIN_TIMEOUT_MS,
MAX_DRAIN_TIMEOUT_MS,
'Local AI drain timeout',
);
const drainPollMs = boundedInteger(
aiOptions.drainPollMs,
25,
MIN_DRAIN_POLL_MS,
MAX_DRAIN_POLL_MS,
'Local AI drain poll interval',
);
let stopPromise: Promise<LocalApplicationStopResult> | undefined;
return Object.freeze({
status: 'active',
profile: application.profile,
application: applicationView(application),
ai: Object.freeze({
status: 'active' as const,
generation: activation.generation,
capability,
prompts,
...(promptOutputs === undefined ? {} : { promptOutputs }),
...(promptExecutionOutputs === undefined
? {}
: { promptExecutionOutputs }),
}),
stop() {
if (stopPromise) return stopPromise;
stopPromise = (async () => {
const errors: unknown[] = [];
let timedOut = false;
await bestEffortAudit(aiOptions.audit, {
profile: application.profile,
state: 'draining',
generation: activation.generation,
});
try {
const deadline = performance.now() + drainTimeoutMs;
let result = await capability.stop();
while (result === 'draining' && performance.now() < deadline) {
await delay(
Math.min(
drainPollMs,
Math.max(1, deadline - performance.now()),
),
);
result = await capability.stop();
}
timedOut = result === 'draining';
} catch (error) {
errors.push(error);
}
try {
timedOut = (await application.stop()) === 'timed_out' || timedOut;
} catch (error) {
errors.push(error);
}
await bestEffortAudit(aiOptions.audit, {
profile: application.profile,
state: timedOut ? 'drain_timed_out' : 'stopped',
generation: activation.generation,
});
if (errors.length > 0) {
throw oneOrAggregate(
errors,
'Local AI feature application stop failed',
);
}
return timedOut ? 'timed_out' : 'stopped';
})();
return stopPromise;
},
});
} catch (error) {
const cleanupErrors: unknown[] = [];
if (featureDatabase && !featureOwnedByProfile) {
try {
await featureDatabase.close();
} catch (cleanupError) {
cleanupErrors.push(cleanupError);
}
}
try {
await application.stop();
} catch (cleanupError) {
cleanupErrors.push(cleanupError);
}
await bestEffortAudit(aiOptions.audit, {
profile: application.profile,
state: 'failed',
});
const cause =
error instanceof LocalAiFeatureApplicationUnavailableError
? error
: new LocalAiFeatureApplicationUnavailableError({
cause: error instanceof Error ? error : undefined,
});
if (cleanupErrors.length > 0) {
throw new AggregateError(
[cause, ...cleanupErrors],
'Local AI feature activation failed and cleanup was incomplete',
);
}
throw cause;
}
}
@@ -0,0 +1,177 @@
import type {
LocalAdoptedProfileBootstrapOptions,
LocalAdoptedProfileBootstrapResult,
} from '@qinglong/local-admin/adopted-profile';
import type {
LocalProfileStorageAudit,
LocalProfileStorageBootstrapResult,
} from '@qinglong/local-sqlite/profile';
import type { LocalRunStartupRecoverySummary } from '@qinglong/local-execution/recovery';
import type { LocalWorkflowTaskStartupRecoverySummary } from '@qinglong/local-execution/recovery';
import type { LocalCompletionReceiptCleanupSummary } from '@qinglong/local-process';
import type {
LocalExecutionControlCycleSummary,
LocalExecutionDrainSummary,
} from '@qinglong/local-execution/control';
import type { PluginPackageStageProvider } from '@qinglong/runtime-core/plugin-package-installation';
import type { PluginPackageRecoveryCycleResult } from '@qinglong/runtime-core/plugin-package-recovery';
import type { PluginPackageAutomationPublicationRecoveryCycleResult } from '@qinglong/runtime-core/plugin-package-automation-publication';
import type { PluginPackageTaskPublicationRecoveryCycleResult } from '@qinglong/runtime-core/plugin-package-task-publication';
import type { ProjectToolDefinitionSnapshotRecoveryCycleResult } from '@qinglong/runtime-core/project-tool-definition-snapshot';
export type LocalApplicationProfile = 'edge' | 'standalone';
export type LocalApplicationActivationState =
| 'disabled'
| 'storage_ready'
| 'plugin_packages_recovered'
| 'plugin_package_tasks_published'
| 'plugin_package_automations_published'
| 'plugin_package_tools_snapshotted'
| 'secrets_ready'
| 'runs_recovered'
| 'receipts_reconciled'
| 'execution_control_degraded'
| 'scheduler_degraded'
| 'recovered'
| 'lifecycles_started'
| 'active'
| 'draining'
| 'failed'
| 'stopped';
export type LocalApplicationStopResult = 'stopped' | 'timed_out';
export interface LocalApplicationProductSurfaceAuthority {
readonly profile: LocalApplicationProfile;
readonly runs: Pick<
ReadyFreshStorage['runs'],
'findRunById' | 'listEvents' | 'listRunsByProject'
>;
readonly stepRuns: Awaited<ReturnType<ReadyFreshStorage['stepRunReader']>>;
readonly runCancellation: Awaited<
ReturnType<ReadyFreshStorage['runCancellationRepository']>
>;
readonly taskStart: Awaited<
ReturnType<ReadyFreshStorage['taskStartRepository']>
>;
readonly taskDefinitions: Pick<
ReadyFreshStorage['taskDefinitions'],
'findCurrentTaskDefinition' | 'listTaskDefinitions'
>;
readonly apiCredentials: ReadyFreshStorage['apiCredentials'];
readonly ownerPepper: ReadyFreshStorage['ownerPepper'];
readonly projectPolicy: ReadyFreshStorage['projectPolicy'];
readonly securityAudit: ReadyFreshStorage['securityAudit'];
}
export interface LocalApplicationProductSurfaceLifecycle {
stopAndDrain(): Promise<LocalApplicationStopResult>;
}
export interface LocalApplicationProductSurface {
start(
authority: Readonly<LocalApplicationProductSurfaceAuthority>,
): Promise<Readonly<LocalApplicationProductSurfaceLifecycle>>;
}
export interface LocalApplicationPluginPackageRecoveryOptions {
readonly stageProvider: PluginPackageStageProvider;
readonly stagingRoot: string;
readonly activationRoot: string;
readonly now: () => number;
readonly pageSize?: number;
readonly maxPages?: number;
readonly taskPublicationPageSize?: number;
readonly taskPublicationMaxPages?: number;
}
type ReadyAdoptedStorage = Extract<
LocalAdoptedProfileBootstrapResult,
{ status: 'adopted_storage_ready' }
>;
type ReadyFreshStorage = Extract<
LocalProfileStorageBootstrapResult,
{ status: 'storage_ready' }
>;
export interface LocalApplicationActivationAudit {
readonly profile: LocalApplicationProfile;
readonly state: LocalApplicationActivationState;
readonly runRecovery?: LocalRunStartupRecoverySummary;
readonly workflowTaskRecovery?: LocalWorkflowTaskStartupRecoverySummary;
readonly receiptCleanup?: LocalCompletionReceiptCleanupSummary;
readonly executionControl?: LocalExecutionControlCycleSummary;
readonly executionDrain?: LocalExecutionDrainSummary;
readonly pluginPackageRecovery?: PluginPackageRecoveryCycleResult;
readonly pluginPackageTaskPublicationRecovery?: PluginPackageTaskPublicationRecoveryCycleResult;
readonly pluginPackageAutomationPublicationRecovery?: PluginPackageAutomationPublicationRecoveryCycleResult;
readonly pluginPackageToolSnapshotRecovery?: ProjectToolDefinitionSnapshotRecoveryCycleResult;
}
export interface LocalApplicationDisabledBootstrapOptions {
readonly enabled?: false;
readonly profile: LocalApplicationProfile;
readonly applicationAudit: (
record: LocalApplicationActivationAudit,
) => void | Promise<void>;
}
interface LocalApplicationEnabledBootstrapCommon {
readonly enabled: true;
readonly profile: LocalApplicationProfile;
readonly receiptRoot: string;
readonly artifactRoot: string;
readonly secretKeyringPath: string;
readonly pluginPackages: LocalApplicationPluginPackageRecoveryOptions;
readonly productSurface?: LocalApplicationProductSurface;
readonly applicationAudit: (
record: LocalApplicationActivationAudit,
) => void | Promise<void>;
}
export interface LocalApplicationAdoptedBootstrapOptions
extends LocalApplicationEnabledBootstrapCommon,
Omit<LocalAdoptedProfileBootstrapOptions, 'enabled' | 'profile'> {
readonly storageMode?: 'adopted';
}
export interface LocalApplicationFreshBootstrapOptions
extends LocalApplicationEnabledBootstrapCommon {
readonly storageMode: 'fresh';
readonly databasePath: string;
readonly busyTimeoutMs?: number;
readonly audit: (record: LocalProfileStorageAudit) => void | Promise<void>;
}
export type LocalApplicationEnabledBootstrapOptions =
| LocalApplicationAdoptedBootstrapOptions
| LocalApplicationFreshBootstrapOptions;
export type LocalApplicationBootstrapOptions =
| LocalApplicationDisabledBootstrapOptions
| LocalApplicationEnabledBootstrapOptions;
export type LocalApplicationBootstrapResult =
| {
readonly status: 'disabled';
readonly profile: LocalApplicationProfile;
stop(): Promise<'stopped'>;
}
| {
readonly status: 'active';
readonly profile: LocalApplicationProfile;
readonly evidence:
| ReadyAdoptedStorage['evidence']
| ReadyFreshStorage['evidence'];
readonly runs: ReadyAdoptedStorage['runs'] | ReadyFreshStorage['runs'];
readonly runRecovery: LocalRunStartupRecoverySummary;
readonly workflowTaskRecovery: LocalWorkflowTaskStartupRecoverySummary;
readonly receiptCleanup: LocalCompletionReceiptCleanupSummary;
readonly executionControl: LocalExecutionControlCycleSummary;
readonly pluginPackageRecovery: PluginPackageRecoveryCycleResult;
readonly pluginPackageTaskPublicationRecovery: PluginPackageTaskPublicationRecoveryCycleResult;
readonly pluginPackageAutomationPublicationRecovery: PluginPackageAutomationPublicationRecoveryCycleResult;
readonly pluginPackageToolSnapshotRecovery: ProjectToolDefinitionSnapshotRecoveryCycleResult;
stop(): Promise<LocalApplicationStopResult>;
};
@@ -0,0 +1,192 @@
import { LocalPluginPackageActivationPublisher } from '@qinglong/local-admin/package-activation';
import { LocalPluginPackageResourceByteSource } from '@qinglong/local-admin/package-resource-materialization';
import {
PluginPackageRecoveryCoordinator,
type PluginPackageRecoveryCycleResult,
} from '@qinglong/runtime-core/plugin-package-recovery';
import {
PluginPackageAutomationPublicationCoordinator,
PluginPackageAutomationPublicationRecoveryCoordinator,
type PluginPackageAutomationPublicationRecoveryCycleResult,
} from '@qinglong/runtime-core/plugin-package-automation-publication';
import {
MAX_PLUGIN_PACKAGE_TASK_PUBLICATION_RECOVERY_PAGES,
MAX_PLUGIN_PACKAGE_TASK_PUBLICATION_RECOVERY_PAGE_SIZE,
PluginPackageTaskPublicationCoordinator,
PluginPackageTaskPublicationRecoveryCoordinator,
type PluginPackageTaskPublicationRecoveryCycleResult,
} from '@qinglong/runtime-core/plugin-package-task-publication';
import {
MAX_PROJECT_TOOL_SNAPSHOT_RECOVERY_PAGES,
MAX_PROJECT_TOOL_SNAPSHOT_SOURCE_PAGE_SIZE,
ProjectToolDefinitionSnapshotPublicationCoordinator,
ProjectToolDefinitionSnapshotRecoveryCoordinator,
type ProjectToolDefinitionSnapshotRecoveryCycleResult,
} from '@qinglong/runtime-core/project-tool-definition-snapshot';
import { createBuiltInTaskSpecSemanticRegistry } from '@qinglong/runtime-core/task-spec-semantic';
import type { LocalApplicationEnabledBootstrapOptions } from './contract';
import type { LocalApplicationReadyStorage } from './storageActivation';
import {
LocalApplicationPluginPackageAutomationPublicationRequiredError,
LocalApplicationPluginPackageRecoveryRequiredError,
LocalApplicationPluginPackageTaskPublicationRequiredError,
LocalApplicationPluginPackageToolSnapshotRequiredError,
} from './startupErrors';
export interface LocalApplicationPluginPackageStartup {
readonly pluginPackageRecovery: Readonly<PluginPackageRecoveryCycleResult>;
readonly pluginPackageTaskPublicationRecovery: Readonly<PluginPackageTaskPublicationRecoveryCycleResult>;
readonly pluginPackageAutomationPublicationRecovery: Readonly<PluginPackageAutomationPublicationRecoveryCycleResult>;
readonly pluginPackageToolSnapshotRecovery: Readonly<ProjectToolDefinitionSnapshotRecoveryCycleResult>;
}
export async function recoverLocalApplicationPluginPackages(
options: LocalApplicationEnabledBootstrapOptions,
storage: LocalApplicationReadyStorage,
): Promise<LocalApplicationPluginPackageStartup> {
const pluginPackageInstalls = await storage.pluginPackageInstalls();
const pluginPackageActivation = new LocalPluginPackageActivationPublisher({
stagingRoot: options.pluginPackages.stagingRoot,
activationRoot: options.pluginPackages.activationRoot,
now: options.pluginPackages.now,
});
const pluginPackageRecovery = await new PluginPackageRecoveryCoordinator({
repository: pluginPackageInstalls,
stageProvider: options.pluginPackages.stageProvider,
publisher: pluginPackageActivation,
now: options.pluginPackages.now,
}).recover({
...(options.pluginPackages.pageSize === undefined
? {}
: { pageSize: options.pluginPackages.pageSize }),
...(options.pluginPackages.maxPages === undefined
? {}
: { maxPages: options.pluginPackages.maxPages }),
});
if (!pluginPackageRecovery.safeToAdmit) {
throw new LocalApplicationPluginPackageRecoveryRequiredError(
pluginPackageRecovery,
);
}
await options.applicationAudit({
profile: options.profile,
state: 'plugin_packages_recovered',
pluginPackageRecovery,
});
const taskSpecSemanticRegistry = createBuiltInTaskSpecSemanticRegistry();
const pluginPackageTaskReconciliations =
await storage.pluginPackageTaskReconciliations();
const pluginPackageMaterializedRevisions =
await storage.pluginPackageMaterializedRevisions();
const pluginPackageTaskPublicationRecovery =
await new PluginPackageTaskPublicationRecoveryCoordinator({
source: pluginPackageTaskReconciliations,
publisher: new PluginPackageTaskPublicationCoordinator({
generationSource: pluginPackageActivation,
lockSource: pluginPackageInstalls,
byteSource: new LocalPluginPackageResourceByteSource({
stagingRoot: options.pluginPackages.stagingRoot,
}),
materializedRepository: pluginPackageMaterializedRevisions,
reconciliationRepository: pluginPackageTaskReconciliations,
taskSpecSemanticRegistry,
}),
}).recover({
...(options.pluginPackages.taskPublicationPageSize === undefined
? {}
: { pageSize: options.pluginPackages.taskPublicationPageSize }),
...(options.pluginPackages.taskPublicationMaxPages === undefined
? {}
: { maxPages: options.pluginPackages.taskPublicationMaxPages }),
});
if (!pluginPackageTaskPublicationRecovery.safeToAdmit) {
throw new LocalApplicationPluginPackageTaskPublicationRequiredError(
pluginPackageTaskPublicationRecovery,
);
}
await options.applicationAudit({
profile: options.profile,
state: 'plugin_package_tasks_published',
pluginPackageRecovery,
pluginPackageTaskPublicationRecovery,
});
const pluginPackageAutomationPublications =
await storage.pluginPackageAutomationPublications();
const pluginPackageAutomationPublicationRecovery =
await new PluginPackageAutomationPublicationRecoveryCoordinator({
source: pluginPackageAutomationPublications,
publisher: new PluginPackageAutomationPublicationCoordinator({
generationSource: pluginPackageActivation,
materializedRepository: pluginPackageMaterializedRevisions,
repository: pluginPackageAutomationPublications,
taskSpecSemanticRegistry,
now: options.pluginPackages.now,
}),
}).recover({
...(options.pluginPackages.taskPublicationPageSize === undefined
? {}
: { pageSize: options.pluginPackages.taskPublicationPageSize }),
...(options.pluginPackages.taskPublicationMaxPages === undefined
? {}
: { maxPages: options.pluginPackages.taskPublicationMaxPages }),
});
if (!pluginPackageAutomationPublicationRecovery.safeToAdmit) {
throw new LocalApplicationPluginPackageAutomationPublicationRequiredError(
pluginPackageAutomationPublicationRecovery,
);
}
await options.applicationAudit({
profile: options.profile,
state: 'plugin_package_automations_published',
pluginPackageRecovery,
pluginPackageTaskPublicationRecovery,
pluginPackageAutomationPublicationRecovery,
});
const projectToolDefinitionSnapshots =
await storage.projectToolDefinitionSnapshots();
const pluginPackageToolSnapshotRecovery =
await new ProjectToolDefinitionSnapshotRecoveryCoordinator({
source: projectToolDefinitionSnapshots,
publisher: new ProjectToolDefinitionSnapshotPublicationCoordinator({
source: projectToolDefinitionSnapshots,
materializedRepository: pluginPackageMaterializedRevisions,
repository: projectToolDefinitionSnapshots,
taskSpecSemanticRegistry,
pageSize: Math.min(
options.pluginPackages.taskPublicationPageSize ??
(options.profile === 'edge' ? 4 : 16),
MAX_PROJECT_TOOL_SNAPSHOT_SOURCE_PAGE_SIZE,
),
}),
}).recover({
pageSize:
options.pluginPackages.taskPublicationPageSize ??
(options.profile === 'edge' ? 1 : 8),
maxPages:
options.pluginPackages.taskPublicationMaxPages ??
MAX_PROJECT_TOOL_SNAPSHOT_RECOVERY_PAGES,
});
if (!pluginPackageToolSnapshotRecovery.safeToAdmit) {
throw new LocalApplicationPluginPackageToolSnapshotRequiredError(
pluginPackageToolSnapshotRecovery,
);
}
await options.applicationAudit({
profile: options.profile,
state: 'plugin_package_tools_snapshotted',
pluginPackageRecovery,
pluginPackageTaskPublicationRecovery,
pluginPackageAutomationPublicationRecovery,
pluginPackageToolSnapshotRecovery,
});
return Object.freeze({
pluginPackageRecovery,
pluginPackageTaskPublicationRecovery,
pluginPackageAutomationPublicationRecovery,
pluginPackageToolSnapshotRecovery,
});
}
@@ -0,0 +1,53 @@
import type { PluginPackageRecoveryCycleResult } from '@qinglong/runtime-core/plugin-package-recovery';
import type { PluginPackageAutomationPublicationRecoveryCycleResult } from '@qinglong/runtime-core/plugin-package-automation-publication';
import type { PluginPackageTaskPublicationRecoveryCycleResult } from '@qinglong/runtime-core/plugin-package-task-publication';
import type { ProjectToolDefinitionSnapshotRecoveryCycleResult } from '@qinglong/runtime-core/project-tool-definition-snapshot';
export class LocalApplicationStartupRecoveryRequiredError extends Error {
constructor(
readonly observedCandidates: number,
readonly truncated: boolean,
) {
super('Local application has unresolved startup recovery candidates');
this.name = 'LocalApplicationStartupRecoveryRequiredError';
}
}
export class LocalApplicationPluginPackageRecoveryRequiredError extends Error {
constructor(readonly recovery: Readonly<PluginPackageRecoveryCycleResult>) {
super('Local application has unresolved Plugin Package recovery work');
this.name = 'LocalApplicationPluginPackageRecoveryRequiredError';
}
}
export class LocalApplicationPluginPackageTaskPublicationRequiredError extends Error {
constructor(
readonly recovery: Readonly<PluginPackageTaskPublicationRecoveryCycleResult>,
) {
super(
'Local application has unresolved Plugin Package Task publication work',
);
this.name = 'LocalApplicationPluginPackageTaskPublicationRequiredError';
}
}
export class LocalApplicationPluginPackageAutomationPublicationRequiredError extends Error {
constructor(
readonly recovery: Readonly<PluginPackageAutomationPublicationRecoveryCycleResult>,
) {
super(
'Local application has unresolved Plugin Package Workflow/Prompt publication work',
);
this.name =
'LocalApplicationPluginPackageAutomationPublicationRequiredError';
}
}
export class LocalApplicationPluginPackageToolSnapshotRequiredError extends Error {
constructor(
readonly recovery: Readonly<ProjectToolDefinitionSnapshotRecoveryCycleResult>,
) {
super('Local application has unresolved Plugin Package Tool snapshot work');
this.name = 'LocalApplicationPluginPackageToolSnapshotRequiredError';
}
}
@@ -0,0 +1,54 @@
import {
bootstrapLocalAdoptedProfileStorage,
type LocalAdoptedProfileBootstrapResult,
} from '@qinglong/local-admin/adopted-profile';
import {
bootstrapLocalProfileStorage,
type LocalProfileStorageBootstrapResult,
} from '@qinglong/local-sqlite/profile';
import type { LocalApplicationEnabledBootstrapOptions } from './contract';
export type LocalApplicationReadyStorage =
| Extract<
LocalAdoptedProfileBootstrapResult,
{ status: 'adopted_storage_ready' }
>
| Extract<LocalProfileStorageBootstrapResult, { status: 'storage_ready' }>;
export async function openLocalApplicationStorage(
options: LocalApplicationEnabledBootstrapOptions,
): Promise<LocalApplicationReadyStorage> {
const opened =
options.storageMode === 'fresh'
? await bootstrapLocalProfileStorage({
enabled: true,
profile: options.profile,
databasePath: options.databasePath,
...(options.busyTimeoutMs === undefined
? {}
: { busyTimeoutMs: options.busyTimeoutMs }),
audit: options.audit,
})
: await bootstrapLocalAdoptedProfileStorage({
enabled: true,
profile: options.profile,
sourcePath: options.sourcePath,
targetPath: options.targetPath,
recoveryPath: options.recoveryPath,
manifestPath: options.manifestPath,
activationPath: options.activationPath,
expectedActivationDigest: options.expectedActivationDigest,
...(options.busyTimeoutMs === undefined
? {}
: { busyTimeoutMs: options.busyTimeoutMs }),
audit: options.audit,
adoptionAudit: options.adoptionAudit,
});
if (
opened.status !== 'adopted_storage_ready' &&
opened.status !== 'storage_ready'
) {
throw new Error('Enabled local application storage did not become ready');
}
return opened;
}