mcard-js
Version:
MCard - Content-addressable storage with cryptographic hashing, handle resolution, and vector search for Node.js and browsers
232 lines • 7.44 kB
JavaScript
/**
* OpenTelemetrySidecar - Universal Observability for PTR Engine
*
* This module provides OpenTelemetry-based observability for the PTR runtime,
* enabling distributed tracing, metrics, and logging across all REPL phases.
*
* REPL Phase Instrumentation:
* - prep: Log V_pre validation status, trace artifact loading
* - exec: Metrics (CPU, memory, duration), trace execution path
* - post: Log verification results, trace VCard generation
* - await: Event for handle_history recording
*
* Works in both Node.js and Browser environments:
* - Node.js: Uses @opentelemetry/sdk-node
* - Browser: Uses FaroSidecar (Grafana Faro)
*
* See Also:
* - CLM_MCard_REPL_Implementation.md §11: Grafana LGTM Integration
* - PTR_MCard_CLM_Recent_Developments_Jan2026.md §6.2: Universal Observability
*/
/**
* REPL Phase enum for instrumentation
*/
export var REPLPhase;
(function (REPLPhase) {
REPLPhase["PREP"] = "prep";
REPLPhase["EXEC"] = "exec";
REPLPhase["POST"] = "post";
REPLPhase["AWAIT"] = "await";
})(REPLPhase || (REPLPhase = {}));
/**
* OpenTelemetrySidecar - Universal observability for PTR
*
* Provides instrumentation hooks for each REPL phase:
* - prep: Artifact loading, V_pre validation
* - exec: CLM execution, sandbox runtime
* - post: Balanced verification, VCard generation
* - await: Handle history recording, state transition
*
* Usage:
* const sidecar = OpenTelemetrySidecar.getInstance();
* sidecar.initialize({
* endpoint: "http://localhost:4317",
* serviceName: "ptr-runtime",
* serviceVersion: "1.0.0"
* });
*
* const span = sidecar.startPhase(REPLPhase.PREP, { pcardHash: "abc123" });
* // ... do prep work
* sidecar.endPhase(span);
*/
export class OpenTelemetrySidecar {
static instance = null;
_initialized = false;
_config = null;
_isNode;
// Phase timing metrics
_phaseDurations = new Map();
_phaseCounts = new Map();
_errorCounts = new Map();
constructor() {
// Detect environment
this._isNode = typeof window === 'undefined' && typeof process !== 'undefined';
// Initialize metrics maps
for (const phase of Object.values(REPLPhase)) {
this._phaseDurations.set(phase, []);
this._phaseCounts.set(phase, 0);
this._errorCounts.set(phase, 0);
}
}
/**
* Get the singleton instance
*/
static getInstance() {
if (!OpenTelemetrySidecar.instance) {
OpenTelemetrySidecar.instance = new OpenTelemetrySidecar();
}
return OpenTelemetrySidecar.instance;
}
/**
* Initialize the OpenTelemetry SDK
*/
initialize(config) {
if (this._initialized) {
console.warn('[OpenTelemetrySidecar] Already initialized');
return true;
}
this._config = config;
// In Node.js: Would initialize @opentelemetry/sdk-node
// In Browser: Falls back to console logging
// Full OTLP integration requires optional dependency
if (this._isNode) {
console.log(`[OpenTelemetrySidecar] Node.js mode initialized for ` +
`${config.serviceName}@${config.serviceVersion} -> ${config.endpoint}`);
console.log(`[OpenTelemetrySidecar] Note: Install @opentelemetry/sdk-node for full OTLP export`);
}
else {
console.log(`[OpenTelemetrySidecar] Browser mode - using console fallback. ` +
`Use FaroSidecar for browser observability.`);
}
this._initialized = true;
return true;
}
/**
* Start tracing a REPL phase
*/
startPhase(phase, options = {}) {
const context = {
phase,
pcardHash: options.pcardHash,
targetHash: options.targetHash,
attributes: options.attributes,
startTime: Date.now()
};
// Log phase start
this.logEvent(phase, `${phase}_start`, {
pcardHash: options.pcardHash ?? '',
targetHash: options.targetHash ?? '',
...options.attributes
});
return context;
}
/**
* End tracing a REPL phase
*/
endPhase(context, success = true, attributes) {
const duration = Date.now() - context.startTime;
// Record metrics
this._recordPhaseDuration(context.phase, duration);
this._recordPhaseCount(context.phase);
if (!success) {
this._recordError(context.phase);
}
// Log phase end
this.logEvent(context.phase, `${context.phase}_complete`, {
duration_ms: duration,
success,
...attributes
});
}
/**
* Log an event within a REPL phase
*/
logEvent(phase, eventName, attributes) {
const timestamp = new Date().toISOString();
const namespace = this._config?.namespace ?? 'ptr';
if (this._initialized) {
// In production: would use OTLP exporter
// For now: structured console logging
console.log(JSON.stringify({
timestamp,
name: `${namespace}.repl.${eventName}`,
phase,
attributes: attributes ?? {}
}));
}
}
/**
* Record phase duration metric
*/
_recordPhaseDuration(phase, durationMs) {
const durations = this._phaseDurations.get(phase) ?? [];
durations.push(durationMs);
// Keep last 100 samples
if (durations.length > 100) {
durations.shift();
}
this._phaseDurations.set(phase, durations);
}
/**
* Record phase count metric
*/
_recordPhaseCount(phase) {
const count = this._phaseCounts.get(phase) ?? 0;
this._phaseCounts.set(phase, count + 1);
}
/**
* Record error count
*/
_recordError(phase) {
const count = this._errorCounts.get(phase) ?? 0;
this._errorCounts.set(phase, count + 1);
}
/**
* Get metrics summary
*/
getMetrics() {
const metrics = {};
for (const phase of Object.values(REPLPhase)) {
const durations = this._phaseDurations.get(phase) ?? [];
const avgDuration = durations.length > 0
? durations.reduce((a, b) => a + b, 0) / durations.length
: 0;
metrics[phase] = {
count: this._phaseCounts.get(phase) ?? 0,
errors: this._errorCounts.get(phase) ?? 0,
avgDurationMs: Math.round(avgDuration * 100) / 100,
samples: durations.length
};
}
return metrics;
}
/**
* Check if initialized
*/
isInitialized() {
return this._initialized;
}
/**
* Check if running in Node.js
*/
isNode() {
return this._isNode;
}
}
/**
* Helper function to create a traced phase execution
*/
export async function tracePhase(phase, fn, options = {}) {
const sidecar = OpenTelemetrySidecar.getInstance();
const span = sidecar.startPhase(phase, options);
try {
const result = await fn();
sidecar.endPhase(span, true);
return result;
}
catch (error) {
sidecar.endPhase(span, false, { error: String(error) });
throw error;
}
}
//# sourceMappingURL=OpenTelemetrySidecar.js.map