feat(tess): add safe runtime observability (#726)
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful

This commit was merged in pull request #726.
This commit is contained in:
2026-07-13 01:29:18 +00:00
parent 7b9f40d3b7
commit 86a50138a9
12 changed files with 391 additions and 24 deletions

View File

@@ -1,5 +1,10 @@
import { ForbiddenException, Inject, Injectable, Logger, NotFoundException } from '@nestjs/common';
import { AgentRuntimeProviderRegistry } from '@mosaicstack/agent';
import {
createRuntimeAuditLogEntry,
type LogService,
type RuntimeAuditErrorCode,
} from '@mosaicstack/log';
import type {
AgentRuntimeProvider,
RuntimeAttachHandle,
@@ -14,6 +19,7 @@ import type {
RuntimeStreamEvent,
} from '@mosaicstack/types';
import type { ActorTenantScope } from '../auth/session-scope.js';
import { LOG_SERVICE } from '../log/log.tokens.js';
export const AGENT_RUNTIME_PROVIDER_REGISTRY = Symbol('AGENT_RUNTIME_PROVIDER_REGISTRY');
export const RUNTIME_PROVIDER_AUDIT_SINK = Symbol('RUNTIME_PROVIDER_AUDIT_SINK');
@@ -42,6 +48,8 @@ export interface RuntimeAuditEvent {
channelId: string;
correlationId: string;
resourceId?: string;
durationMs?: number;
errorCode?: RuntimeAuditErrorCode;
}
export interface RuntimeAuditSink {
@@ -87,8 +95,12 @@ export class DenyRuntimeApprovalVerifier implements RuntimeApprovalVerifier {
export class RuntimeProviderAuditService implements RuntimeAuditSink {
private readonly logger = new Logger(RuntimeProviderAuditService.name);
constructor(@Inject(LOG_SERVICE) private readonly logService: LogService) {}
async record(event: RuntimeAuditEvent): Promise<void> {
this.logger.log(JSON.stringify(event));
const entry = createRuntimeAuditLogEntry(event);
await this.logService.logs.ingest(entry);
this.logger.log(JSON.stringify({ event: entry.content, metadata: entry.metadata }));
}
}
@@ -267,6 +279,7 @@ export class RuntimeProviderService {
invoke: (provider: AgentRuntimeProvider, scope: RuntimeScope) => Promise<T>,
): Promise<T> {
const scope = this.deriveScope(context);
const startedAt = Date.now();
await this.record(providerId, operation, 'requested', scope, resourceId);
let invocationStarted = false;
try {
@@ -276,13 +289,22 @@ export class RuntimeProviderService {
}
invocationStarted = true;
const result = await invoke(provider, scope);
await this.recordCompletion(providerId, operation, scope, resourceId);
await this.recordCompletion(providerId, operation, scope, resourceId, Date.now() - startedAt);
return result;
} catch (error: unknown) {
const durationMs = Date.now() - startedAt;
if (invocationStarted && !(error instanceof RuntimeApprovalDeniedError)) {
await this.recordFailure(providerId, operation, scope, resourceId);
await this.recordFailure(providerId, operation, scope, resourceId, durationMs);
} else {
await this.record(providerId, operation, 'denied', scope, resourceId);
await this.record(
providerId,
operation,
'denied',
scope,
resourceId,
durationMs,
'policy_denied',
);
}
throw error;
}
@@ -300,6 +322,7 @@ export class RuntimeProviderService {
) => AsyncIterable<RuntimeStreamEvent>,
): AsyncIterable<RuntimeStreamEvent> {
const scope = this.deriveScope(context);
const startedAt = Date.now();
await this.record(providerId, operation, 'requested', scope, resourceId);
let invocationStarted = false;
try {
@@ -309,12 +332,21 @@ export class RuntimeProviderService {
for await (const event of invoke(provider, scope)) {
yield event;
}
await this.recordCompletion(providerId, operation, scope, resourceId);
await this.recordCompletion(providerId, operation, scope, resourceId, Date.now() - startedAt);
} catch (error: unknown) {
const durationMs = Date.now() - startedAt;
if (invocationStarted) {
await this.recordFailure(providerId, operation, scope, resourceId);
await this.recordFailure(providerId, operation, scope, resourceId, durationMs);
} else {
await this.record(providerId, operation, 'denied', scope, resourceId);
await this.record(
providerId,
operation,
'denied',
scope,
resourceId,
durationMs,
'policy_denied',
);
}
throw error;
}
@@ -358,9 +390,18 @@ export class RuntimeProviderService {
operation: RuntimeProviderOperation,
scope: RuntimeScope,
resourceId: string | undefined,
durationMs: number,
): Promise<void> {
try {
await this.record(providerId, operation, 'failed', scope, resourceId);
await this.record(
providerId,
operation,
'failed',
scope,
resourceId,
durationMs,
'provider_error',
);
} catch {
this.logger.error(
`Runtime provider failure audit failed provider=${providerId} operation=${operation} correlation=${scope.correlationId}`,
@@ -373,9 +414,10 @@ export class RuntimeProviderService {
operation: RuntimeProviderOperation,
scope: RuntimeScope,
resourceId: string | undefined,
durationMs: number,
): Promise<void> {
try {
await this.record(providerId, operation, 'succeeded', scope, resourceId);
await this.record(providerId, operation, 'succeeded', scope, resourceId, durationMs);
} catch {
this.logger.error(
`Runtime provider completion audit failed provider=${providerId} operation=${operation} correlation=${scope.correlationId}`,
@@ -389,6 +431,8 @@ export class RuntimeProviderService {
outcome: RuntimeProviderAuditOutcome,
scope: RuntimeScope,
resourceId: string | undefined,
durationMs?: number,
errorCode?: RuntimeAuditErrorCode,
): Promise<void> {
await this.audit.record({
providerId,
@@ -399,6 +443,8 @@ export class RuntimeProviderService {
channelId: scope.channelId,
correlationId: scope.correlationId,
...(resourceId ? { resourceId } : {}),
...(durationMs !== undefined ? { durationMs } : {}),
...(errorCode ? { errorCode } : {}),
});
}
}