From 06f7889b9986eb90a722f6ba1c1dcc8083439ed2 Mon Sep 17 00:00:00 2001 From: texnari4 <39917166+texnari4@users.noreply.github.com> Date: Wed, 28 Oct 2026 22:10:02 +0300 Subject: [PATCH 1/2] =?UTF-8?q?Export=20worker=20and=20workflow=20OTLP=20s?= =?UTF-8?q?pans=20for=20connected=20API=E2=86=92Temporal=20traces.=20Imple?= =?UTF-8?q?ments=20#61:=20shared=20exporter,=20Temporal=20interceptors/sin?= =?UTF-8?q?ks,=20allowlisted=20attributes,=20continue-as-new=20/=20backgro?= =?UTF-8?q?und=20links,=20and=20integration=20tests.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/src/dot-goals.ts | 26 +- .../src/memory-consolidation-coordinator.ts | 8 +- apps/api/src/memory-mutation-coordinator.ts | 8 +- apps/api/src/room-agent.ts | 13 +- apps/api/src/trace-context.ts | 2 +- apps/worker/package.json | 1 + apps/worker/src/activity-observability.ts | 71 ++- apps/worker/src/memory-maintenance-worker.ts | 7 +- apps/worker/src/memory-mutation-worker.ts | 7 +- apps/worker/src/worker.ts | 9 +- .../workflow-observability-interceptors.ts | 457 ++++++++++++++ apps/worker/src/workflow-observability.ts | 137 +++++ .../tests/activity-observability.test.ts | 6 +- apps/worker/tests/otlp-probe-workflows.ts | 46 ++ .../tests/workflow-otlp-observability.test.ts | 578 ++++++++++++++++++ docs/configuration-reference.md | 7 + docs/quality/observability.md | 52 +- .../otlp-api-to-activity-trace.png | Bin 0 -> 155311 bytes packages/contracts/src/schemas.ts | 9 +- packages/runtime/src/otlp-span-exporter.ts | 134 +++- 20 files changed, 1511 insertions(+), 67 deletions(-) create mode 100644 apps/worker/src/workflow-observability-interceptors.ts create mode 100644 apps/worker/src/workflow-observability.ts create mode 100644 apps/worker/tests/otlp-probe-workflows.ts create mode 100644 apps/worker/tests/workflow-otlp-observability.test.ts create mode 100644 docs/quality/screenshots/otlp-api-to-activity-trace.png diff --git a/apps/api/src/dot-goals.ts b/apps/api/src/dot-goals.ts index 30dd69dee..face965f0 100644 --- a/apps/api/src/dot-goals.ts +++ b/apps/api/src/dot-goals.ts @@ -16,6 +16,7 @@ import { } from "@space/contracts"; import { SpaceConflictError, SpaceNotFoundError, type SpaceStore } from "@space/runtime"; import type { DotGoalsRepository, DotsRepository } from "@space/db"; +import { traceContextForRawRequest, traceparentForTraceId, workflowCorrelationMemo } from "./trace-context.js"; /** * Space Dots Stage 4: Goals. @@ -26,7 +27,7 @@ import type { DotGoalsRepository, DotsRepository } from "@space/db"; * blocking question, cancellation, three consecutive failures or the budget. */ export interface DotGoalWorkflowCoordinator { - start(goalId: string): Promise; + start(goalId: string, trace?: { traceId?: string | null; traceparent?: string | null }): Promise; wake(goalId: string): Promise; /** Starts the Goal's workflow when none is running; true when it had to start one. */ ensure?(goalId: string): Promise; @@ -38,11 +39,18 @@ export class TemporalDotGoalWorkflowCoordinator implements DotGoalWorkflowCoordi try { return await action(new Client({ connection, namespace: this.options.namespace })); } finally { await connection.close(); } } - start(goalId: string) { + start(goalId: string, trace?: { traceId?: string | null; traceparent?: string | null }) { + const traceparent = trace?.traceparent ?? traceparentForTraceId(trace?.traceId); + const memo = workflowCorrelationMemo({ traceId: trace?.traceId, traceparent }); return this.client(async (client) => { try { - await client.workflow.start("dotGoalWorkflow", { workflowId: dotGoalWorkflowId(goalId), taskQueue: this.options.taskQueue, - args: [{ goalId }], workflowIdReusePolicy: "REJECT_DUPLICATE" }); + await client.workflow.start("dotGoalWorkflow", { + workflowId: dotGoalWorkflowId(goalId), + taskQueue: this.options.taskQueue, + args: [{ goalId, ...(trace?.traceId ? { traceId: trace.traceId } : {}), ...(traceparent ? { traceparent } : {}) }], + workflowIdReusePolicy: "REJECT_DUPLICATE", + ...(Object.keys(memo).length ? { memo } : {}) + }); } catch (error) { if (!(error instanceof WorkflowExecutionAlreadyStartedError)) throw error; } }); } @@ -125,7 +133,15 @@ export function createDotGoalService(deps: DotGoalServiceDeps) { if ((error as { code?: string }).code === "DOT_GOAL_OPEN") throw new DotGoalError(409, "DOT_GOAL_OPEN", "This conversation already has an open Goal."); throw error; } - try { await deps.workflow.start(goal.goalId); } + try { + if (request) { + const traceId = request.id ? String(request.id) : null; + const traceparent = request.raw ? traceContextForRawRequest(request.raw).traceparent : traceparentForTraceId(traceId); + await deps.workflow.start(goal.goalId, { traceId, traceparent }); + } else { + await deps.workflow.start(goal.goalId); + } + } catch { goal = await finish(goal, "FAILED", "The Goal runtime could not start. Try again."); throw new DotGoalError(503, "DOT_GOAL_RUNTIME", "The Goal runtime could not start. Try again."); diff --git a/apps/api/src/memory-consolidation-coordinator.ts b/apps/api/src/memory-consolidation-coordinator.ts index df237c648..a939691c3 100644 --- a/apps/api/src/memory-consolidation-coordinator.ts +++ b/apps/api/src/memory-consolidation-coordinator.ts @@ -6,6 +6,7 @@ import { type MemoryConsolidationWorkflowInput } from "@space/contracts"; import { SpaceFeatureDisabledError } from "@space/runtime"; +import { withTurnTraceparent, workflowCorrelationMemo } from "./trace-context.js"; export { MEMORY_CONSOLIDATION_TASK_QUEUE, MEMORY_CONSOLIDATION_WORKFLOW_TYPE }; @@ -21,6 +22,7 @@ interface WorkflowStartOptions { workflowId: string; workflowIdReusePolicy: "REJECT_DUPLICATE"; workflowIdConflictPolicy: "USE_EXISTING"; + memo?: Record; } interface WorkflowStartHandle { @@ -42,14 +44,16 @@ export async function scheduleMemoryConsolidation( workflowId: string, startWorkflow: MemoryConsolidationWorkflowStarter ): Promise { - const input = memoryConsolidationWorkflowInputSchema.parse(rawInput); + const input = withTurnTraceparent(memoryConsolidationWorkflowInputSchema.parse(rawInput)); + const memo = input.traceparent ? workflowCorrelationMemo({ traceId: input.traceId, traceparent: input.traceparent }) : undefined; try { const handle = await startWorkflow(MEMORY_CONSOLIDATION_WORKFLOW_TYPE, { args: [input], taskQueue: MEMORY_CONSOLIDATION_TASK_QUEUE, workflowId, workflowIdReusePolicy: "REJECT_DUPLICATE", - workflowIdConflictPolicy: "USE_EXISTING" + workflowIdConflictPolicy: "USE_EXISTING", + ...(memo && Object.keys(memo).length ? { memo } : {}) }); return { status: "SCHEDULED", diff --git a/apps/api/src/memory-mutation-coordinator.ts b/apps/api/src/memory-mutation-coordinator.ts index f7d3d48bb..dc2a370fc 100644 --- a/apps/api/src/memory-mutation-coordinator.ts +++ b/apps/api/src/memory-mutation-coordinator.ts @@ -7,6 +7,7 @@ import { type MemoryMutationWorkflowInput } from "@space/contracts"; import { SpaceFeatureDisabledError } from "@space/runtime"; +import { withTurnTraceparent, workflowCorrelationMemo } from "./trace-context.js"; export { MEMORY_MUTATION_TASK_QUEUE, MEMORY_MUTATION_WORKFLOW_TYPE, buildMemoryMutationWorkflowId }; @@ -22,6 +23,7 @@ interface WorkflowStartOptions { workflowId: string; workflowIdReusePolicy: "REJECT_DUPLICATE"; workflowIdConflictPolicy: "USE_EXISTING"; + memo?: Record; } interface WorkflowStartHandle { @@ -43,15 +45,17 @@ export async function scheduleMemoryMutation( startWorkflow: MemoryMutationWorkflowStarter, commandHash?: string ): Promise { - const input = memoryMutationWorkflowInputSchema.parse(rawInput); + const input = withTurnTraceparent(memoryMutationWorkflowInputSchema.parse(rawInput)); const workflowId = buildMemoryMutationWorkflowId(input.changeSetId, commandHash); + const memo = input.traceparent ? workflowCorrelationMemo({ traceId: input.traceId, traceparent: input.traceparent }) : undefined; try { const handle = await startWorkflow(MEMORY_MUTATION_WORKFLOW_TYPE, { args: [input], taskQueue: MEMORY_MUTATION_TASK_QUEUE, workflowId, workflowIdReusePolicy: "REJECT_DUPLICATE", - workflowIdConflictPolicy: "USE_EXISTING" + workflowIdConflictPolicy: "USE_EXISTING", + ...(memo && Object.keys(memo).length ? { memo } : {}) }); return { status: "SCHEDULED", workflowId: handle.workflowId, runId: handle.firstExecutionRunId ?? null }; } catch (error) { diff --git a/apps/api/src/room-agent.ts b/apps/api/src/room-agent.ts index 5eccd5097..f4f945117 100644 --- a/apps/api/src/room-agent.ts +++ b/apps/api/src/room-agent.ts @@ -188,16 +188,17 @@ export class TemporalRoomAgentWorkflowCoordinator implements RoomAgentWorkflowCo async enqueueAction(actionId: string, bridge: SpaceAgentRoomActionBridgeRequest, traceId: string): Promise { const lease = await this.connect("roomAgentActionWorkflow"); + const traceparent = traceparentForTraceId(traceId) ?? traceparentForTraceId(bridge.requestId ?? null); try { try { await lease.client.workflow.start("roomAgentActionWorkflow", { workflowId: `space-room-action:${actionId}`, taskQueue: this.options.taskQueue, - args: [{ actionId, bridge: { ...bridge, backgroundExecution: true }, traceId }], + args: [{ actionId, bridge: { ...bridge, backgroundExecution: true }, traceId, ...(traceparent ? { traceparent } : {}) }], workflowIdReusePolicy: "REJECT_DUPLICATE", // traceId is this internal request; bridge.requestId is the originating request. memo: workflowCorrelationMemo({ traceId, - traceparent: traceparentForTraceId(traceId), + traceparent, parentTraceId: bridge.requestId ?? null, missionId: bridge.missionId }) @@ -219,6 +220,11 @@ export class TemporalRoomAgentWorkflowCoordinator implements RoomAgentWorkflowCo // the child turn workflow and its activities (additive optional field). const item = { ...parsed, turn: withTurnTraceparent(parsed.turn) }; const workflowId = buildRoomAgentSupervisorWorkflowId(item.turn.roomId); + const memo = workflowCorrelationMemo({ + traceId: item.turn.traceId, + traceparent: item.turn.traceparent, + missionId: item.missionId + }); const lease = await this.connect(ROOM_AGENT_SUPERVISOR_WORKFLOW_TYPE); try { const handle = await lease.client.workflow.signalWithStart(ROOM_AGENT_SUPERVISOR_WORKFLOW_TYPE, { @@ -226,7 +232,8 @@ export class TemporalRoomAgentWorkflowCoordinator implements RoomAgentWorkflowCo taskQueue: this.options.taskQueue, args: [{ roomId: item.turn.roomId, pending: [], processedCount: 0 }], signal: ROOM_AGENT_ENQUEUE_SIGNAL, - signalArgs: [item] + signalArgs: [item], + ...(Object.keys(memo).length ? { memo } : {}) }); recordWorkflowStart(ROOM_AGENT_SUPERVISOR_WORKFLOW_TYPE, "SIGNALED"); return { workflowId, runId: handle.signaledRunId }; diff --git a/apps/api/src/trace-context.ts b/apps/api/src/trace-context.ts index af9e55efd..f45617fc6 100644 --- a/apps/api/src/trace-context.ts +++ b/apps/api/src/trace-context.ts @@ -96,7 +96,7 @@ export const requestTraceContexts = new TraceContextRegistry(); /** traceparent for work started on behalf of this Space request id, if known. */ export function traceparentForTraceId(traceId: string | null | undefined, registry = requestTraceContexts): string | null { - return childTraceparent(registry.lookup(traceId)?.traceparent ?? null); + return registry.lookup(traceId)?.traceparent ?? null; } /** Adds the request's traceparent to a turn input unless it already has a valid one. */ diff --git a/apps/worker/package.json b/apps/worker/package.json index ec58125fa..db05b0164 100644 --- a/apps/worker/package.json +++ b/apps/worker/package.json @@ -26,6 +26,7 @@ "undici": "8.12.3" }, "devDependencies": { + "@temporalio/testing": "1.24.0", "typescript": "^5.9.3", "vitest": "^4.1.12" }, diff --git a/apps/worker/src/activity-observability.ts b/apps/worker/src/activity-observability.ts index ea4d021d1..409fdd6bf 100644 --- a/apps/worker/src/activity-observability.ts +++ b/apps/worker/src/activity-observability.ts @@ -1,7 +1,7 @@ import { AsyncLocalStorage } from "node:async_hooks"; import { randomBytes } from "node:crypto"; import type { Context } from "@temporalio/activity"; -import type { ActivityInterceptorsFactory } from "@temporalio/worker"; +import { defaultPayloadConverter, type ActivityInterceptorsFactory, type Headers } from "@temporalio/worker"; import { categorizeFailureCode, formatTraceparent, @@ -10,9 +10,11 @@ import { traceIdFromTraceparent, type FailureCategory } from "@space/contracts"; -import type { SpanExporter } from "@space/runtime"; +import { sanitizeSpanAttributes, type SpanExporter } from "@space/runtime"; import { logIdentifier, workerLog, type WorkerLogValue } from "./worker-log.js"; import { workerMetrics, type WorkerMetrics } from "./worker-metrics.js"; +import { lookupActiveWorkflowTrace } from "./workflow-observability.js"; +import { WORKFLOW_TRACE_ID_HEADER, WORKFLOW_TRACEPARENT_HEADER } from "./workflow-observability-interceptors.js"; /** * One structured start/finish record and metrics per activity execution, with @@ -135,8 +137,21 @@ export interface ObserveActivityDependencies { metrics?: WorkerMetrics; now?: () => number; cancelled?: () => boolean; - /** OTLP export (SPACE_OTEL_EXPORTER_OTLP_ENDPOINT): one span per activity whose input carries a traceparent. */ + /** OTLP export (SPACE_OTEL_EXPORTER_OTLP_ENDPOINT): one span per activity whose input or workflow header carries a traceparent. */ spans?: SpanExporter | null; + headers?: Headers; +} + +function readActivityHeader(headers: Headers | undefined, key: string): string | null { + if (!headers) return null; + const payload = headers[key]; + if (!payload) return null; + try { + const decoded = defaultPayloadConverter.fromPayload(payload); + return typeof decoded === "string" ? decoded : null; + } catch { + return null; + } } function activityTraceparent(args: readonly unknown[]) { @@ -194,8 +209,49 @@ export async function observeActivityExecution( queueMs, rssBytes: process.memoryUsage().rss }); - const parent = dependencies.spans ? activityTraceparent(args) : null; + const headerTp = parseTraceparent(readActivityHeader(dependencies.headers, WORKFLOW_TRACEPARENT_HEADER)); + const activeWf = lookupActiveWorkflowTrace( + info.workflowExecution?.workflowId ?? null, + info.workflowExecution?.runId ?? null + ); + const activeWfTp = parseTraceparent(activeWf?.traceparent); + const parent = dependencies.spans ? (headerTp ?? activeWfTp ?? activityTraceparent(args)) : null; if (parent) { + const corr = activityCorrelation(args); + const rawAttributes: Record = { + ...(activeWf?.correlation ?? {}), + "space.activity_type": activityType, + "space.outcome": outcome, + "space.attempt": info.attempt, + "space.duration_ms": durationMs + }; + if (info.activityId) rawAttributes["space.activity_id"] = String(info.activityId); + if (info.workflowType) rawAttributes["space.workflow_type"] = info.workflowType; + if (info.workflowExecution?.workflowId) rawAttributes["space.workflow_id"] = info.workflowExecution.workflowId; + if (info.workflowExecution?.runId) rawAttributes["space.run_id"] = info.workflowExecution.runId; + if (queueMs !== null) rawAttributes["space.queue_ms"] = queueMs; + if (reasonCode) rawAttributes["space.reason_code"] = reasonCode; + if (category) rawAttributes["space.category"] = category; + const headerTraceId = readActivityHeader(dependencies.headers, WORKFLOW_TRACE_ID_HEADER); + if (headerTraceId) rawAttributes["space.trace_id"] = headerTraceId; + const corrAttrMap: ReadonlyArray = [ + ["traceId", "space.trace_id"], + ["parentTraceId", "space.parent_trace_id"], + ["missionId", "space.mission_id"], + ["roomId", "space.room_id"], + ["paneId", "space.pane_id"], + ["agentRunId", "space.agent_run_id"], + ["agentSessionId", "space.agent_session_id"], + ["actionId", "space.action_id"], + ["goalId", "space.goal_id"], + ["dotId", "space.dot_id"], + ["turnId", "space.turn_id"], + ["cliTaskId", "space.cli_task_id"], + ["revisionId", "space.revision_id"] + ]; + for (const [corrKey, attrKey] of corrAttrMap) { + if (corr[corrKey]) rawAttributes[attrKey] = corr[corrKey]!; + } dependencies.spans!.record({ traceId: parent.traceId, spanId: randomBytes(8).toString("hex"), @@ -203,7 +259,7 @@ export async function observeActivityExecution( name: `activity ${activityType}`, startTimeUnixNano: BigInt(startedAt) * 1_000_000n, endTimeUnixNano: BigInt(startedAt + durationMs) * 1_000_000n, - attributes: { "space.activity_type": activityType, "space.outcome": outcome, "space.attempt": info.attempt }, + attributes: sanitizeSpanAttributes(rawAttributes), error: outcome === "FAILED" || outcome === "FAILED_RESULT", kind: 1 }); @@ -224,11 +280,14 @@ export async function observeActivityExecution( } /** Temporal worker interceptor factory: register in Worker.create({ interceptors }). */ -export function createActivityObservabilityInterceptors(dependencies: Omit = {}): ActivityInterceptorsFactory { +export function createActivityObservabilityInterceptors( + dependencies: Omit = {} +): ActivityInterceptorsFactory { return (context) => ({ inbound: { execute: (input, next) => observeActivityExecution(context.info, input.args, () => next(input), { ...dependencies, + headers: input.headers, cancelled: () => context.cancellationSignal.aborted }) } diff --git a/apps/worker/src/memory-maintenance-worker.ts b/apps/worker/src/memory-maintenance-worker.ts index e12046033..5535be4a9 100644 --- a/apps/worker/src/memory-maintenance-worker.ts +++ b/apps/worker/src/memory-maintenance-worker.ts @@ -9,6 +9,7 @@ import { import { DEFAULT_TEMPORAL_ADDRESS, DEFAULT_TEMPORAL_NAMESPACE } from "./constants.js"; import { otlpSpanExporterFromEnv } from "@space/runtime"; import { createActivityObservabilityInterceptors } from "./activity-observability.js"; +import { createWorkflowObservabilitySinks, workflowObservabilityInterceptorPath } from "./workflow-observability.js"; import { reloadWhenServicesCommitChanges } from "./deployed-code-reload.js"; const service = "space-memory-maintenance-worker"; @@ -30,7 +31,11 @@ if (!memoryMaintenanceEnabled(process.env)) { workflowsPath, activities, ...memoryMaintenanceWorkerOptions, - interceptors: { activity: [createActivityObservabilityInterceptors({ spans })] } + interceptors: { + activity: [createActivityObservabilityInterceptors({ spans })], + workflowModules: [workflowObservabilityInterceptorPath] + }, + sinks: createWorkflowObservabilitySinks({ spans }) }); console.log(JSON.stringify({ service, diff --git a/apps/worker/src/memory-mutation-worker.ts b/apps/worker/src/memory-mutation-worker.ts index c2eadf657..5afb333a3 100644 --- a/apps/worker/src/memory-mutation-worker.ts +++ b/apps/worker/src/memory-mutation-worker.ts @@ -5,6 +5,7 @@ import { MEMORY_MUTATION_TASK_QUEUE, memoryMutationsEnabled, memoryMutationWorke import { DEFAULT_TEMPORAL_ADDRESS, DEFAULT_TEMPORAL_NAMESPACE } from "./constants.js"; import { otlpSpanExporterFromEnv } from "@space/runtime"; import { createActivityObservabilityInterceptors } from "./activity-observability.js"; +import { createWorkflowObservabilitySinks, workflowObservabilityInterceptorPath } from "./workflow-observability.js"; import { reloadWhenServicesCommitChanges } from "./deployed-code-reload.js"; const service = "space-memory-mutation-worker"; @@ -23,7 +24,11 @@ if (!memoryMutationsEnabled(process.env)) { workflowsPath: fileURLToPath(new URL("./memory-mutation-workflows.js", import.meta.url)), activities: { executePersistedMemoryChangeSet }, ...memoryMutationWorkerOptions, - interceptors: { activity: [createActivityObservabilityInterceptors({ spans })] } + interceptors: { + activity: [createActivityObservabilityInterceptors({ spans })], + workflowModules: [workflowObservabilityInterceptorPath] + }, + sinks: createWorkflowObservabilitySinks({ spans }) }); console.log(JSON.stringify({ service, status: "running", address, namespace, taskQueue: MEMORY_MUTATION_TASK_QUEUE })); const stopReloadWatch = reloadWhenServicesCommitChanges(worker, { log: (message) => console.log(JSON.stringify({ service, status: "reloading", message })) }); diff --git a/apps/worker/src/worker.ts b/apps/worker/src/worker.ts index 83d23cbc6..c7f689fe7 100644 --- a/apps/worker/src/worker.ts +++ b/apps/worker/src/worker.ts @@ -7,6 +7,7 @@ import { TelegramDeliveryWorker, TelegramSecretStore, otlpSpanExporterFromEnv } import { NativeConnection, Runtime, Worker } from "@temporalio/worker"; import * as activities from "./activities.js"; import { createActivityObservabilityInterceptors } from "./activity-observability.js"; +import { createWorkflowObservabilitySinks, workflowObservabilityInterceptorPath } from "./workflow-observability.js"; import { resolveTemporalCoreMetricsBind, resolveWorkerMetricsListen, startWorkerMetricsServer } from "./worker-metrics.js"; import { workerLog } from "./worker-log.js"; import { sweepCodexCliCompletions } from "./codex-cli-completion.js"; @@ -43,9 +44,11 @@ const worker = await Worker.create({ taskQueue, workflowsPath, activities, - // Structured, correlated activity records and worker metrics. Activity-side - // only: the workflow sandbox and its replay behaviour are unchanged. - interceptors: { activity: [createActivityObservabilityInterceptors({ spans })] } + interceptors: { + activity: [createActivityObservabilityInterceptors({ spans })], + workflowModules: [workflowObservabilityInterceptorPath] + }, + sinks: createWorkflowObservabilitySinks({ spans }) }); // Optional loopback-only Prometheus endpoint for worker metrics. diff --git a/apps/worker/src/workflow-observability-interceptors.ts b/apps/worker/src/workflow-observability-interceptors.ts new file mode 100644 index 000000000..e7cd83cd6 --- /dev/null +++ b/apps/worker/src/workflow-observability-interceptors.ts @@ -0,0 +1,457 @@ +import { + defaultPayloadConverter, + isCancellation, + proxySinks, + workflowInfo, + type ActivityInput, + type ContinueAsNewInput, + type Headers, + type LocalActivityInput, + type Next, + type SignalInput, + type Sinks, + type StartChildWorkflowExecutionInput, + type WorkflowExecuteInput, + type WorkflowInboundCallsInterceptor, + type WorkflowInterceptorsFactory, + type WorkflowOutboundCallsInterceptor +} from "@temporalio/workflow"; +import { + categorizeFailureCode, + formatTraceparent, + isSafeErrorCode, + parseTraceparent +} from "@space/contracts"; + +export const WORKFLOW_TRACEPARENT_HEADER = "space-traceparent"; +export const WORKFLOW_CONTINUED_TRACEPARENT_HEADER = "space-continued-from-traceparent"; +export const WORKFLOW_TRACE_ID_HEADER = "space-trace-id"; + +export interface WorkflowSpanLinkRecord { + traceId: string; + spanId: string; + attributes?: Record; +} + +export interface WorkflowSpanRecordPayload { + traceId: string; + spanId: string; + parentSpanId: string | null; + workflowType: string; + workflowId: string; + runId: string; + parentWorkflowId: string | null; + parentRunId: string | null; + continuedFromRunId: string | null; + outcome: "COMPLETED" | "FAILED" | "CANCELLED" | "CONTINUED_AS_NEW"; + reasonCode: string | null; + category: string | null; + correlation: Record; + links: WorkflowSpanLinkRecord[]; +} + +export interface WorkflowObservabilitySinks extends Sinks { + workflowObservability: { + workflowStarted(spanId: string, traceparent: string, correlation: Record): void; + workflowFinished(payload: WorkflowSpanRecordPayload): void; + }; +} + +const { workflowObservability } = proxySinks(); + +const safeIdPattern = /^[A-Za-z0-9:._-]{1,240}$/; + +function safeId(value: unknown): string | null { + return typeof value === "string" && safeIdPattern.test(value) ? value : null; +} + +/** + * Pure deterministic 64-bit FNV-1a / SplitMix64 hex generator for workflow isolate. + * Produces 16 hex chars (spanId) or 32 hex chars (traceId) without Node crypto. + */ +function deterministicHexBlock(seed: string, salt: number): string { + let hash = 0xcbf29ce484222325n ^ BigInt(salt & 0xffffffff); + const prime = 0x100000001b3n; + for (let index = 0; index < seed.length; index += 1) { + hash ^= BigInt(seed.charCodeAt(index)); + hash = (hash * prime) & 0xffffffffffffffffn; + } + // SplitMix64 finalizer + hash = ((hash ^ (hash >> 30n)) * 0xbf58476d1ce4e5b9n) & 0xffffffffffffffffn; + hash = ((hash ^ (hash >> 27n)) * 0x94d049bb133111ebn) & 0xffffffffffffffffn; + hash = (hash ^ (hash >> 31n)) & 0xffffffffffffffffn; + if (hash === 0n) hash = 1n; + return hash.toString(16).padStart(16, "0"); +} + +export function workflowSpanId(workflowId: string, runId: string): string { + return deterministicHexBlock(`wf-span:${workflowId}:${runId}`, 0x53504143); +} + +export function deterministicTraceId(seed: string): string { + return `${deterministicHexBlock(`wf-trace-hi:${seed}`, 0x10203040)}${deterministicHexBlock(`wf-trace-lo:${seed}`, 0x50607080)}`; +} + +export function continuationSpanId(sourceRunId: string): string { + return deterministicHexBlock(`bg-continuation:${sourceRunId}`, 0x4247434e); +} + +function readHeaderString(headers: Headers | undefined, key: string): string | null { + if (!headers) return null; + const payload = headers[key]; + if (!payload) return null; + try { + const decoded = defaultPayloadConverter.fromPayload(payload); + return typeof decoded === "string" ? decoded : null; + } catch { + return null; + } +} + +const correlationKeys = [ + "traceId", + "traceparent", + "requestId", + "roomId", + "paneId", + "missionId", + "roomAgentMissionId", + "agentRunId", + "agentSessionId", + "actionId", + "goalId", + "dotId", + "turnId", + "cliTaskId", + "revisionId", + "changeSetId", + "runId" +] as const; +const nestedKeys = ["turn", "item", "bridge", "input", "turnInput"] as const; + +type CorrelationKey = (typeof correlationKeys)[number]; + +interface ExtractedWorkflowInput { + fields: Partial>; + sourceRunId: string | null; +} + +function scanObject(value: unknown, out: ExtractedWorkflowInput, depth: number): void { + if (!value || typeof value !== "object") return; + if (Array.isArray(value)) { + if (depth > 0) { + for (const item of value.slice(0, 4)) scanObject(item, out, depth - 1); + } + return; + } + const record = value as Record; + for (const key of correlationKeys) { + if (out.fields[key] !== undefined) continue; + const id = safeId(record[key]); + if (id !== null) out.fields[key] = id; + } + const continuation = record.roomAgentContinuation; + if (!out.sourceRunId && continuation && typeof continuation === "object" && !Array.isArray(continuation)) { + const sourceId = safeId((continuation as Record).sourceRunId); + if (sourceId) out.sourceRunId = sourceId; + } + if (depth <= 0) return; + for (const key of nestedKeys) { + scanObject(record[key], out, depth - 1); + } + if (Array.isArray(record.pending)) { + scanObject(record.pending[0], out, depth - 1); + } +} + +export function extractWorkflowInputMetadata( + args: readonly unknown[], + memo?: Record +): ExtractedWorkflowInput { + const out: ExtractedWorkflowInput = { fields: {}, sourceRunId: null }; + for (const arg of args) scanObject(arg, out, 3); + if (memo && typeof memo === "object") { + if (!out.fields.traceparent) { + const memoTp = safeId(memo.traceparent); + if (memoTp) out.fields.traceparent = memoTp; + } + if (!out.fields.traceId) { + const memoTraceId = safeId(memo.spaceTraceId); + if (memoTraceId) out.fields.traceId = memoTraceId; + } + if (!out.fields.requestId) { + const parentSpaceTraceId = safeId(memo.parentSpaceTraceId); + if (parentSpaceTraceId) out.fields.requestId = parentSpaceTraceId; + } + if (!out.fields.missionId) { + const memoMissionId = safeId(memo.missionId); + if (memoMissionId) out.fields.missionId = memoMissionId; + } + } + return out; +} + +function buildCorrelationAttributes(fields: Partial>): Record { + const attrs: Record = {}; + if (fields.traceId) attrs["space.trace_id"] = fields.traceId; + if (fields.requestId && fields.requestId !== fields.traceId) attrs["space.parent_trace_id"] = fields.requestId; + const missionId = fields.missionId ?? fields.roomAgentMissionId; + if (missionId) attrs["space.mission_id"] = missionId; + const mapping: ReadonlyArray = [ + ["roomId", "space.room_id"], + ["paneId", "space.pane_id"], + ["agentRunId", "space.agent_run_id"], + ["agentSessionId", "space.agent_session_id"], + ["actionId", "space.action_id"], + ["goalId", "space.goal_id"], + ["dotId", "space.dot_id"], + ["turnId", "space.turn_id"], + ["cliTaskId", "space.cli_task_id"], + ["revisionId", "space.revision_id"], + ["changeSetId", "space.change_set_id"] + ]; + for (const [field, attrKey] of mapping) { + if (fields[field]) attrs[attrKey] = fields[field]!; + } + return attrs; +} + +function workflowErrorCode(error: unknown): string { + let cursor = error as { name?: unknown; type?: unknown; code?: unknown; cause?: unknown } | undefined; + for (let depth = 0; cursor && depth < 8; depth += 1, cursor = cursor.cause as typeof cursor) { + if (isSafeErrorCode(cursor.code)) return cursor.code; + if (isSafeErrorCode(cursor.type)) return cursor.type; + if (cursor.name === "TimeoutFailure") return "WORKFLOW_TIMEOUT"; + } + return "WORKFLOW_ERROR"; +} + +function isSupervisorMissionTerminalActivity(activityType: string): boolean { + return ( + activityType === "finishRoomAgentSupervisorTurn" || + activityType === "markRoomAgentMissionFinished" || + activityType === "markRoomAgentMissionContinued" || + activityType === "discardRoomAgentSupervisorTurn" + ); +} + +export const interceptors: WorkflowInterceptorsFactory = () => { + const info = workflowInfo(); + let spanId = workflowSpanId(info.workflowId, info.runId); + let traceId = deterministicTraceId(`${info.workflowId}:${info.firstExecutionRunId || info.runId}`); + let parentSpanId: string | null = info.parent ? workflowSpanId(info.parent.workflowId, info.parent.runId) : null; + let flags = "01"; + let hasExplicitTrace = false; + let correlation: Record = {}; + const links: WorkflowSpanLinkRecord[] = []; + let spanEmitted = false; + + const currentTraceparent = () => formatTraceparent(traceId, spanId, flags); + + const applyTraceInput = (args: readonly unknown[], headers?: Headers) => { + const headerTp = parseTraceparent(readHeaderString(headers, WORKFLOW_TRACEPARENT_HEADER)); + const continuedTp = parseTraceparent(readHeaderString(headers, WORKFLOW_CONTINUED_TRACEPARENT_HEADER)); + const headerTraceId = safeId(readHeaderString(headers, WORKFLOW_TRACE_ID_HEADER)); + const extracted = extractWorkflowInputMetadata(args, info.memo); + const argTp = parseTraceparent(extracted.fields.traceparent); + const traceIdField = extracted.fields.traceId ?? ""; + // Background continuations keep a new W3C trace (`trace:bg-*`) and point + // at the caller's span with a link. A parent-workflow header still nests + // real child workflows under the parent span. + const isBackgroundContinuation = Boolean(extracted.sourceRunId) || traceIdField.startsWith("trace:bg-"); + + const chosenTp = headerTp ?? (isBackgroundContinuation ? null : argTp); + if (chosenTp) { + traceId = chosenTp.traceId; + parentSpanId = chosenTp.parentId; + flags = chosenTp.flags; + hasExplicitTrace = true; + } else if (info.parent) { + parentSpanId = workflowSpanId(info.parent.workflowId, info.parent.runId); + } + + const nextCorrelation = buildCorrelationAttributes(extracted.fields); + if (!nextCorrelation["space.trace_id"] && headerTraceId) { + nextCorrelation["space.trace_id"] = headerTraceId; + } + correlation = { ...correlation, ...nextCorrelation }; + + // 1. Continue-As-New link + if (info.continuedFromExecutionRunId || continuedTp) { + const prevTraceId = continuedTp?.traceId ?? traceId; + const prevSpanId = + continuedTp?.parentId ?? + (info.continuedFromExecutionRunId ? workflowSpanId(info.workflowId, info.continuedFromExecutionRunId) : null); + if (prevSpanId && !links.some(l => l.spanId === prevSpanId && l.attributes?.["space.link_kind"] === "CONTINUE_AS_NEW")) { + links.push({ + traceId: prevTraceId, + spanId: prevSpanId, + attributes: { + "space.link_kind": "CONTINUE_AS_NEW", + ...(info.continuedFromExecutionRunId ? { "space.continued_from_run_id": info.continuedFromExecutionRunId } : {}) + } + }); + } + } + + // 2. Background continuation link (`trace:bg-...` or `roomAgentContinuation.sourceRunId`). + // Prefer the caller's real span (traceparent) over a synthetic id so the link + // resolves in an OTLP backend. + if (isBackgroundContinuation) { + const sourceRunId = extracted.sourceRunId ?? traceIdField; + const linkedTraceId = argTp?.traceId ?? continuedTp?.traceId ?? traceId; + const linkedSpanId = argTp?.parentId ?? continuedTp?.parentId ?? continuationSpanId(sourceRunId); + if (!links.some(l => l.spanId === linkedSpanId && l.attributes?.["space.link_kind"] === "BACKGROUND_CONTINUATION")) { + links.push({ + traceId: linkedTraceId, + spanId: linkedSpanId, + attributes: { + "space.link_kind": "BACKGROUND_CONTINUATION", + ...(sourceRunId ? { "space.source_run_id": sourceRunId } : {}) + } + }); + } + } + }; + + const emitStart = () => { + if (!info.unsafe.isReplaying) { + workflowObservability.workflowStarted(spanId, currentTraceparent(), correlation); + } + }; + + const emitFinish = ( + outcome: WorkflowSpanRecordPayload["outcome"], + reasonCode: string | null = null, + category: string | null = null + ) => { + if (spanEmitted) return; + spanEmitted = true; + if (!info.unsafe.isReplaying) { + workflowObservability.workflowFinished({ + traceId, + spanId, + parentSpanId, + workflowType: info.workflowType, + workflowId: info.workflowId, + runId: info.runId, + parentWorkflowId: info.parent?.workflowId ?? null, + parentRunId: info.parent?.runId ?? null, + continuedFromRunId: info.continuedFromExecutionRunId ?? null, + outcome, + reasonCode, + category, + correlation, + links + }); + } + }; + + const inbound: WorkflowInboundCallsInterceptor = { + async execute(input: WorkflowExecuteInput, next: Next) { + applyTraceInput(input.args, input.headers); + emitStart(); + try { + const result = await next(input); + const failedObj = + result && typeof result === "object" && (result as { status?: unknown }).status === "FAILED" + ? (result as { reasonCode?: unknown; errorCode?: unknown }) + : null; + if (failedObj) { + const code = isSafeErrorCode(failedObj.reasonCode) + ? failedObj.reasonCode + : isSafeErrorCode(failedObj.errorCode) + ? failedObj.errorCode + : "WORKFLOW_RESULT_FAILED"; + emitFinish("FAILED", code, categorizeFailureCode(code)); + } else { + emitFinish("COMPLETED"); + } + return result; + } catch (error) { + // ContinueAsNew is handled in the outbound continueAsNew interceptor before throwing. + if ((error as { name?: string } | null)?.name === "ContinueAsNew") { + emitFinish("CONTINUED_AS_NEW"); + throw error; + } + if (isCancellation(error)) { + emitFinish("CANCELLED", "WORKFLOW_CANCELLED", "CANCELLED"); + } else { + const code = workflowErrorCode(error); + emitFinish("FAILED", code, categorizeFailureCode(code)); + } + throw error; + } + }, + + async handleSignal(input: SignalInput, next: Next) { + const beforeExplicit = hasExplicitTrace; + applyTraceInput(input.args, input.headers); + if (!beforeExplicit && hasExplicitTrace) { + // A supervisor signaled with a mission turn starts a new mission span cycle if a previous one already emitted. + spanEmitted = false; + emitStart(); + } else if (spanEmitted) { + // Subsequent mission signaled into an existing supervisor workflow run. + spanId = workflowSpanId(info.workflowId, `${info.runId}:${ correlation["space.mission_id"] ?? "next" }`); + spanEmitted = false; + emitStart(); + } + return next(input); + } + }; + + const outbound: WorkflowOutboundCallsInterceptor = { + async scheduleActivity(input: ActivityInput, next: Next) { + input.headers[WORKFLOW_TRACEPARENT_HEADER] = defaultPayloadConverter.toPayload(currentTraceparent()); + const spaceTraceId = correlation["space.trace_id"]; + if (spaceTraceId) { + input.headers[WORKFLOW_TRACE_ID_HEADER] = defaultPayloadConverter.toPayload(spaceTraceId); + } + const result = await next(input); + if (info.workflowType === "roomAgentSupervisorWorkflow" && isSupervisorMissionTerminalActivity(input.activityType)) { + emitFinish("COMPLETED"); + } + return result; + }, + + async scheduleLocalActivity( + input: LocalActivityInput, + next: Next + ) { + input.headers[WORKFLOW_TRACEPARENT_HEADER] = defaultPayloadConverter.toPayload(currentTraceparent()); + const spaceTraceId = correlation["space.trace_id"]; + if (spaceTraceId) { + input.headers[WORKFLOW_TRACE_ID_HEADER] = defaultPayloadConverter.toPayload(spaceTraceId); + } + return next(input); + }, + + async startChildWorkflowExecution( + input: StartChildWorkflowExecutionInput, + next: Next + ) { + input.headers[WORKFLOW_TRACEPARENT_HEADER] = defaultPayloadConverter.toPayload(currentTraceparent()); + const spaceTraceId = correlation["space.trace_id"]; + if (spaceTraceId) { + input.headers[WORKFLOW_TRACE_ID_HEADER] = defaultPayloadConverter.toPayload(spaceTraceId); + } + return next(input); + }, + + async continueAsNew(input: ContinueAsNewInput, next: Next) { + input.headers[WORKFLOW_CONTINUED_TRACEPARENT_HEADER] = defaultPayloadConverter.toPayload(currentTraceparent()); + input.headers[WORKFLOW_TRACEPARENT_HEADER] = defaultPayloadConverter.toPayload( + parentSpanId ? formatTraceparent(traceId, parentSpanId, flags) : currentTraceparent() + ); + const spaceTraceId = correlation["space.trace_id"]; + if (spaceTraceId) { + input.headers[WORKFLOW_TRACE_ID_HEADER] = defaultPayloadConverter.toPayload(spaceTraceId); + } + emitFinish("CONTINUED_AS_NEW"); + return next(input); + } + }; + + return { inbound: [inbound], outbound: [outbound] }; +}; diff --git a/apps/worker/src/workflow-observability.ts b/apps/worker/src/workflow-observability.ts new file mode 100644 index 000000000..5c661169f --- /dev/null +++ b/apps/worker/src/workflow-observability.ts @@ -0,0 +1,137 @@ +import { existsSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import type { InjectedSinks } from "@temporalio/worker"; +import { sanitizeSpanAttributes, type SpanExporter } from "@space/runtime"; +import { + type WorkflowObservabilitySinks, + type WorkflowSpanRecordPayload +} from "./workflow-observability-interceptors.js"; + +/** Compiled `.js` in production, the `.ts` source when tests bundle the worker. */ +function resolveWorkflowObservabilityInterceptorPath(): string { + const compiled = fileURLToPath(new URL("./workflow-observability-interceptors.js", import.meta.url)); + if (existsSync(compiled)) return compiled; + const source = compiled.replace(/\.js$/, ".ts"); + return existsSync(source) ? source : compiled; +} + +export const workflowObservabilityInterceptorPath = resolveWorkflowObservabilityInterceptorPath(); + +export interface ActiveWorkflowTraceState { + spanId: string; + traceparent: string; + correlation: Record; + startedAtMs: number; +} + +const activeWorkflows = new Map(); +const MAX_ACTIVE_WORKFLOWS = 4096; + +function workflowKey(workflowId: string, runId: string): string { + return `${workflowId}\u0000${runId}`; +} + +export function rememberActiveWorkflowTrace( + workflowId: string, + runId: string, + state: ActiveWorkflowTraceState +): void { + const key = workflowKey(workflowId, runId); + activeWorkflows.delete(key); + activeWorkflows.set(key, state); + while (activeWorkflows.size > MAX_ACTIVE_WORKFLOWS) { + const oldest = activeWorkflows.keys().next().value; + if (oldest === undefined) break; + activeWorkflows.delete(oldest); + } +} + +export function lookupActiveWorkflowTrace( + workflowId: string | null | undefined, + runId: string | null | undefined +): ActiveWorkflowTraceState | null { + if (!workflowId || !runId) return null; + return activeWorkflows.get(workflowKey(workflowId, runId)) ?? null; +} + +export interface WorkflowObservabilityOptions { + spans?: SpanExporter | null; + now?: () => number; +} + +export function recordWorkflowSpan( + payload: WorkflowSpanRecordPayload, + options: WorkflowObservabilityOptions & { startedAtMs?: number } = {} +): void { + const spans = options.spans; + if (!spans) return; + const now = options.now ?? Date.now; + const finishedAtMs = now(); + const key = workflowKey(payload.workflowId, payload.runId); + const existing = activeWorkflows.get(key); + const startedAtMs = options.startedAtMs ?? existing?.startedAtMs ?? finishedAtMs; + const durationMs = Math.max(0, finishedAtMs - startedAtMs); + + const rawAttributes: Record = { + ...payload.correlation, + "space.workflow_type": payload.workflowType, + "space.workflow_id": payload.workflowId, + "space.run_id": payload.runId, + "space.outcome": payload.outcome, + "space.duration_ms": durationMs + }; + if (payload.parentWorkflowId) rawAttributes["space.parent_workflow_id"] = payload.parentWorkflowId; + if (payload.parentRunId) rawAttributes["space.parent_run_id"] = payload.parentRunId; + if (payload.continuedFromRunId) rawAttributes["space.continued_from_run_id"] = payload.continuedFromRunId; + if (payload.reasonCode) rawAttributes["space.reason_code"] = payload.reasonCode; + if (payload.category) rawAttributes["space.category"] = payload.category; + + spans.record({ + traceId: payload.traceId, + spanId: payload.spanId, + parentSpanId: payload.parentSpanId, + name: `workflow ${payload.workflowType}`, + startTimeUnixNano: BigInt(startedAtMs) * 1_000_000n, + endTimeUnixNano: BigInt(startedAtMs + durationMs) * 1_000_000n, + attributes: sanitizeSpanAttributes(rawAttributes), + error: payload.outcome === "FAILED", + kind: 1, + ...(payload.links.length ? { links: payload.links } : {}) + }); +} + +/** + * Temporal worker sinks for deterministic workflow span export. + * Both functions set `callDuringReplay: false` so workflow replay never emits duplicate spans. + */ +export function createWorkflowObservabilitySinks( + options: WorkflowObservabilityOptions = {} +): InjectedSinks { + const now = options.now ?? Date.now; + return { + workflowObservability: { + workflowStarted: { + callDuringReplay: false, + fn(info, spanId, traceparent, correlation) { + const runStartMs = info.runStartTime instanceof Date ? info.runStartTime.getTime() : now(); + rememberActiveWorkflowTrace(info.workflowId, info.runId, { + spanId, + traceparent, + correlation: correlation ?? {}, + startedAtMs: Number.isFinite(runStartMs) ? Math.min(runStartMs, now()) : now() + }); + } + }, + workflowFinished: { + callDuringReplay: false, + fn(info, payload) { + const existing = lookupActiveWorkflowTrace(info.workflowId, info.runId); + recordWorkflowSpan(payload, { + ...options, + ...(existing ? { startedAtMs: existing.startedAtMs } : {}) + }); + } + } + } + }; +} diff --git a/apps/worker/tests/activity-observability.test.ts b/apps/worker/tests/activity-observability.test.ts index 654bf7962..4390fa5f5 100644 --- a/apps/worker/tests/activity-observability.test.ts +++ b/apps/worker/tests/activity-observability.test.ts @@ -182,11 +182,13 @@ describe("worker metrics endpoint", () => { }); }); -it("registers the activity interceptor on every Temporal worker, including both memory workers", async () => { +it("registers the activity and workflow observability interceptors and sinks on every Temporal worker, including both memory workers", async () => { const { readFile } = await import("node:fs/promises"); for (const entry of ["worker.ts", "memory-maintenance-worker.ts", "memory-mutation-worker.ts"]) { const source = await readFile(new URL(`../src/${entry}`, import.meta.url), "utf8"); - expect(source, entry).toMatch(/interceptors: \{ activity: \[createActivityObservabilityInterceptors\(\{ spans \}\)\] \}/); + expect(source, entry).toMatch(/activity:\s*\[createActivityObservabilityInterceptors\(\{\s*spans\s*\}\)\]/); + expect(source, entry).toMatch(/workflowModules:\s*\[workflowObservabilityInterceptorPath\]/); + expect(source, entry).toMatch(/sinks:\s*createWorkflowObservabilitySinks\(\{\s*spans\s*\}\)/); } }); diff --git a/apps/worker/tests/otlp-probe-workflows.ts b/apps/worker/tests/otlp-probe-workflows.ts new file mode 100644 index 000000000..f2ac504ac --- /dev/null +++ b/apps/worker/tests/otlp-probe-workflows.ts @@ -0,0 +1,46 @@ +import { continueAsNew, executeChild, proxyActivities } from "@temporalio/workflow"; + +export interface OtlpProbeInput { + traceparent: string; + traceId: string; + prompt?: string; +} + +const flaky = proxyActivities<{ otlpFlakyActivity(): Promise }>({ + startToCloseTimeout: "20 seconds", + retry: { maximumAttempts: 2, initialInterval: "1 millisecond", backoffCoefficient: 1 } +}); + +const once = proxyActivities<{ otlpOnceActivity(label: string): Promise }>({ + startToCloseTimeout: "20 seconds", + retry: { maximumAttempts: 1 } +}); + +/** Parent workflow: one retried activity, then a child workflow on the same trace. */ +export async function otlpParentWorkflow(input: OtlpProbeInput): Promise { + await flaky.otlpFlakyActivity(); + return executeChild(otlpChildWorkflow, { + workflowId: `otlp-child:${input.traceId}`, + args: [input] + }); +} + +export async function otlpChildWorkflow(input: OtlpProbeInput): Promise { + return once.otlpOnceActivity("child"); +} + +/** First run continues as new; the next run links back to this one. */ +export async function otlpContinueWorkflow(input: OtlpProbeInput & { remaining: number }): Promise { + if (input.remaining > 0) { + await continueAsNew({ ...input, remaining: input.remaining - 1 }); + } + await once.otlpOnceActivity("continued"); + return "done"; +} + +/** Background continuation: own W3C trace, span link to the caller's traceparent. */ +export async function otlpBackgroundWorkflow( + input: OtlpProbeInput & { roomAgentContinuation: { sourceRunId: string; generation: number } } +): Promise { + return once.otlpOnceActivity("background"); +} diff --git a/apps/worker/tests/workflow-otlp-observability.test.ts b/apps/worker/tests/workflow-otlp-observability.test.ts new file mode 100644 index 000000000..003625e2d --- /dev/null +++ b/apps/worker/tests/workflow-otlp-observability.test.ts @@ -0,0 +1,578 @@ +import { createServer, type IncomingMessage, type ServerResponse } from "node:http"; +import { fileURLToPath } from "node:url"; +import { afterEach, describe, expect, it } from "vitest"; +import { defaultPayloadConverter, Worker } from "@temporalio/worker"; +import { TestWorkflowEnvironment } from "@temporalio/testing"; +import { + formatTraceparent, + parseTraceparent +} from "@space/contracts"; +import { + ALLOWED_SPAN_ATTRIBUTE_KEYS, + createOtlpSpanExporter, + sanitizeSpanAttributes, + type SpanExporter +} from "@space/runtime"; +import { + createActivityObservabilityInterceptors, + noteActivityFailureReason, + observeActivityExecution +} from "../src/activity-observability.js"; +import { + createWorkflowObservabilitySinks, + recordWorkflowSpan, + rememberActiveWorkflowTrace, + workflowObservabilityInterceptorPath +} from "../src/workflow-observability.js"; +import { + continuationSpanId, + extractWorkflowInputMetadata, + WORKFLOW_CONTINUED_TRACEPARENT_HEADER, + WORKFLOW_TRACEPARENT_HEADER, + workflowSpanId +} from "../src/workflow-observability-interceptors.js"; + +interface OtlpJsonAttribute { + key: string; + value: { stringValue?: string; intValue?: string }; +} + +interface OtlpJsonLink { + traceId: string; + spanId: string; + attributes?: OtlpJsonAttribute[]; +} + +interface OtlpJsonSpan { + traceId: string; + spanId: string; + parentSpanId?: string; + name: string; + kind: number; + startTimeUnixNano: string; + endTimeUnixNano: string; + attributes: OtlpJsonAttribute[]; + links?: OtlpJsonLink[]; + status: { code: number }; +} + +function attrMap(attrs: readonly OtlpJsonAttribute[] | undefined): Record { + const map: Record = {}; + for (const item of attrs ?? []) { + if (item.value.intValue !== undefined) map[item.key] = Number(item.value.intValue); + else if (item.value.stringValue !== undefined) map[item.key] = item.value.stringValue; + } + return map; +} + +async function startFakeOtlpCollector() { + const batches: Array<{ serviceName: string; spans: OtlpJsonSpan[] }> = []; + const rawPayloads: string[] = []; + const server = createServer((req: IncomingMessage, res: ServerResponse) => { + if (req.method === "POST" && req.url === "/v1/traces") { + let body = ""; + req.on("data", (chunk) => { body += String(chunk); }); + req.on("end", () => { + rawPayloads.push(body); + const parsed = JSON.parse(body) as { + resourceSpans?: Array<{ + resource?: { attributes?: OtlpJsonAttribute[] }; + scopeSpans?: Array<{ spans?: OtlpJsonSpan[] }>; + }>; + }; + for (const rs of parsed.resourceSpans ?? []) { + const serviceName = String(attrMap(rs.resource?.attributes)["service.name"] ?? ""); + for (const ss of rs.scopeSpans ?? []) { + batches.push({ serviceName, spans: ss.spans ?? [] }); + } + } + res.writeHead(200, { "content-type": "application/json" }); + res.end("{}"); + }); + return; + } + res.writeHead(404); + res.end(); + }); + + await new Promise((resolve) => server.listen(0, "127.0.0.1", () => resolve())); + const address = server.address(); + if (!address || typeof address === "string") throw new Error("Failed to bind fake OTLP collector"); + const endpoint = `http://127.0.0.1:${address.port}`; + + return { + endpoint, + batches, + rawPayloads, + allSpans: () => batches.flatMap((b) => b.spans), + close: () => new Promise((resolve) => server.close(() => resolve())) + }; +} + +const activeExporters: SpanExporter[] = []; +afterEach(async () => { + await Promise.all(activeExporters.splice(0).map((e) => e.close())); +}); + +describe("OTLP workflow and activity trace tree (#61)", () => { + const w3cTraceId = "4bf92f3577b34da6a3ce929d0e0e4736"; + const apiSpanId = "00f067aa0ba902b7"; + const apiTraceparent = formatTraceparent(w3cTraceId, apiSpanId, "01"); + const sensitivePrompt = "PRIVATE PROMPT sk-live-secret-token-999 /Users/operator/secret.txt https://internal.local/secret?key=1"; + + it("enforces the strict attribute allowlist and strips prompts, paths, URLs, error messages and secrets", () => { + const dirty: Record = { + "space.trace_id": "req:abcdefghijkl", + "space.workflow_type": "codexAppServerTurnWorkflow", + "space.activity_type": "runCodexAppServerTurn", + "space.attempt": 2, + "space.outcome": "FAILED", + "space.reason_code": "CODEX_APP_SERVER_TURN_TIMEOUT", + "space.category": "TIMEOUT", + "space.duration_ms": 145, + "space.room_id": "room:alpha", + "space.pane_id": "pane:beta", + "space.mission_id": "mission:gamma", + "prompt": sensitivePrompt, + "error.message": "Connection refused to 10.0.0.12 with sk-live-secret", + "file.path": "/Users/operator/secret.txt", + "url.full": "https://internal.local/secret?key=1", + "space.agent_run_id": sensitivePrompt + }; + + const cleaned = sanitizeSpanAttributes(dirty); + expect(Object.keys(cleaned).every((k) => ALLOWED_SPAN_ATTRIBUTE_KEYS.has(k))).toBe(true); + expect(cleaned).toEqual({ + "space.trace_id": "req:abcdefghijkl", + "space.workflow_type": "codexAppServerTurnWorkflow", + "space.activity_type": "runCodexAppServerTurn", + "space.attempt": 2, + "space.outcome": "FAILED", + "space.reason_code": "CODEX_APP_SERVER_TURN_TIMEOUT", + "space.category": "TIMEOUT", + "space.duration_ms": 145, + "space.room_id": "room:alpha", + "space.pane_id": "pane:beta", + "space.mission_id": "mission:gamma" + }); + expect(JSON.stringify(cleaned)).not.toMatch(/PRIVATE PROMPT|sk-live|secret\.txt|internal\.local/); + }); + + it("connects API SERVER span -> workflow span -> retrying activity attempts -> child workflow span in one OTLP trace", async () => { + const collector = await startFakeOtlpCollector(); + try { + const apiExporter = createOtlpSpanExporter({ + endpoint: collector.endpoint, + serviceName: "space-api", + flushIntervalMs: 60_000 + }); + const workerExporter = createOtlpSpanExporter({ + endpoint: collector.endpoint, + serviceName: "space-worker", + flushIntervalMs: 60_000 + }); + activeExporters.push(apiExporter, workerExporter); + + // 1. API records SERVER span + apiExporter.record({ + traceId: w3cTraceId, + spanId: apiSpanId, + parentSpanId: null, + name: "POST /api/turns", + startTimeUnixNano: 1_700_000_000_000_000_000n, + endTimeUnixNano: 1_700_000_000_050_000_000n, + attributes: { + "http.request.method": "POST", + "http.route": "/api/turns", + "http.response.status_code": 200, + "space.request_id": "req:turn00000001" + }, + error: false, + kind: 2 + }); + + // 2. Parent workflow (`memoryMaintenanceWorkflow`) starts under the API span's traceparent + const parentWfId = "space-memory-maintenance:2026-10-28"; + const parentRunId = "run-parent-1"; + const parentWfSpanId = workflowSpanId(parentWfId, parentRunId); + const parentWfTraceparent = formatTraceparent(w3cTraceId, parentWfSpanId, "01"); + + const sinks = createWorkflowObservabilitySinks({ spans: workerExporter, now: () => 1_700_000_000_100 }); + sinks.workflowObservability.workflowStarted.fn( + { workflowId: parentWfId, runId: parentRunId, runStartTime: new Date(1_700_000_000_050) } as never, + parentWfSpanId, + parentWfTraceparent, + { "space.trace_id": "req:turn00000001", "space.room_id": "room:1" } + ); + + // 3. Activity attempt 1 (fails with TIMEOUT) and attempt 2 (succeeds) run as children of parentWfSpanId + const wfHeaders = { + [WORKFLOW_TRACEPARENT_HEADER]: defaultPayloadConverter.toPayload(parentWfTraceparent) + }; + const actInfo = (attempt: number) => ({ + activityType: "refreshMemoryGraphSnapshot", + activityId: "act-1", + attempt, + workflowType: "memoryMaintenanceWorkflow", + workflowExecution: { workflowId: parentWfId, runId: parentRunId }, + currentAttemptScheduledTimestampMs: 1_700_000_000_060 + }); + + await observeActivityExecution( + actInfo(1), + [{ scheduledAt: "2026-10-28T00:00:00.000Z", traceId: "req:turn00000001", prompt: sensitivePrompt }], + async () => { + noteActivityFailureReason("CODEX_APP_SERVER_TURN_TIMEOUT"); + return { status: "FAILED" }; + }, + { spans: workerExporter, headers: wfHeaders, now: () => 1_700_000_000_080 } + ); + + await observeActivityExecution( + actInfo(2), + [{ scheduledAt: "2026-10-28T00:00:00.000Z", traceId: "req:turn00000001", prompt: sensitivePrompt }], + async () => ({ status: "REFRESHED" }), + { spans: workerExporter, headers: wfHeaders, now: () => 1_700_000_000_095 } + ); + + // 4. Child workflow (`memoryConsolidationWorkflow`) nests under `parentWfSpanId` + const childWfId = "space-memory-consolidation:scheduled:2026-10-28"; + const childRunId = "run-child-1"; + const childWfSpanId = workflowSpanId(childWfId, childRunId); + sinks.workflowObservability.workflowStarted.fn( + { workflowId: childWfId, runId: childRunId, runStartTime: new Date(1_700_000_000_096) } as never, + childWfSpanId, + formatTraceparent(w3cTraceId, childWfSpanId, "01"), + { "space.trace_id": "req:turn00000001" } + ); + sinks.workflowObservability.workflowFinished.fn( + { workflowId: childWfId, runId: childRunId } as never, + { + traceId: w3cTraceId, + spanId: childWfSpanId, + parentSpanId: parentWfSpanId, + workflowType: "memoryConsolidationWorkflow", + workflowId: childWfId, + runId: childRunId, + parentWorkflowId: parentWfId, + parentRunId: parentRunId, + continuedFromRunId: null, + outcome: "COMPLETED", + reasonCode: null, + category: null, + correlation: { "space.trace_id": "req:turn00000001" }, + links: [] + } + ); + + // 5. Parent workflow finishes + sinks.workflowObservability.workflowFinished.fn( + { workflowId: parentWfId, runId: parentRunId } as never, + { + traceId: w3cTraceId, + spanId: parentWfSpanId, + parentSpanId: apiSpanId, + workflowType: "memoryMaintenanceWorkflow", + workflowId: parentWfId, + runId: parentRunId, + parentWorkflowId: null, + parentRunId: null, + continuedFromRunId: null, + outcome: "COMPLETED", + reasonCode: null, + category: null, + correlation: { "space.trace_id": "req:turn00000001", "space.room_id": "room:1" }, + links: [] + } + ); + + await apiExporter.flush(); + await workerExporter.flush(); + + const spans = collector.allSpans(); + expect(spans).toHaveLength(5); + for (const s of spans) { + expect(s.traceId).toBe(w3cTraceId); + } + + const apiSpan = spans.find((s) => s.name === "POST /api/turns")!; + const parentWfSpan = spans.find((s) => s.name === "workflow memoryMaintenanceWorkflow")!; + const childWfSpan = spans.find((s) => s.name === "workflow memoryConsolidationWorkflow")!; + const activitySpans = spans.filter((s) => s.name === "activity refreshMemoryGraphSnapshot"); + + expect(apiSpan.parentSpanId).toBeUndefined(); + expect(parentWfSpan.parentSpanId).toBe(apiSpan.spanId); + expect(childWfSpan.parentSpanId).toBe(parentWfSpan.spanId); + expect(attrMap(childWfSpan.attributes)["space.parent_workflow_id"]).toBe(parentWfId); + + expect(activitySpans).toHaveLength(2); + const attempt1 = activitySpans.find((s) => attrMap(s.attributes)["space.attempt"] === 1)!; + const attempt2 = activitySpans.find((s) => attrMap(s.attributes)["space.attempt"] === 2)!; + + expect(attempt1.parentSpanId).toBe(parentWfSpan.spanId); + expect(attempt1.status.code).toBe(2); + expect(attrMap(attempt1.attributes)).toMatchObject({ + "space.activity_type": "refreshMemoryGraphSnapshot", + "space.attempt": 1, + "space.outcome": "FAILED_RESULT", + "space.reason_code": "CODEX_APP_SERVER_TURN_TIMEOUT", + "space.category": "TIMEOUT", + "space.trace_id": "req:turn00000001" + }); + + expect(attempt2.parentSpanId).toBe(parentWfSpan.spanId); + expect(attempt2.status.code).toBe(0); + expect(attrMap(attempt2.attributes)).toMatchObject({ + "space.activity_type": "refreshMemoryGraphSnapshot", + "space.attempt": 2, + "space.outcome": "COMPLETED", + "space.trace_id": "req:turn00000001" + }); + + expect(collector.rawPayloads.join("\n")).not.toMatch(/PRIVATE PROMPT|sk-live|secret\.txt|internal\.local/); + } finally { + await collector.close(); + } + }); + + it("attaches OTLP span links for continue-as-new runs and background continuations (trace:bg-*)", async () => { + const collector = await startFakeOtlpCollector(); + try { + const exporter = createOtlpSpanExporter({ + endpoint: collector.endpoint, + serviceName: "space-worker", + flushIntervalMs: 60_000 + }); + activeExporters.push(exporter); + + const prevRunId = "run-initial-01"; + const prevSpanId = workflowSpanId("dot-goal:dot_goal:1", prevRunId); + const sourceRunId = "agent_run:source-123"; + const bgSourceSpanId = continuationSpanId(sourceRunId); + + recordWorkflowSpan( + { + traceId: w3cTraceId, + spanId: workflowSpanId("dot-goal:dot_goal:1", "run-continued-02"), + parentSpanId: apiSpanId, + workflowType: "dotGoalWorkflow", + workflowId: "dot-goal:dot_goal:1", + runId: "run-continued-02", + parentWorkflowId: null, + parentRunId: null, + continuedFromRunId: prevRunId, + outcome: "COMPLETED", + reasonCode: null, + category: null, + correlation: { "space.goal_id": "dot_goal:1", "space.trace_id": "trace:bg-cont-01" }, + links: [ + { + traceId: w3cTraceId, + spanId: prevSpanId, + attributes: { + "space.link_kind": "CONTINUE_AS_NEW", + "space.continued_from_run_id": prevRunId + } + }, + { + traceId: w3cTraceId, + spanId: bgSourceSpanId, + attributes: { + "space.link_kind": "BACKGROUND_CONTINUATION", + "space.source_run_id": sourceRunId + } + } + ] + }, + { spans: exporter, startedAtMs: 1_000, now: () => 1_200 } + ); + + await exporter.flush(); + const [exported] = collector.allSpans(); + expect(exported).toBeDefined(); + expect(exported!.links).toHaveLength(2); + expect(exported!.links![0]!.spanId).toBe(prevSpanId); + expect(attrMap(exported!.links![0]!.attributes)).toEqual({ + "space.link_kind": "CONTINUE_AS_NEW", + "space.continued_from_run_id": prevRunId + }); + expect(exported!.links![1]!.spanId).toBe(bgSourceSpanId); + expect(attrMap(exported!.links![1]!.attributes)).toEqual({ + "space.link_kind": "BACKGROUND_CONTINUATION", + "space.source_run_id": sourceRunId + }); + } finally { + await collector.close(); + } + }); + + it("configures workflow sinks with callDuringReplay: false so replaying a workflow history emits zero spans", async () => { + const recorded: unknown[] = []; + const spans: SpanExporter = { + record: (span) => void recorded.push(span), + flush: async () => undefined, + close: async () => undefined, + stats: () => ({ exported: recorded.length, dropped: 0, failed: 0 }) + }; + const sinks = createWorkflowObservabilitySinks({ spans }); + expect(sinks.workflowObservability.workflowStarted.callDuringReplay).toBe(false); + expect(sinks.workflowObservability.workflowFinished.callDuringReplay).toBe(false); + + // Also verify extractWorkflowInputMetadata never leaks free-text prompts + const metadata = extractWorkflowInputMetadata([ + { + roomId: "room:replay", + paneId: "pane:replay", + traceId: "trace:bg-replay1", + traceparent: apiTraceparent, + prompt: sensitivePrompt, + roomAgentContinuation: { sourceRunId: "agent_run:orig1", generation: 1 } + } + ]); + expect(metadata.sourceRunId).toBe("agent_run:orig1"); + expect(metadata.fields.traceId).toBe("trace:bg-replay1"); + expect(JSON.stringify(metadata)).not.toContain("PRIVATE PROMPT"); + }); + + it("runs a real Temporal workflow with stub activities in @temporalio/testing and exports a connected trace to the local HTTP OTLP collector", async () => { + // createLocal uses the Temporal CLI dev server, which has an arm64 build. + // createTimeSkipping's Java test server does not run on Apple Silicon without Rosetta, + // and a skipped server must fail this test rather than pass it. + const testEnv = await TestWorkflowEnvironment.createLocal(); + const collector = await startFakeOtlpCollector(); + const workerExporter = createOtlpSpanExporter({ + endpoint: collector.endpoint, + serviceName: "space-worker", + flushIntervalMs: 60_000 + }); + activeExporters.push(workerExporter); + + try { + const workflowsPath = fileURLToPath(new URL("./otlp-probe-workflows.ts", import.meta.url)); + const interceptorPath = fileURLToPath(new URL("../src/workflow-observability-interceptors.ts", import.meta.url)); + let flakyAttempts = 0; + const activities = { + otlpFlakyActivity: async () => { + flakyAttempts += 1; + if (flakyAttempts === 1) { + throw Object.assign(new Error("transient failure sk-live-secret"), { code: "UPSTREAM_RETRYABLE_UNAVAILABLE" }); + } + return "ok"; + }, + otlpOnceActivity: async (label: string) => label + }; + + const taskQueue = "otlp-integration-test-queue"; + const worker = await Worker.create({ + connection: testEnv.nativeConnection, + taskQueue, + workflowsPath, + activities, + interceptors: { + activity: [createActivityObservabilityInterceptors({ spans: workerExporter })], + workflowModules: [interceptorPath] + }, + sinks: createWorkflowObservabilitySinks({ spans: workerExporter }) + }); + + const probeInput = { + traceparent: apiTraceparent, + traceId: "req:otlp00000001", + prompt: sensitivePrompt + }; + + const parentHandle = await worker.runUntil(async () => { + const parent = await testEnv.client.workflow.start("otlpParentWorkflow", { + taskQueue, + workflowId: "otlp-parent:req:otlp00000001", + args: [probeInput] + }); + const continued = await testEnv.client.workflow.start("otlpContinueWorkflow", { + taskQueue, + workflowId: "otlp-continue:req:otlp00000001", + args: [{ ...probeInput, remaining: 1 }] + }); + const background = await testEnv.client.workflow.start("otlpBackgroundWorkflow", { + taskQueue, + workflowId: "otlp-background:trace:bg-cont01", + args: [{ + ...probeInput, + traceId: "trace:bg-cont01", + roomAgentContinuation: { sourceRunId: "agent_run:source-123", generation: 1 } + }] + }); + await Promise.all([parent.result(), continued.result(), background.result()]); + return parent; + }); + + await workerExporter.flush(); + const spans = collector.allSpans(); + const parentWf = spans.find((s) => s.name === "workflow otlpParentWorkflow")!; + const childWf = spans.find((s) => s.name === "workflow otlpChildWorkflow")!; + const flakySpans = spans.filter((s) => s.name === "activity otlpFlakyActivity"); + const childActivity = spans.find((s) => s.name === "activity otlpOnceActivity" && attrMap(s.attributes)["space.workflow_type"] === "otlpChildWorkflow"); + + expect(parentWf).toBeDefined(); + expect(parentWf.traceId).toBe(w3cTraceId); + expect(parentWf.parentSpanId).toBe(apiSpanId); + + expect(childWf).toBeDefined(); + expect(childWf.traceId).toBe(w3cTraceId); + expect(childWf.parentSpanId).toBe(parentWf.spanId); + + expect(flakySpans).toHaveLength(2); + const failedAttempt = flakySpans.find((s) => attrMap(s.attributes)["space.attempt"] === 1)!; + const okAttempt = flakySpans.find((s) => attrMap(s.attributes)["space.attempt"] === 2)!; + expect(failedAttempt.parentSpanId).toBe(parentWf.spanId); + expect(failedAttempt.traceId).toBe(w3cTraceId); + expect(attrMap(failedAttempt.attributes)["space.outcome"]).toBe("FAILED"); + expect(attrMap(failedAttempt.attributes)["space.reason_code"]).toBe("UPSTREAM_RETRYABLE_UNAVAILABLE"); + expect(okAttempt.parentSpanId).toBe(parentWf.spanId); + expect(attrMap(okAttempt.attributes)["space.outcome"]).toBe("COMPLETED"); + + expect(childActivity).toBeDefined(); + expect(childActivity!.traceId).toBe(w3cTraceId); + expect(childActivity!.parentSpanId).toBe(childWf.spanId); + + const continueSpans = spans.filter((s) => s.name === "workflow otlpContinueWorkflow"); + expect(continueSpans).toHaveLength(2); + for (const span of continueSpans) { + expect(span.traceId).toBe(w3cTraceId); + expect(span.parentSpanId).toBe(apiSpanId); + } + const continued = continueSpans.find((s) => (s.links ?? []).some((link) => attrMap(link.attributes)["space.link_kind"] === "CONTINUE_AS_NEW")); + expect(continued).toBeDefined(); + const continueLink = continued!.links!.find((link) => attrMap(link.attributes)["space.link_kind"] === "CONTINUE_AS_NEW")!; + expect(continueLink.traceId).toBe(w3cTraceId); + expect(continueSpans.some((s) => s.spanId === continueLink.spanId)).toBe(true); + + const background = spans.find((s) => s.name === "workflow otlpBackgroundWorkflow"); + expect(background).toBeDefined(); + expect(background!.traceId).not.toBe(w3cTraceId); + const bgLink = background!.links?.find((link) => attrMap(link.attributes)["space.link_kind"] === "BACKGROUND_CONTINUATION"); + expect(bgLink).toBeDefined(); + expect(bgLink!.traceId).toBe(w3cTraceId); + expect(bgLink!.spanId).toBe(apiSpanId); + expect(attrMap(bgLink!.attributes)["space.source_run_id"]).toBe("agent_run:source-123"); + const bgActivity = spans.find((s) => s.name === "activity otlpOnceActivity" && s.traceId === background!.traceId); + expect(bgActivity?.parentSpanId).toBe(background!.spanId); + + const countBeforeReplay = collector.allSpans().length; + const history = await parentHandle.fetchHistory(); + await Worker.runReplayHistory( + { + workflowsPath, + interceptors: { workflowModules: [interceptorPath] }, + sinks: createWorkflowObservabilitySinks({ spans: workerExporter }) + }, + history + ); + await workerExporter.flush(); + expect(collector.allSpans().length).toBe(countBeforeReplay); + expect(collector.rawPayloads.join("\n")).not.toMatch(/PRIVATE PROMPT|sk-live|secret/); + } finally { + await collector.close(); + await testEnv.teardown(); + } + }, 180_000); +}); diff --git a/docs/configuration-reference.md b/docs/configuration-reference.md index e432af4c6..bec51405c 100644 --- a/docs/configuration-reference.md +++ b/docs/configuration-reference.md @@ -52,3 +52,10 @@ background work, and is hidden in the web app. Public installs upgraded from 1.1.x that used Job Copilot, the games, channels, Telegram or the desktop routes set the modules they keep, for example `SPACE_OPTIONAL_MODULES=job-copilot,games`. + +## Observability and OTLP trace export + +`SPACE_OTEL_EXPORTER_OTLP_ENDPOINT` (unset by default) enables optional zero-dependency OTLP/HTTP JSON trace export (`/v1/traces`) across both `space-api` and all Temporal workers (`space-worker`, `space-memory-maintenance-worker`, `space-memory-mutation-worker`). When set to an `http:` or `https:` collector base URL: +- `space-api` exports one `SERVER` span per HTTP request and propagates its W3C `traceparent` into Temporal workflow inputs and memos. +- Every Temporal worker exports one `INTERNAL` workflow span per workflow run, parented to the API request span or to the parent workflow span for a child workflow. Continue-as-new keeps that parent and adds a span link to the previous run. A background continuation (`trace:bg-*`) starts its own W3C trace and links to the caller's span. Export goes through replay-safe Temporal interceptors and sinks (`callDuringReplay: false`). Each activity attempt is an `INTERNAL` child span with allowlisted identifiers, attempt number, outcome, and stable `reasonCode` / `category`. Unset, the workers export nothing. + diff --git a/docs/quality/observability.md b/docs/quality/observability.md index d5cd7080e..d2622f7e4 100644 --- a/docs/quality/observability.md +++ b/docs/quality/observability.md @@ -220,8 +220,14 @@ SERVER span per API request when `SPACE_OTEL_EXPORTER_OTLP_ENDPOINT` is set (no OpenTelemetry dependency; `packages/runtime/src/otlp-span-exporter.ts`). Since 2026-10-16 the worker and both memory workers use the same exporter (services `space-worker`, `space-memory-maintenance-worker`, `space-memory-mutation-worker`): -one INTERNAL span per activity whose input carries a `traceparent`, as a child of -that span, with the activity type, outcome and attempt only. +one INTERNAL workflow span per workflow execution (parented to the propagated API +`traceparent`, or to the parent workflow span for a child workflow). A continue-as-new +run keeps that parent and adds an OTLP span link to the run it continues. A +background continuation (`trace:bg-*`) starts its own W3C trace and adds a span +link to the caller's span. Spans leave the sandbox through Temporal sinks with +`callDuringReplay: false`, so replay emits no duplicates. Each activity attempt is +an INTERNAL child of its workflow span, with the allowlisted correlation ids, +activity type, attempt, outcome, and `reasonCode` / `category`. - **API:** a valid inbound `traceparent` is continued (same trace ID, new span). An invalid or missing header gets a new trace ID. Every API log line carries `w3cTraceId` (Fastify `childLoggerFactory`). @@ -233,28 +239,24 @@ that span, with the activity type, outcome and attempt only. - **Worker:** worker logs read the trace ID from the activity input. Internal room-action calls from the worker send a child `traceparent`, so the internal API request joins the same trace. -- **Workflow code is unchanged.** Correlation is done in the API starter - (input/memo) and in an activity interceptor. The workflow bundle only gains the - optional contract field. +- **Workflow functions do no export I/O.** Correlation still enters through the API + starter (input/memo). Span export is a workflow interceptor + (`workflow-observability-interceptors.ts`) plus an activity interceptor. The + interceptor only reads workflow info and writes trace headers; spans leave the + sandbox through sinks with `callDuringReplay: false`. Unset + `SPACE_OTEL_EXPORTER_OTLP_ENDPOINT` exports nothing. ### Replay compatibility -No file under `apps/worker/src` that the workflow bundle imports was changed -(`workflows.ts`, `room-supervisor-state.ts`, `turn-runtime-policy.ts`, -`dot-goal-loop.ts`). The `agent-turns` workflow bundle was built for the base -commit and for this change and compared after normalizing paths and webpack -module numbers. The only differences are in `@space/contracts`: -- the new `observability` module; -- the optional `traceparent` turn-input field; -- the optional file search and readiness fields. - -A history without `traceparent` parses exactly as before. Activity inputs are -not part of Temporal's determinism check. - -The read-only live replay (`node scripts/temporal-replay-check.mjs --limit 30`) -could not be run from the agent sandbox that made this change. Its network -namespace has no route to the Temporal container (`172.18.0.5:7233`), and -`127.0.0.1:7233` refuses connections. Run it before deploying this change. +Workflow bodies (`workflows.ts`, `room-supervisor-state.ts`, `turn-runtime-policy.ts`, +`dot-goal-loop.ts`) are not instrumented. The worker loads +`workflow-observability-interceptors.ts` as a workflow module. Span ids are a pure +function of workflow id and run id (no clocks or randomness in the sandbox). Sinks +do not run during replay, so replaying a history emits no spans. +`apps/worker/tests/workflow-otlp-observability.test.ts` runs a workflow in +`@temporalio/testing` and replays its history to prove that. A history without +`traceparent` parses as before. Activity inputs are not part of Temporal's +determinism check. ## Redaction policy @@ -292,6 +294,7 @@ No credential or transcript export was added. | `apps/api/src/__tests__/files-search-errors.test.ts` | `EMPTY_RESULT`/`OK` and each failure code | | `apps/api/tests/files-search-routes.test.ts` | The HTTP error envelope | | `apps/worker/tests/activity-observability.test.ts` | Interceptor records, outcomes, categories, metrics, loopback endpoint | +| `apps/worker/tests/workflow-otlp-observability.test.ts` | Allowlisted workflow and activity spans, parent/child trace, continue-as-new and background links, Temporal test server, replay emits no spans | | `apps/api/tests/observability-fault-injection.test.ts` | Four injected executor faults and one Temporal start failure, end to end | ## Not covered @@ -341,9 +344,10 @@ No credential or transcript export was added. the lookup also returns the Space-native agent runs (`space_agent_runs`) of the trace's workflows, joined by workflow id, with their status and error code, and names a failed run's code as the cause when no event carries one. -- **Hosts emit no spans.** Since 2026-10-16 the OTLP exporter sends API SERVER - spans and worker activity spans (the worker and both memory workers); the CLI - and Browser Hosts still join traces through log lines only. +- **CLI and Browser Hosts emit no spans.** With `SPACE_OTEL_EXPORTER_OTLP_ENDPOINT` + set, the API exports its SERVER span and every Temporal worker (main, memory + maintenance, memory mutation) exports the workflow span and one span per activity + attempt on that same trace. Host processes still join traces through log lines only. - **Recovery counters are partial.** Recovery counters exist for plan model retries, CLI terminal reconnects and worker activity retries only. - **Worker endpoints are not scraped yet.** Stable's `deploy/space-worker.service` diff --git a/docs/quality/screenshots/otlp-api-to-activity-trace.png b/docs/quality/screenshots/otlp-api-to-activity-trace.png new file mode 100644 index 0000000000000000000000000000000000000000..62123dd1a9330ae045a3bdbe36fa17ee041eaf30 GIT binary patch literal 155311 zcmd42s>Vo29vO)-!V1XZ?}N!D#h_eyc|rY&)=(N#5mhuCZmU#=FKwe+?@T{LkA_}b zKVrk&enh@20hYgF#wPX_uZ}!f#v_QqO*L`L@^$~SO!$k1GQ22f`D{tP#R{sd9678) zw;7PCJ!ev1vJYn|_cb)}Nw@i9sBo*l*5YNL!_#*GiK+6f4Ds2xNZj#Q*Uw(gmzR5> zQ056~7U@gcxVt)8uW&n<4trASIVRCbv<)*KF&Ij(uhz{U;kQi5!}Z1;s;g|LUR4DT z%QD8yvPy+g@mKI)7i@x(EO@_Z-CUq&ANu}w^Z)!&NNT}^{J8G(<45aZr4*Ybl0 zPB+ClbZ|431;Lu7c*Rop62aIdnDuUu+Kc>X%(gH~f@-7n6 zdnCCp;+h^AhncUx;mQyU?l`+Uxx2r3`O4j$Sv^NSk_cELuF%BVcdv zEyhHx#Kf@L^RT$u^St%xA9-=@Kl0>*ex&lmw7mFFPrqGj8f!0IU7X$BT;1v|l~9Ja z)X_qr(?4Fh%6RKghEXJ;kq=xL8D#_=$@gm}qnaMp&Uaw7kyAEfv`$o$mY z=xlF)s0w92e^+&Lb#pzqzP!FYuU0)*l}UGlvY|R=7z|5tCbnhv*0=r8Ca1qscq+CU zk!AtVYU#gyYy5n5Oks?*;v>kyNlz`v#@v+SBn4Emx({vN2wm z{yRqccvw^aUHd8QKmcbrKr-qQN+ z(TzB?+nby8h4?Kke{SyiWB#z1d}1t6HFZ+xI>uewamemESYnA>Y-Dm@7&<0Q%qn@g z_P>50zJIYdXr@{2Q4TT`(29X%wH)&Y!niyh<+g>F7}Q^Y*i1cwBxha4HdRZR`?%>^ z&g6ADHVX?0aWy$N`+)F#Va8=v2?^}vY_8*}%nBF0N^ceV&;RbEA{nu5nR2YSVue$>8qPZ; zB%+s9u5J8+W@bA&dEQt^lVLG(1z@Lm#SG)GK6mOqu`Rbb%*<6oL*p)P)%h9}1tP-C zRZ@1X5cO@k$i+Fy$^#u<0^;fBcupqunG35d^@m52!-|nn1qJ32`A0)wSgMA)HcCN= zxBi=X^P5|bDt>gh(f^q8Tj(9uWldd8@Bm^(Y-6T@wgB4X;Y|)kR52tm+dP-H_v#`1 zk!|gHajBFxZ|QC`l$ruNlx!@}tSYwYT@L1&JxLD1V5gnUO&5#h%1Spilc*9MHrJ@V zmLs#^WJ9U9qPKrBqY6Ika1Y9V{CD!(-y<9F2;4X*Y2y)xlsI2K*Ri&#pi<9NE zh8p5>*pJg9&RLn03q#^a`O-_QhHQI<79r-QmV0W5+;(FvL)z!Q+4qs@Av4YVA7;uA zSt?{AJ{l)w^EEp}cX#AF;0sjF`_6XvcLJW3MRGfXT3&AzZ2j+L)#xAWot>YGN_*d5 zV8N4yhgf@Mse&JzyL7bgY8YN1RUjFw%&CHYCvsZsmVLnqlF(KCdU&uWE$@!-N8)OA z3;3fcsbWO%Kiy;^16E5^>YOei*F4;KfBx}5y{e+w6-{_}!6=M`2(!irZJ9@ZeG^S6rB-Zx6Kb5&=RNIf+#!<8-!nea2R2qa+Em!Du^IWxKOc^dgRW{_W= zLrb0ZTA7R7nt@Rraarl0-{b85z1J*nF)Lb(zQQUkc?+kT9kwlKrWK_;<=~jwPsjh< zn14R;ed?7&QMz_d0KFGTIew0>DRe%*MEZWazJ0j6wOy{sRJEjN0+HBpqbzU9`JeH? zGiHz=#E8Ozg4MxJl*g?GNpofQU(Jjb@_Q{9KVH>t}!WI2TiC(H`z(%>w^D|az zTVCET?uhpG9&BVfC1SzI?Qr=E74wB)x5FNVUnxZk`(8og-19& zT*)=mB#t>+=Ms*aXtxY2xJ+h8d1R{tu?{zzWV*fi*;Nh+65>;i)? zrA-6!EAp!U2WBE)lLOO)_v7*tKnefMX-HEh$YW$JM!* zfoUvhBqcAd&onU@Xl*EE+Egc*#bIC96xom%7{MPCW0;tvdu&xSHayN!m4j~Xj55mT z`o~{+{J$3Eu%EhWST9+p^5Z2YmaDZmOji@e2RmO-LGE1b9?%Q#_Qnc43~klOAjZTm zGbu6)5qU%AIb;(Z)|ZkrW+*mY8!imV5cH_l=<19hb*-**{M6}mF7czRPBNy-&1D;L zO6tocJ~Xh<6yPNm;Gu?gXfetyYCCJ|jez$xXh7*talZ{UX|S*`TtST+Ix5O6G=^>M z^*P|l(sqedt=~*|ZgcT?w270=|INlCgWDE#K+G2go^1OR<`IeoPg#PNq z5rbcXxGKjW&>}ew-c0B8_?FOmVmZnw(k<<2W=rMRY2WML;-g&Z#he$;oGKQj5w*;? z9HG!2dF_F%WLQQ(qFzFhtstd((`cC;cx<|Rd|-KIcD6-8Lr%`x$X?t;+_Pr7^b3)$ z>Nk)uYv_N?5(7}j%V&~m@pS68ye_YyjJ?|dJv-HMKhD;Vr;0`@Tr3%A1#}#>}x9(G=Y8uiDytur94N!wa?x00WhA+(Ax&q;i*E^t5Cm zX=VV)ve7sVv{89f=F(3%F69sTZx=YE2{whsC!{sCc_nb2V$}zQ>pH zpXq5pEfzA8s_AB$Q3R9(bmLm~`qBm77Yq+Cg@JJ_vH0RzGaYYj#J>D#A$=YyTcQn9 zELow}f;QJOmerT4Gw@pr8n6H_9vZImva|CQ={o$V>zeDSN`|P5C1w0q^%ha0P64W| z|E$Mk1mD8qcna62svN7U!j8z}EUfQd8yjm9{u&(4|HdsV8j?f1T?aoCc3TeF>cUXC zN*>3279W@N-^$I)f}8BQXBjWvlVR|bZ{oY$c-qu-<#vqWrB1SGGE%dZV?;o7DoHM{ zIP74xf9%%K#{DzSE4s=k+QmTK^d-f(#Qv?`kXh-Dr1=92tidx*T>)P!iJe7V0APbN`mAhciXNEK*TWn|)(V*rXl{HU%r9@2++$Az zrWYeiL)!Dw%Qfs5`k4x3h~H^#q^2%;bOeJ4_HD$@;Jna)sUX#u_t@wR?gqQwCBY+^ zLFB4~nJb-R84tw6d+F}v%uI~tNKi3aqm%JiLxG=aYi_K^18ba4yzw)HpFipoGxN{l z*a3GTBg0iHUk<$9xF2Lrmme#g!=kHHBPYyQ7>aXdVa*C^)zvwi|oP6X2 zy80J-c`-YrU*X$EI)eDqC>wI|xB_1MMF5w=uAqLUmFt}Z+uQ>nSzNpII!kl9=7t3~ z{b;5~yeSihJ0kopFX}s8WR1r)Zz`B;m)L`y?u#>O1qBwSHcuw@Usx}db>6xx^ln_7 zqbd{_Dd&>5xPEAs1eESnWr`Gi>NGEwkZ)=kT2fi!^qW2viCYL&S5UB381*w;m`O-U zy1LsDr7g0wt|UI(xjMdpdp7f-;8e^WsGBxnnpo?$u=&;-B%@4}w+&8Jvd}cSu{Je0 zy{kvTx+D~?zE5VJJK^)Nkv$pr30C!Iiyc#fXM8RFKQ18ex`%*_>O~W*TmNgbz{1RB zmrRkU_cJ)U>)Ceo?4k!9jB>sEv71fZHvxLRLzeNW{hd8)RJoRz@g>#{1T2ESHm~$? zI{wLGhoH0h^uDiN(W3EmB3O~m92mh!eZTCo_)U5C!}-wV6hYOB&t5)xap{QIqJ3(G?sl;*Hs1xsH# z_?%T^B?cK2VTLJOs0bW`6LQ5P^n5$Yip@Ypg5`XfM1eWt`BXWX2zNs@2hoXI)jydW5;7f7cF( zY^84i*GF&*OUx~%S%FrTaUJPx0g5_akDo*JGF3O|)Hz2<&A0aU7?bRoYR-phHjRXR zbUXNbxUAuKnZ0ya4E3nB;pZD4f?o2zWeH0>metT{6<*E|@!G?7;I(|5f9P0BYQMi6 zOBPj_D8=h)aG1NU67@`e+UC_dN$P+f+Gan4=d(QFE%)ATVUrD1dmI;Jsio!9JAk3l z<=b@LtW9Z~-5*9s0_t>+%C0;11<&0s{i1DnZJ6VsX!{KfR&j}K*5tEOx& zFcnjqvNNaJToPc}#eNHvpH7oiwuK+Z-9OT-BzZnm)-**Q7G2Trlc#Z&ySsY)&3LVl zJ_t5{I`qDCwI0-rl{~j>KV|16FOH6xYYyE5yy1!-1AgMa4e|`?pH*r;81Bi|_C3!l zi${i#@HwqzI`{)13ZuuQT%v}8KS4n95s1rb4s>jvEw;(KVL*w{%I_izP;MftS3xmz zZ6(!I3j>tgx9>{Y(IyAI_Ly-MW<2WZwKQ49AWCj?Anuu%x#02qvO1d%=cvoWwvB!W zXS7!8$FREiS`wKmpI*St3s|2N;2g8{N=+BkWe&j8!7*VPKpQ4Z+*#Lr(v{sP?7EI> ze2UGi+AMj0ao+kEpiIMO5Oap9Ih~h+wA#8zNxMZnG-1DzvaYh z8HlOQn&@|Tp5IfY@T-g0*8}}leh54b&+5vCLB`wgo)Rw!go$`3!m(5uCZ~SoLRh_Q z8!H;{IKSW1VJOH#mV7tMVaC14`bBHRhIzCnLcQq(j0y5Kj&ud)Q%Z2S+YY)=N%FdX zv~ri@mW7@LE3(Cp;_zaLTv^#+ikj)w(ae*pT*7@5ue}ZO@&4s+IY;a+`?9$Mnfj^; z8ChC-+TtgjMYdNr#7uW9*%!Z;f=0j204xCzyltLfyoJKvj;7ht>Dc{c^xTevLBw(E z0xbPrkE<1JI>afQ35jdFx%To7O8=F#ENEr%lL$T>KF6g*a)X}H@7|ke|1Av{KY0o2 z*zkp%qLNEi5uyo8a%Bu^uwlZJ_cJID3VMwf;l`uw!z}z~!gk@~AfnfhX$+#++zvq$ zf`OkYxtTc3`3s;SkQ>l~WP1-*RU4h^=NBTu{$y+6f zZ4ti!PH6I(|Dsf*3&uOsPrqC;NJ)Cf?N;N`4Xgb}j{|T9Z@hHxz4q*!3d|FCwiElZ z6q;Qx&5}di-@5OA+}riIntrW9@15$P>L=ftA8nZ#vUzARZNyd#qMUBl;B<+~?sERy z*Prpwqa54quJZBFU;{V()X{1GBVnI{#mPjAF@vZ^!J(c6YK8aV(iRyh^_?q_0NbNX zFrXd^-x2wI5LwkC#pZ6guYymC75XdalCFNnj_;n_=_TRC3b;Lm`4fhRAjzI zCQJ-pUE@TMQpnz}hq#0^@he_;#wninu`@Na=39!qU<0t_mSIb3xfgJ(E6Z~NDCrR? z$j)Lr{-eP2wbWAH7b--=hno1BHun?ag!tAzu)P@zI4124B7(zw=|!HMGHdA?Yn$3e zcdA6B*|0U>F6~d7%kCmgF#(N(2}F_U^0iW3_IhoP6wexnOYQAK_BYrI^bU(lcMA4p zcbZbOSr$@(BI%7%RkSD8I(;tsY>res37c&#OYqb1INLd#_WRV(}HrPV~vM=PJf z?!=(Ow(N9TFHvF!49$yEhoG$=uk$tQ(A(oi1y*2$#tE{wf8hFnfWmoe?NDh)kn{Vr z%oj*wq-YKKxdV@2KQ0G4QBlD3B!Q@b-?qBXPAiu^rpOZ*7Fj*;T~If?5MFH-R=Jz8 zO!A}E_2RV4ydXU`=7aD$&ve^_o%^3Qzi`QKGPcBJyOr3D=V2q~riGd%*;2Ifp-`r) z0g&J`>o5VUMNy%W-<7bn^E#5de${sh0{u1$a`%z#H8YpBku6v6CJq9fDL+YozyO(0 zUr0Wec^5799SED%NI}up^ojRjeKwsD8ex39X22Gp5Pv4=<-`F$%D;00#CYOAHd-Gw z?pr(;*oKN$Jx6oF-qK<1aQ?jUl#+Rs*;TIhxJQU1#j|j)V&HL~ZhR}X0NN@3i?t(M zez11s>Ous~Pf~geMbj~UGJDOd^7eM3G9Qs*ig<6a`=M#p_-#f_89&yG57Nz#OBRWK zy`#64dWS}xJD{Ygr8PA?O}ch`^8J{!eq_lNqs{Fdf=y-l#2Trhxa@g~PC%f^Oq0}r zc5y<2CvjV~B+Ku1U8`NS3LPB;iDtC<*5YSIPWsiJo;>5Z>kd<@ z_?cR)jCKZez(?%O#e9vQRYi*}qjPZ5Hj1zSYg@pqh~}19S9#tKX365xBE8iJn-4Xs?iPzZmfXEP84os||ki0=zsz||8c>bno*tQAQ!T*RWIF2^3~ z-HJZ%V-B}Tdbt-GE`HTV@Li;$?ziWWX0Xt+*V|nxjklBT|Rsa3vOIP)bB>=+!k9f(7{zF7CF!cpfe9!(nQWj&JI!@m&jbbZ?rK4C0CDUEkiUP-kO z)1eV!VD+6Bf{UBjXQ(pmFJO?jX8azY@|KIF_3u&yD}vw)b0Wt|ys)2cORkz9NJI1b zR$T5pyV%&d=W70-K#x>PVZcMgcdFAo3Pm|qa$e;swqzddaE z=FJ;7LQX0@!$Z{JA?CfmW-b)$4ZnN!FXNYBE1w>c3|fhLhX+>gRQMStb(V_dvXV{+LlxdRXXhpQEVtf}4?D;wEWUq|%_qONB*@QMd=8dzlH;isKU zfyWY&lf0M9z6bM!l~LC{eU>NL^=Dnwt54_A44e(9S>E5KcLSDhm4jj*TWj02?jP3& zLC?pc!#iM}u@E+G_#unI-C*X%8>d=q4gHjJa=?d*6)%fm$IsoFfVbKuCwuK!$p>*v zWF3G@=oIn|AcY@fX6<=fcfvZQ=`u!OlYJqq2xaiy%il`#OpxEQWQMV3+*361m8)2q zu!QW#W_Lam|52O3O)}Pe80YPp_NG^Sa$HdyXJ!X|L4;uE=gqZ;E#KX1>=0$+wC61* z0kC^k8ymE%l?Du3$aq`yt}QdARHNcR10J!nO)qlBvuv=kuF?-SnZFHU^Sul#oR*oO z1RC7XI7ad9W{F%Jo91fl?T}~m0ONV2UeA@)9d8c)IOKB-)0s5V#4>&FRyP%Dw!Pg| z?Wk*8%Yf^qV&nT1oe_EuoFS{UhZvIB+(wWakkTFWV|5V-qx2C}36G<6g0IgdUC2^J zycSaQ*_L;omQxl03yZ4{5{VwHXYtA8v1o5#s*(qf{8*Yh{x=|ztBkE+$DyX0si zuawoNOLpYAnYtZT1L=w&9gAi5Y8jcQKV(@vOoCN^*L1oVSlOv$(XRHKIqP*lP&Z@A ze{T|Op!&kXrG3xew?kq5#H*l?m#wZAO_g_T647EI{Q>{k1p;{1vec= zCT_!65VBmNmeyIQwGtB^_9A9Rt7Xr(O3^@pt0t6E2of|sE0d~)kcrkH^XR^>SjYhD zc(z74b#b`+IP=n1tor}B087i5k+CAxcqVRUc#AuMt$d9%H!}mh4zh8B$=pbT;7`%3 z1K1M+b|4P3&=Wph;2Rhjanu^*D_OoCjwS%iL{hTjGm<|e6JPmwM}`=$)xO>pd#Obea_Ts3 zxAzfk7suWr#@xF#1V>BlM;WB;61_J<)Y%!{_ui{fS&u)^;#UN%!G!}#=){u;e+qv3 zJ+Yee+H)+Y)UL)Alr@Q5!J3x9%&4hDg%d`G8{icMe+Mj)njf7nnCGmQ``;?q5X!g% zt6GN;Ro@<}bh3$}Rb4RXs;h~#Ij4d-<>Mb}z2=+ixh|WAi-NSgQ*T8GUL}a~2kgOl z%L}&uE4(V{x;Q!+%m6`$9*EH^#}6fiRZbVNiBe6!OKDY1clfHGUKyvCjNsyH7jkr% zo6Ii?Q8t7}7w6u#JkFNYNkO>%VoHJMJ@0z^u>Ix+E_?3FYTvVcK%VLcXy*0SK^<52 zt3uf5uH5@b#jeMc5<0aRVJKD4iiBGMjxYLt&ZC+3DID(dU-bnNl6}j8!&pf!<&kX zs$`RW%%YuSaIC$O1NMq@@q^BoY+2iv40mU7td|rGzhdkyhjN1PB*^m-#8$JFcbI_m ztG%%2Pd{C8?u=ZBczsJs;>#BpZnV+4d~MMhHH2|?H~5P!Wjui@3EIx{lOmZI5w*RX zCRSCG3y3(ry(z}_3vA(Utvf3pIrKRwuu5vTv|tX)?*u3K(slBBK2M>Ph+5q{qxOqd zYxB>`u-AHAn56;cffXeHdGsw@1N_Ic1oAUH_x zBhpoHYu`=Uz&^yg_}%(;Nh7(oj%=yJs*gHiO+4E8{e_=VC3S5Fx;4xhmMLN8LllZD;KF5ET_Gt~WQW#m0g9p-Qd?mK`$J z9KbmFC8|`e=${bhYAD~t5D1Ip$|!V{sl#f2W?4Y;AM>$%6=EG+Kyl>i8gu+X4o`KeAAk)?@5Jtg&rdJnt4 zF6NS{53|zT!owdq3QWKH0%^>PW=x33X4LkiSXVU?o=)YhC2l^?thUVi$8@fS#k_^^4g` zkLA*8@a1W0v%=g5&(&^lMLWBGxZjBH+y!cQbY`0(?WZbfHCl&pNsX_zlvG2}*`ftH z@;5&H3(2Co;_=lgPor~WJA-DE7#)sR+ua(;=i#NsVx<$Y78P1~=WyrvVnd9{qxW8Z zA4Qw#i@52FEXf6Yj+&EI`RA-vWaCMyi%^zrbW3S`@9vT z<8@M8c6dQPH^uq8%j;ozz7>A+ghPO8BV?!gMh|Sgvt|PjKwN4vZ}p1J+PGS0E%zv~ z`J%xF|0YZ>$>og8J|m^(E1Z(-WoV!ghRMi~-vdsw+r4;e=|eSA+}LH+X#Xu(LsoG# zZ_g`lpIWNpHJb=W;zWFEde_b${<}occ4chw2L=G;B}}tqLxqITQ;m-aHZcQSuU=`5 zqNEm#ZiD(1^R}WHj0_mcwBQ2RI@hG?DZL%u>2*tSH7%BeMltKFs0xOD5$*wFh`zAQ-5)Qr<;Df%_S>?H{&?Q*2M_Ogc*H}C%Xy?ZJq z>s8U$tf;0#V?%6c|?nizFm(3J(M?aSgg;{sO7J(0{- z+=hbF!(_tVw>P)Umui2Nlax(T z#M%pI4yS7FW?~X`#8z=D8+*+Qi7W^rk8cOI&Le5;jlp%qe!MRx`$++1ko4lec`B5N z-wL*7kG(7uq4cWL@XSrUwA>GID{NjEx7iGj9Xlrk7M1RGJ@5C+g?Ug1cmR<=C7*}s z-lZ$E{aV?r7u!8r#~;)J3a`wIP_N5WmWCl3v>~0knrEF!F(b2u#beKZgF^ULeW~gZ zxxmg&n%bAUM0-Res(PvSeq39tP6Nz%gP^K0#t?p)zHC;u$%(xsx<-)+rhWLq9-VM9 zU#3V8FDG&lj&ZW!H@oI?yzq0+$OxqWovbb4vv1F@3L=osYhP-cB^SXq{!NQiui}vR z{tl&EgY{@mIr5bS86yTQ2GZOt6}}OjS@u0rckMvEyQE4MGzb;VzKP&NigpxDhhJ4x zR;agF?{(NhO;2xLAlE4pRHm$7o=PP5im&0l1Q5vvC1$;s#qAy1?rC4ilpQ#gZCrU4KS7JTlMX$^#u;0DHdArl^EG_z@fSJr+2Lb6pURiZsEn31p3sKKdsymv`RSQNRf?2W!GbPc0n#eFR^k7KPU+URdq znX13TekkQy^tLxBYBSP=;JNYk1o*buGY*UAQ`W??uHm|Dki^;g+f_7jhlSDJ85x%g z&G5{#p23HUi_H!kx{@G?(qIuT(B|~4{@1c^!w&jJp6kk$#o$pvPY?36-K8q}0*zs@ zIgL8!YV(t```IhK*7JbQED3UsOj{~*jh$C>jC@*;Zk!S zmbu%^=L23@Gh~z%V;N@*ORW|L#}6qa)|f}VxA`n|(>2lD0Y6z;i=;o<_80J>yN+b@ z+uoK-%+kqcgGYrT2(2VSq?0UWe&KKzki-Q4j&FJt%kM#IN?OT@eTC;gcRNYGJqdGJ z&Ed};Q;U7s*mzc5{%4}cC1kwdBG_6U`KQM~a`V+9g|?=w&gArHYw|p1GCJYSrGlsX z^ttAyP_B|UMn3sry8`ePF|IBO49_GnKx|qpP?pEf%w0of>LX`0u}(T| zEa|&6MRLLKUZcBnQ^>84txmkj{@)rK<%LE6|!D#|=Op;z8j{Oo5XS%3of%vCcMvfFT zppt28a+`~ug}uBqPpCHdYDS;>e5z03L9U_qGZ&TX7VimvmiB%3?nlBgXkzwE^14b! zRiiILq#2>2*aoy>D?}Y|EL)SN?^$Ln6wuY$M6%C<70^vgu4G!REcPZY2;e26wg$I% z@XVGM>jb73DfAf=?mr6S5U;vQf~`jpS`~vmc~VZodB+Ki{pb-$eQRY8)Y!&87&_}` zs2R9iVz0e}Urg3Bss%kYzFy%p2yFFs^{CD!urz}Gwvft%>U!JZM%WAxEvDtz_7kLN z>ig|(O?XV)Q{TJe325V;uVmb}IN%#X2=G}2{R>)e_GkI`k1Q=^p>TLZ(ee7@Thys? z0zyok+f5p~<0=b~CfJF1edQGw_odBbyfpxrShVO|U&H6Zn04I7s_%DK;z{DTSj=oq z>qQOZLZ!2W9AT@D8u59K24}4WP(`Mvd^Q!6&~qcuVXh#KO@uz?pdXbj)5+RW zJSKexSvNQhr-!e2g@rlaX?vDYAE`cOEiASUOnKOTJe+(hHb*lew;WN~@K#5Chh_$2 zc7xkCA+1*dLlnt`Hl;{nN}yLgmgwr{z&LiAMGkul3c{%k1k2c9Ofj7@AbL%wL<2%ubS~q07+h_8fvDq<89943 z?kXro_Fi*e=OXBd6QBF^)44fOW4Uu$ZohYs7hug=e}-Qa@>eV?I6j)rv+0(Ak<&Yj(`_K7XE6soy87L6ss0ntTFPjHF6p5&HTwi6 z9*(XAH}ohfK6}`=Y$6M^=ib(YzT3%SPVgyv;~&aW?| z(Bzrsy*9ZZF@W`w=DA(@(d+cP6*n>g2&9c@qv@xJXCB#ZozvVc6>ZJ{i;0gZNX3%A zp>qreU(;|qgJVokNx0dAmQpGI0l+SsO$O1T&TQlH}Ft0m?99~Uso zPL-0L`{T!4Gygh{h69b|q;E-a@ur>eF7Y6@Cu*2HVyglx(Tn<tAL*QT^z)sTd3* zvcjfAKj?2-zqMJ+)-M$tmi2-?V+*<822aw&d));oAysaKaF^?v2z;E4;BMreDpC*> z>3v|+LCGIg?{^+u*+?6d9BIxsyc*mc>}t9o;?;ov0aoY+)mj^ARa{Z!tAZSM8~1_- zfN8jRWm%F4Z96RC)TnW?ZeNGlP;LMG;FJ3L z@+6epa3hpBJ<>G2Hecj@e;|;C0z@kMFtP*YQzRCq_%tcw%6s^wWp7>lM9ItZ$VHiC6E1nzjzDq3vx~DqqulIBTnGtcMCUQWKF`zw{BhaWVpb z4K*_;6;|{(R4PP61z0qA3^XQhep0anjf+%V>;kdx*Va*8xH zci^R7@izwQJ2LOd-jG4_5Zb7CWmxIOGPg}<*CB?p!$;sA2!1sl!szg^2dYxVKwp9f zP1$Lys`sMZmaIoxn{~O<%GLZub_l-wr0nQqEhS8)STPin#15FQ4iL_3QOy(j&_Es$ z#3n>jk6pOvg~&g5vTgaP%!HpDlyYn#k7rbl!*IYz@dCtCF-Nhvlm)gn$@a0FEkpAE^l|1 z!E4F+RXGiHUKd7FRnLb4chUAbWND;tF7aJ9&Pa()#ksE6dBMNzUH>p!r@9UbEp)m1E%x-`m8Ld zh{8~Eg(AUb%79Se?Tjn_ptP@ivFiFOVH3RMxyJ@feQ=b@8r6vipYEdAEBQ-R!CVAz zQ<1l2LUyB6GH1tJKt}-kb&KMtKCI+iRLa@3X8iff8stl#SN`+O3JJ9ve-YeoT2CZ@ zc+|T)$>s3X7>l62Z)5X@A~8t)|O|ra;?|*{A5opwU63*?OtAUy&-C+G<)G zfj(ch7Gvw)1j)dnS<~&Umgvy504aK00HrtnaJkWK5+DNgPGwxP)c)mRO0s@L-~qui zy8ca9$oag*yP-??>QCV6CtXV1 zwh7}-zw;%=CKG_vLGXZUyR#!3ClJ4bTc4BcheQcD32F9TL4Gt$VRUzIDxy9h{~<7| z+y}TSM1OB%$p$4ey6rYFJdoFlN~0M_8&2oYH1O{5BC!=?+{}Wyh?f{|+Af+zlk-MpKCwI5 z)M3G-{No5o)Vg}6FeD8>VPJrKN1y1mRDV`wQyL*cskCNhZ*%P$jVWrhSQ#UuQF!(~ zf_C}>G96ZUk^Rs^1^X7!af88_M@$M~la>63--?-5iuAEB)wtRQVB0=2aN+xKRky!9 z0&uxpptV$CCC`);<`$2lrZd%Ki~)hP*MWO9R%XY1j-}X$8h@L-hMu2=s2N}CDkL-y-(j9#Q20xL>>7O8xm%Q{pA^!l-|Cvp?E2q^y)nRGpqODqiQ zh`6XI2B1Ayvd$wB2>8&8YgUt!yWPN)Z7qbBGrnr{5U+3YKK^!@49hKa7lX{j5XtmV z#PG&(=0R0F+9r`ou?|X$G9x=GQ)NL?mGlu_uUZmo{u>KxGK|$Cte%wI-6dm;ngH zZmC|1!G(ELVe_c%U|Gn;jhhw8D?gtbmE@`~Y!e($P!)?yHJxL<%rd%yU%{-NT5>Dw zzQ$qknXy4xNYdOx7=C zA$9qrwjB00E|!XNHpnNl_`}BvCrmas|Z@$t3~j_0nWzVX`9+g`m}3n?034Z=pS$?#~qF25KVtf z1mC@!a_~8;V@3HYD?E~6fryJCqPA(e57%}T^T#B-LoqI!e}yr%7;p!YbRx@=&pvwp zPfmnfc>Ae=QQ5G4ez^=_o#E=?5h7XEOQy&QS7y}fPgFpig;EIPj+gjaF(~3YYg2aIX<_e!mg%`Cy{^gS9o79NzF$XOF+hnmC#ldTZ7W;Crc&>M%h1f9}fR><=P4glj+Em1&!;?EUk|6UG|nHPwzGlVXr_3)mwr$1#y zMLM`TEE@o`mFL9*OM|~TbGTbiRhWb-V>Y0|+Q>3MPlqMD%7%XWHL}mr8O zo3#$`1;LDnX7m^-DcMk>U#PV%LONUqEkK5^So>No2E9JL$ipU%RM7mDO(yBL(lka2 zczaI7tA&Fg4GO@P@gpkH+EwFf832r4Mn;yMvg)*gNKG4Y$Y|cWdO>X|7#d-yMmUrO z*cl%%G`<=R&$v&7T&_Cnv>g%>vf&5_3yKmU~oV6=7NE@^Bhe zXJ96Vj^ulMrJbs!-4%8Nbp#e*TEyIEW6ZR;@MosSIO$zZU}`X7oPE1XAwM#*C;R65 z7k(sMohebhp&EQ;CUwg4YxD9D7YJMBNUg6`k6la%2E=yzEup9)7rkU5nnVgNBjte& z|IF<`=!h=Y9mT;?dkm0n zwZ5O7H}xabK1$oC)`58?bHusRkQb4QX1O6rX7*S*MbgPFB|SocC0Q>j+@BHYw{9do zj)>aLW*_1SM(Y@)1$)#kN$sAF-eqTJsdRt%mG24I>144p*5bNgnTXBKf%fcVl2vKZ zO`B9iv;{0AK*eyq#5kq$Oe*Bb$-C+e}&#-4XHdb#=&I+$)G0xak(vilQ=T;}uS z)TP5wjrODQq$qWgMyu#ZR@!5TbGvj@0geD$D;;D zmY`dsgt-YxI=fBmcBD(9o*pM3wboSTE*_=2Y%OwZToEr@i(2z2Q1#A7gAFsrj*hnN zYytgmSLS^;Ghn`I2U`fyc8h1LEtU##zoCuSgEYor&!`5tV`6rn))8TZnJ| zq{H2ldMYUtWLhbaJ2p0+Pe;4c^b_mM`K;8*KA8wJRK{lzS?flMez8mcEt+*jxJ+q1 zRK0*)ZL4t{vn4J~UoIVpJ%_4k=T>#QOH(j=Wd0h-P_<3*qlI+c%iieU`Ght-wpTGT z1(Lf@3pHO;e_ybXqnA)ZP$4fPqw^=l2G9?W%S2>sY)Q%uAM{}8T8x!WO;&dQr2c&+ z3UhqVWt!F`(`^@(y_P=_kGViUx0!lc*G1c+Z5k3Bcz$}(a@lLp2{W?^sTQ>K8>&6~ANS zG$)j2v@^@4THDWxNA_S?{T~-FF@B`E92eDFdXlQ8`u8OH1C>3EjA!fFxEvxDF^no; z(iG|j{S5Z}NZ-K4S~K@yxefG};3S<(Q)9dErC#$;n>_A(Yze7ZHhzV|(J4ZUkjgXO zXLLelNcuA%?77Ow|KZ6q+XgcU{rckW{7UzM;KzP!e75E-_!%(T$&aPHRyQ?qBI5sy z5F0rZhbqZ%?;&(3jF;prrZdO+6HC0Y#y)0uyEi{Il~B^_&k+UA5%JH(J1YbtHtGsm zGXX^3umfhXuH)em{su>0`mg3bY&0k3*u0sXtFJGFag;km6e@}dlQ-JT-@pc#q}cQr7BgI&LsLKcIcl8lF;x;NPM`)e}R|#y4t#jE9U&CzvZ0z=uW=9{e zzx|57b(Hj(5zfdrpIn5h>I8ktz`&aC@+=SS;&k2f>GolA>8*zw1#i3~-Zb72TX#DI z=IuxP9K6*a2=C~gIH~_c(94;ME^>V>6xS*kD5tA+yT1!>8uqBbA*qUDs~a$o#Z1E& z_~|oCto=AL6?iio%?W)gI?-#HG0?KMT!=!c?iQwO_Dy0_Eey4UnFPXeyFhu8x^|${ zUtdxQf_)dQF+Y@1rBtKcO+ti0vIeM+uZUXB)ONa39w8UXc@i5)$nmBz$G7RkenDtr%#QK>t84ge=C^G z0u-d_ZtHCK*$CKkQ;?-d5e?n+4A09tO`qjDDP9JwgW})2q`fvf`(~@gPMoF;N^$i@ zHeY~iWAmjT%ukgvJH5Q~xEFzq4K~h`8A2lwmP?>+<0!jec*6alOuYKX&Z$lS#0}gz zUT%ew2ht_87Kx3=I-3~pRmDvFecb@;!vkw*pq!qk^NsnzbOKSDnEvb5 z&&fbV;$(K>3l;VCN#RC8{W2Y*)+Ywz{^+?ZTlEQ|=X3A;XbM zw6ef}6tb&P{GRvIN<3iqGi-*Zf$zSC3)|GoTGbP;o7zqh4e+=)KQh)5kWAp;Eq5g; z5eokV-+2YvN8z`GETt3bdad6A-bJggQT!2F<(7FZT&z?-3an>2IMnLao|kK9qrnEr zv)dA&IlC-+Wg|!(Gs?f{alpSri4*RSw|0$K1Np0e!zZv2Su@?ydM=RccSEq4a{8^n z_Y~)Ip3Md{(^WZs`9{{QsW%nojMh(~O0yMEN5Q-Gb zz-|)j8khYE7N;=FI8Z3imvX9{a@xXm>4b@0E9Ca!^WYpUTEbo1aNrAf*zv@IT710} zFYS|fCin0JkmT8HLv#FHY|knR1e zopVjN@_i@Ynr89Wo=k0~KY^!2_#=E;S9I)$>h!4{pg}6OXL&%`uM_Lt$oGj7_}-j> zgnp7s4_!oYUhW}38NIfI=5mNnCibh=@*n(k?HC-U#!2JoGQ`^Z(^XQ07RpxXA?!9B zp1$a8+>~$Qi%jmcQ$VNE|`+~&sOI@!kig;Jx3`a&26pD(%Vr}IF&yGT8y??jdcB$>&y zmm|^MIvP5mZ8xD#Tr8ZB;hnZ$39owl0$o9+}Zvl8hVX&ULu|f+hIPrN%hh=T)ny2YB@wP=4wfQZ`vzPM4NJ)(j|R z41BozO&Hc5pVWNzX;i^n%i~U&GW`C|thHW71#j)x)y-o!$?N*T*OFn8-Qd$B-D6s* zcr_`syOEdvcYefg)}5G3xi?+tYop3u6R1puJae-r49cGKOOR6}NMH!t?@+tHTU|gE z#QLSE$!7!s*!LP$HAQmQJxA;TH!3=+W4gWv^MK3tsPtHV!ujT;c1M@j;oJnL~Z zfbA9m+Cfun^DXB0jT#3tu>aJKk54%DsU~nE%w)pV*UXE)^aByCdMhg8NwI$|)Pp2e z9tZJYrV&kTP4#qljXsPg5a)I?#0}N3tDB+h&IK^t*X%`<-mO|e%5_eLShIL*lt+CL z+M=Whn{AQww{2qr`rs4BmBur{noaz{qNHcHANgaLwMZM}&;gPT#?pTlx?Kb(T%EA!CUKT0eF5 z&tT7;$y2q6oh&*rQHoki2_v9l6)TN!|9ZMwC{rWPpugI(0}SulnynJIu8_ebxujQ= zyq({AY;q*K_Ef@eX-vzu^&xwl@$SdC*%^E{loy>r!0gp+b5=3veeLwuqD+jewOwM3 z8f>3rwD{g;0;M@J3ymrKrr!#7)j>`m#@mM$GcIPg_Q-Uj7p`QA^I->p%eA_b&&oI? z;#b9($5S45^G~#Z9{{le9j|fn<5t2ec$-3ZC6qSoYjLb?3n`kerOY}q!UT>eK{18w7~NT z=s@;y>7riCyx}4QJ9=`oh&Ugm+fFXo-h>}=d&lDmCtPGs$d);pE2xTwi7gU7!I?-N z(-5*0`vn0#R@%O;Cj#gSIyNz7^9ySzF;{Xuh}{jT+(?=EiinyLII!i%Wsv+Z=H$rA z<)=mZg#T=|-1n<3Wxf-$5RK1eRJHJ=+?}~!&c|`lynW?LT-@zJ8_31tZ)Oyr(x}egmV4=8)iv>t%UN6f%95yc30~RxJnSgvoShkPhT&X^r0IVXB`y_y@7$|mzT;eE1n0C!J5TvrR z9Z!v4D&@;LSQIwO7FFAp=U)`W)y`vLlvM5~GCOSNy!Am1kt%EoVQMRA>rYW3m=7%# zw_4q|^VJ2?h_pW%M9<%3?O-6$=B0kHW*_ze1aH!XWd*H!!;1olQJ!vvtzsaf87J3F zUUfOcrTp^vsSJwJ?}jh2fR--IcBB+%(dIH~vj}ITP_dR)eZ##vX-`ddC^_XLz}!l3 z_bBCZF<8lpBd9+lb4ke~^uom5G=nh3MFPUpGGe%5BxNgKE71W5S1-xbMj4knL&Rf; z3?^+J^k>#f+8%~l=(`-pcu4--e0ugOH*?(R1XN1)Z7Vv?Pr*0GqnBU{ZfHJEp^xPA z7719M{GWbE$(x(UtnPi`$6FcjCx;?%40Xotv`^3Q;h6mxsUqaL&kHv81AUHzEL9Ed zA2;($`pwA+1ltEAU5GchZxMVxxu$p z9>JOMJyQFP)L88tCqa%CY%)=Les?P^V!JGng_V4u^gQA0V=If4+eIZ%;k;vB){Kqg zspU_BGkLLiw*&HZ*q9@1ZUG=9wUvdY1C!6oC+LnA!u|4LAan_U0Ji%9kG>@Cq0$7B zw?uUsV4FK~jgAu}$e&KG(D;(g)>D$$cjFW*%s^kL$tdrD>22%g2Wj7>_W2YAk1;@1 zlarcQ@WyscWs~W_hxOHx_XALLUGhc(;>_!tJ}K-PO6!!2uT*=*`Qzr%*(AFskx5JZ z^>#P9ALHKXA(QG6Mq#c}BTf6XE3mI}mQmHc83?OG10nE!p&qYohP7OsQ)iv;rCr(g zGN>m`Jo&Pbn4-1D$WZFpDm`tC*kZ3Z&RJR;yS| zazDRO^8%3=7@wMzj4AqzMF!Sb}ufvVBj(9R6Ul*QG1 zo!6(h3i7ZtA3{!?6mccz@=y%|kG|-w)?h!Pf^g392Ub4CHRAJVjsI^hRvbAKrzKP$^ou zwKAm$Qk#tL&nJzq0%IC$MWI~PFxVc$9c-wl3FelvuMjNkO~M>wiD&bE{r?%Cz` zi3p|^r`uHMZzEbBzm~?TSF&$RWj>JJ)Mj<}{1RDkBb{hq(vf|`#l>8wDR{$lPI4S_ zhBPh?DW2awNhtVh51br0{cpYjs?bTLtIZcJT~4_0G2ETee^`PfH<0pSMumI1(yf38 zAiC5NP8?T12?*$qWrvN@C~L~@f7F9yx{ln2sIekhItlNWl5{Q=d(huYE)emZo&)O9 zq2%Y>G~DcJPRWCQG=1>guG;Q9F-$zt-O(*b@w9xt*|_sq_kLm=LGMx!%7sfW^yp8T z5l*l+JGmDqVw5b9-rIMlK~UGz`9ML%fNT zmgSu|`03@#k;fn`UMm@$d{5wK@67YVFShf!4>6UCmN@XXCGj~DF?5T&89!O65|9Ls z>gVfJxYv^Sg131DrOLifxt5~ejEvbxNez$9xzbWICOuBl4!+0lqA4l+HCjFS#{P#$nkTF0tRT4D&0-KEmu%ypC~&Yv7l!??j4rg367 zK-OrCm8Qh?NcL%f*a%0!*W0@%HTVUl*DF=*LJZM?w#wGtt9(xUW86T=SZW>NXBt#_ zK@jtWC)Q-WNTqpEEH@Jqn2ru7SpcCE;(pk)1YT&+KC>L};JDpC^ncB}6PyB6{2fbL zNC}eind+D5^fU=zZNh5HY14$-fLio~l0_v2`l%ra1Mo&Qv33{@Ckp#5#K-Qq&xNP< zCu!!cyva$6lfW1b-1PQA`<|M7+dTNC%1b!kmhj4#HyDljKsq1-c3dlgN9C|}eHpJX z3{#qa%~CWl>QsIWVaxt}Leod<)3GmpQBDM?TsXFmG}_ymGII`uhhE9&K2g+{aLaU4 zm>F2Rh|^b*u{%>2Sa58orf)lowQC<=S$a8&h>@!O+DqL2GOXR|>qM8C81AUcK^iyu zHX7;4XPKoEK=aWlpmm3MfAXC9!ud--sZyhl_Aw|FQ^5>$phQH;*L{VbA z(*#(%(9K}!7%gizJ}OPzO)8Vjn(38N!YZ~XYz%j`jA|StWgG$KIJk+bcF0;SmU6Lj zoVH$oL@QptL1@eW_+1#nh{B}0^CRuxW1$z!8r~vLwn|((*KngMX#pLn{ZZyTb_QL< zpDQt~ARWQoext{C1_0o`M39l~D6hbv&ASB8A5B$N2Xz~)cU!S*EkQd!tkOC7`Ws9| zRdIYW$|>?tm!y&(eNGo)b%bwB*S5~5iE4#zkSqq1uMs7ZYkA`!O8m*tjZby#f~wm9 zd<$~2&U2P<76v7zZ`wbuU%XnS;%4VB`=Yx4<416SI{&@m4BY^veN3PZ5bZGdbVqu= zH77IP*c=x&H!x`HGP5K~9*j+q3ziF$tC*F6zWR%&ObU9tn7a^Z;o`2Tn$F{BXD48a zlyZ^kSXtjd#2$|~P&>XA+cjx2UItF)5c#5!F-%Z>c&rvNHc8IP`FMZm3lbe&N#d{F!^Xk?%FB3Jcjn8xC;B8*OyHiI5>)$n_~O&3VZkC7jRbQ)gJZ zO-F#YcR9IRebog-4|M@@ij4{j*?=#}S8(C9dKXviz`4`RG+`%Z5}e;uou%ZlvLkyx zL`=AElTG#`SXxR_*XCWw*B!5Xql*js%pzVwdu46B6q+_)J>3bpXWNcZA3TmGM1`{) zo$PcZzJXfUC;S2T9xB<>3T$R<72=D#WE16q&#ZOQ!gaQWntfCr z%iwqIx!^khO_V;MqR?4`R#{sa-V(2{#g}C^Jlhujpya1>+DD+enaHm4RqWYyBR3Cu zzDXfDmEXMGYeC95?wMJMT--C95qNebXlaI2>v7S>rbGfI_S35%*|02;`ZTk`Mf>!$ z04YuBw!o5!JvTd@?MxRZ*P6<@oCpkyB^Tc-0Nn~;BhTQ`Z-lQs9v8xX)W*T*7vjRd z*J$ag5ds2mRMo&dICzy5Br`MDH`db;XAIIR{_cBMc;`@6Pe()7KQJ_Hrv`{W4N`Sa zlfjrZ?&5D-=Jd|==wcC?&>P(z`;o1mecB*#FYca+&{XcQs(l+(-xzvwT;~o^X6Y2- z!G>&?yz%M-BQmU9VmrU7R}i|LHBRF4BoGea_xpNb;q&n~qa? z$2zjA(J~@=W`553@ckkQZzSp+T7n(XXAfrm!;dGPY(~4*vLhU~uBEPmzDMg6qfZpZ zH>c&d98Q5m^}}xWf>uQEPO$9|hNj2!gfhyL^u8w}sFQCzdqfl`13aYwfv1L5+w^$Y z1X58DAwS_fZyObtjN)c|-;A)`&?KbidIDy2E6$2M)BM<69kyCSK0-5=aaZDDVvBL3 ze=A-huo9%JQX!Xv{OLrT**$ZO7me)I+y@K zqte&YMWx#uKXUY0tA$&<^>okYZgqp8w|eP8&PooelT7&TZwXh5prf0&&>Xa%Q-paO z35nr{S`tAbeY&ljfbXY--OnlkMPSn>$VfHZGUA(?N5}RSC9MN$et826cW2x01#Ol8t5UL?mjdDI)J#Ye}8vWag zwOzyi6br0e627h@lcTd103Sg*x3I*6)A?n!N{)KGf z>QsT}A&VLB#t=a8{U zsO}z=QQoBNJHucY82NNc!NtSHKvrJRo?c-{!EV&1v&S&z6x>e;PiljIb~OwJnCod$2`=mt$o*^N7wx;s>!C*jij)xQ`Zta}~M1bcd&cWz;Ct6E@T)Yv}U>3I= zh_Y&C>s+F;%rs%oCHi3*>Px_q)($9zC`&AUo4163o*({>e|0KtC?FF@*ZEKSLtYzvu1VPtm`_b)uY74V|O*d6Ra z%S(c?+JP;*{fjx3fzqmd8yld&sfABRB)x7eW~_`Vml^3BR8dWD$V2c4Ou{KsDf6$M zBftOgFHTeI7jS`tV$E5{K@}6#Nq}vj8#w$IR@)t!n(ngYga&D~Tdm+vlPq29dR=5{ zwt;Is=d)K2{@Q_p4*{I$#u`kQn))yckR!)IBiEd>wq_r&#wr75Ja629;d}4(W;*{T zdvlrJ$2p{umn%0;@j4;7Fpf+`Nhd$*3b|hS+=$9wegOYcD$lK71bofFq{>bnS`I4^EeBFOWX@f7Fi? z6jNjT;eyPdqDCvRTvnj+2}z3`K#QGSnmWEf&IsxqWC94*j0|*UNNmigxh#Bi!)=Q{ zV{P^2>#I)sevqDidY_w3@m-HG#$*d8HK=9r$N3vHQ86h_h+CvxIr~_tM($b@0AguYtu6JRkg!shDu%V=Y~b z6>_V74Bt3+Edd2A9;R~q|H}^F&QQwy>~gH7q=h?Jk9Hn`fmu@(%ZmLIBLX8DBd$=k ze0v%3PNUyIs})LUv;VVz&lVf^8WCE{Y2T$A7=G7c0}1il2qQprpt$ayk93r@K0Dub zr9B_}B<{eCr~_)5G&NoC=b-JvoKfluXH(NK`_moYl&|ufW`GE#W&l1hkZNSclv3XB z4GiUfAIXrJtIi);s{nDA$qfXQgaGVVfGId|_VVN${rIw~qC$Z>E}@)?BEP;_!%~RF zEkS`weoNcu-KgG0(u;Ds!^H%re8R;vc6hHxqgzwG>xZ2K9rjGGj}Z559U(~gld>|R z&@NVm6o9dt{4eJ5KdzneB+@uFP5nJC0N>t}0pGVQU@NoIjJ~6n+F(g4&!;2AGP6jJ zkMlY=4G#Kz!Az1gKiJmSwivJfVQSH;Eeg#bji&;4RmIk%?_oFM0&^30A$5wx)TlTsq4Z!`iOnI}H{!_kw04ylS&rIXCJu61g(r7mCEFi4rGj4C^SM(l z_AcTRN&n-5U!)cF6fI~HX6H0;5@P?djlC3}{Fw+K`Ab5_2>QbY<>I;VgnBj%9v_R^DJ8mwx(e`4FwReEv2>DEew+Tcjb6o9=#T`loa+(H^1GO{+$a* zj2=kSmjXxVHjifeycxW^K8&*BM7{Gm`^wN4*Ic!o?BNE=*XJap9+a(hz4#X;n2jgL zHfCTD2`Qu0(5f^VIy#AhWX@x6SPyV*^HXA)s>x`E;GM~!u_;!k)A8yPnNHClN=g+` zetR02v_>ARo<})kAuv2`h^a(MUg84%$b_M1HgxQaA)lBKTliGq0m?^5(Kea&dAH9b z<>>oAiNW7CDe5!+%#IEX&OX7yM9N?g{>L{`_bjNNGltbXS~3?vn`5|gxYkW^sKu+r zJ0{5hyHio0URj|RX~ZdMs%jPjN>FMVIwo3125Q<~I_e@7>O>18t@w{i4^A6Vl~qLh zejNK;Uh6W=Dp~ZmYna;Fs_La~=D)HoCQFJOnhpqXO3hLnrKN|Lk<>uF>Z>tKX-=n? zy;6#zObi1hbFk0awE>Fg|H6l7y5 zvEU&g)lk57KS*u@{D~%KhQE#i(o_0G#OOPGH&qH}r+@s<2!2CUYTLx3h2((&us)kE zv)QUCqyKHcA*5w){<5ls;Sk77vqEo5T!`!qAz{s;PsFH|E8&=q~&@o z<_J_zi5(y#&0#cm%Rc{z+b5v7n;&QBYIFvpohD7h{k;>QKR`M# zOa7Qt0Us3L(SB2lC=J7jbE%suD$t#d87o8rS_mM0d-0c!Uox2)`G*x1#{w_ux@*G( z2bGG4j*&0F;lx|WHfSK=UvD?G4#G`PS8*JRXbQ;|Atj;sC8+`6#h(B?Y+;jR9mdlO zdj~hyoN&Ipx-~hu{=a1ZZyW=^ljmo}Rt*_eK-H*H^hL2y)D&*Dm_D>bD?vq3(KyoI z+1!I&YPhRwA3_xP{@ymy?>hiv4~z?plk+2cm(_vBg+!nU!Z$VXe|8_Zq6O|J0|h%C zN+*q+O8!=_F3Z@APMR{RJ0J8a?jSg{-w_2fsA^2EEJu`1dS^FIT}{p1i+g5%c3Nve zt&D!SwxZ^*Uj!V9T;w-^BPy};_Y+n7rIm&ikphV5-*+_S1@w$G{S4H71HFO2fs9>- z9C=hWkqM=!Nny30?62z1F0p9sl7g&vL85vN9(qASB(;px)GB{EQC&oB9UlQhc@eKw zpP;NG2?jV!u>;LYqN;*&bpzdn5<8v$y!gLf69p&@c2}&)6xCvXdd$Qo#JO|LZ^Wu} zZ58Fgu@2mFZ$4WT8L}{_%{ZDZmHeo(>eUSHb1aPOyP5CHDpJa%udk?#@0U6qBUigH z8R%BaqAsjrsB|_$H6=0mKdz(ogn`nDr?1b_*=C3#=%zf(#@kfWyR}40ktC|`H9VG` zPF2Es&&H+-Etd<27DO=>>>UKp%NdJ~V+BT(YL!$_$uF)kdE9tz`J@_GG!v+ZW?@a_ zc<5OBn2=^>td=IVl%^_9j0Aa3gS2t1l8M1~a?`$C|5I9gNWy z(-yXO%(4h%-w9blvsL3_YDEJ&gMF(CXw@zHuq*@>D}{RBIr3;(bBAdw13%$ zlGEyH*IJwE#s3^J`CZ;l2`ZXJ4~5171AeNiJ&E*^O{*&#Uaa*hrqllT z^)In%&*}e~iT@m*OtfS+r-H-G+zoJIX4|6i|MM5%mv4d}^GN^q1QP2D7S8`aPKc@5 zpQHb;qXaYoz}9g3KhAtI`uP7&{LjPrzw7%jMo0R}KQlY)d()P zLeUa5vJGhxyu7@|LW@IK_rpz3PEb$KP}!VJO}5_Q;8b^i8=}rrcD7ge27%Nk1O~>{ zs>;dPT55R;2`Sx$`SW>UZf**=B?L61bLNSH60i91w!PQqNrY9o)0p2zC{0nnM zw5~K4eWy&VdRwmvxf>pVx4!CHqZ$S@DPjLLW9GuE*i$J0Ptza3w=k0O`sI?>9JLfB zCwBhA{mcK(zL6KH@kn1eH2OA#!crppP1d@@zcK^{1ax+HH!3?f4GZ|1O~b(F`wI(4 zE?cmh_5&L$;^s%Wdwfoks0xgIGT60Y+l4IHISla!-EU$t0=K!+vWs(+Fzd_;?w^v< zuUt(u*L;wWI5e6C;e{_G_KuWZAo6p~SYpgNM+AXuzC5~gajuw##ls*G_Q!kBkbk_H@LS}>Q1pecUEP` zUV!VIhYB5?o@X+CFz8G73B2;4qUzz)_iF1Ki(5vz@??`fUCwHHviLtkvDK% z7eI%YQlbUJM1-I&{?wFOdu+=sCTooN>04FW;#GhSTHRMcf-FLF`$J6{i z=-D=oG;Env+fqqSFDf&qNu){WgvT+_S)x^=F^W_Rd$?bbb9yI*rXX zJ+@P4k=wjm6b=efSS9oI5zSU}IV&A6B*|A*7ky5_9X&GR@QEL8`n9#SqcT+I*MV^` zi$>DpixY2$p%~zQ7n`OE9;Ukv%&c&X$eyL|TaCx28SqGc z8egJuGT1}<*dLB@(cisS>=D?)Tzs6h6@{JHP08h+XO>DJu=R9ZTWo*MpVn zkdO69CO+JpK3}6+qSM+4FT^&f(t1KwTr`7146ZfZ7tnI6T)%}B)Gr58Ktiks4%7Le za+u45GEIN%S5|AU2nM`oS}s4z{#=jkHI|bLZ;vGtm;aycaSOjKULj=&l9N*koXt2T zxAG8y*ZCFucupVlp*NRwzLU8Qr#U$}vZ)u}@-VUGG3%2s;S5rt2OnL>o82Bsbn!XG z@6J;ozEF*32YF0WDWGKl+%k~Bh6v0)C=bzF5a~YWd6@72=S2DY>Wpl4u=#tc+jPvv zh+C}Q$;rjR(UG2sX}E7lQhNBxZ2ST!RzhfnsFcbP@*IUo<(>fZ|Bloqurs4fms?cK zzc*(rCnvV}nA7!;%YIg(9#&SySTBzc!o}a)++6)?r>~|aW@xA-)(x9?P2=?m$CH$> zUyc;Ke~Nr}f7fhb^*kWJ!UB&IJ6ghcU}%WhQkjsaW_LbcT*CfaM16f+E-wQaD>DrR z$3SAAkUFyEqi4c_r8^4H5NB}Q&RCAVGORl#VcRUiq{+$ar>8$Jr z->D{Iz1wEea_bOytZLMNIqaeh^BEW!4d(wbR+5*$JUf#y#dqEv(y>cH#78X@7Fi=!;}1(})9RU}3&IKFKjV z?;h+7sZ2mXCm+@7B7!Sws@6eHj*f`$({Ub08NTgZeNxX8l3W>7=MlH=UO{Tdm#3L1 zq53(=<}p!GxR5%dDz~A5oa3Ockaa79P(YLdofX}VX_zLa`{y$hXcKg1ZVnd& zGBe$PH$;6AWo(K2&m1&omGEx&WF0XU5&hY6In_^oYst;DNzK4;dq3==53biarNXMJ zx6@*fxxODhCk9_IykahIJ=?Qc6SrER4EhZ5t=wK-Su0@_!yx7btay&?Zl8jlUZu-C z_>l1q{zlVdXZ`qcxS?aZM_T>o`uYS@t7e{lqLHQQc6eb7QKCcOpI0yBMMXEjO_$({ zjn#n?>#5F2g{zCZgaXVj)BDP_#Y3qrM`KopmN>^|aK8Q7+1hsU)|2iZHaxB|dsn^f zoIGOU7G+ARUExGL(4)0-iSUWW9({F2R7^BnqHn(Sqvv0bezy`cm23oQ7u(ssB_cXV znR=j?A#tfXTt2>Ar6O*oInfZp6O(UXg=IwI4WJDRcx=@KWpyg99lq=PVfCm13{-}J zqpoRb@Po_iVzg+Qo(Mv~@7Y@Ce_SOX*PMbHo?dO@rZov=ZnK&NlmI;C<^bod}qg^n2CTA<3Bg<%Q{*`1j6zInLmRNi5ygcFesw z>V%~oRaps%*88#~85F=@$$en(J>DG5DRdK%VHLly89k>Ae$R7ERObDDq^Mxqp7xz}x`=-#D0=iRu2iae)%P@hoqi! z>mI;30P+*B$NhwJbNG(+$$WRjjoohY#Xjk|admYyh2{>JiN92QjndnvMZo>Awp;)V zxhOMJ2H!t)z_&~hV-2sc`o+TDHv>oqk(Dd_v}t|chlIGLsJxP`i>RC#3d-d51%$Gl z(NAWRv4+82-_arm)>;@skFV+BdiOGiz53?lkMQLIylV4kaj@y(-s%FIRkJ!WDvIVr zn>0qUQ2M9--eh*MdEC;2hE;60toKcq+XO4QRP*IQVY_H_z9^m;IzcD;a>CMM<7FGw zH3G4v&n&WL%h5QgraoSiloSX!FiU4!K9_Ri*$&v@;TWfzyysix{nj?ti3ZL0-OVOT zK3T<`8TF2RIh83>r6(6B#;?1VhQ%A)H{s7s+t2nEO;(!95wK;Ug7@INs9S;iBUxbK zVD{erwx9Aa;tcCBph-}6o7OIe6^O5y()KQTwDFWP>fa@qUa9^2A!VFDNop!|-v4-cBgfxrwE zLUPup^H_UUU2bu-{WLB$ysrifKVr{f@`r-4#KYc`>c~h+Vw-$~+8;Q{e`2Njhv%u+ zvGkTtw^+@)7=b4Snn1qX?K||>ecPDma5+(#9XOd3md;75Eo8}4%#2Hm;_tfD3HYRm zuTDpQaFTcb`Lj>jLF}=e&Rsp~lY=2W=-j9eyRXssoFgm%SaAKyGwJ_0iZ!kuoj70Z zXOmJcUY8TDinUq2 zqo%N%y?ck49gZ-zU=Js=5T%9~ttNxhL4R^HD)GiSH(3ZNOEC~}%LBDQy<1Dn9TIYK z3tr0-N=m4`diS>%VV6>p@%t@Avk*x|?0aE`%o{4i=0=XoL<)eSHlr4rhYK zOZ4{UoW3nmuAI^&Oh-@4r}D-mr~~fwN3BZX+dpyruq@ElNYt9uv>m_qnK5e*i_OdA z)$%{x9&@p;l8Ho5PSGB;nh;mLtbj+hl1XADj=8&7T1`E?9VNV^d6e`ZP2vq6PKP<) zT_Kw9?v^vYaMQ}J0ZwK;5T75f4Hs*+WMKJC@&}v*quToRUJ7Uy>Jp7={(369*pPI! zRCW*dz6(paS!>{Ut0I+?Urc(y&CUHj8bY)-F;SD2Y8bg`wPP3r@er2)4u5_ zIyrIV-$>`_64sECl3Mb8i~+I<4xnJ$K53=_SC>N7RQ|hVK04nUpkKznhYIKqTvo_F zawe#U50ls7e9px;YV#0$WigoHz1AIiWjgAp1`;T}IqkPgc%{tCw*bC*u!>T$wz4|y z%*ONvG7S7CfF~>x>||aUeJ4uSoLo3LlL$IF>Uu(Ai9PrvQD#+XDMXNsBl+D7D z^ZhPvGrSoP&{cx)*asVCFQ>w_`KwxZJCR>XBp9Cu5V0B%BlC>`VXlzs{l@gt?Pf@T z*6S@qZbtquN-T=PVMJf;)jdjh*-}H72Gh!*3}kMVsioqMr#};6B!2cH4fdwVb`5vO z;O3$q(_dYTF(K>&zxi&RZV6+-={Kp;^;i}DjfDURAGcGt5$5ol4ez~`+Z_fcasO%0 z{M9m@wXN%ss2ZQBffn12DTwd2U+o8d@0K%Q=^Nu#5~qVguVeD~K8DU%SXf+dP#p%} zkQAJj7&-IoKv!@9*I3kuGB`xKN3L1BfpDh+UjO(BXSv-L(^C-rP}pWoLrar~yJ=>Y zOG|V9k1>mBtTd2*HFrI!V%4GZdFxP zR#tO!NsUORd67dTrFv;mQBg@rjxr-{78XUITsk{#{NFa67-~~fMKLA?!cTvf`3TPSrje9SpP40^PT#lyM8QnFxutv{i@;-&{`XD$=e4Yn}Gm{$omX$%rzir z0{eIIB<4Va@L6w@JEBS0{wvhkCeI=306>miWEC> z(NTk^JJ)BZL~fY%gL&2@ZIFMse|TVcaClgFWOxia))3T>?Nrb4-&j2Ql-Ml#`px)ry{Be%!|txZSof z$cl)l9~p5OUi6!t<+>|(NGL~~L^L1%ehU~yu{KO%(hI#7$4N`wISQHuASS1pfLru0 zG<8RQkUS;(`5MbK@1CFMl0DC_c9&DYc_~fkvCbg#Z}Ozw;r{i?N!-=nW-&THN26tiBEE*T2mt-=*HPU$u3OOt9-r)v0JMH+roo#-sIN_AK zKDy@=nJRCzcX3H~z4f1`ig>iUe3)L;nMoZ=+x6%6hr=z?4o8isfDRbT6IC7@ zb_*oIREWdFet;py33`W%YjF?wvyZ9#=NwCOikA6uc;M|n4yNQjo-Wv`-|7$H9Wp6S zTSj0Gmc$(=rsnvVl>8()q3d9XFZkpbB_K#hUN*~@y?s&6t8>&ZFrs{^L3L)OXjUD? zzoEh7>gu_}QuAv2kI)+O7gkJ2-z>&S@~G}*rGA-}rd3G9;VFn&Vj(wHqN$$;tkEJ+ z)PsOsVU-O;yzT~2tlcTGw zWMfoJTpRw+6qG_nTCc*Ys<9uwQpNueJ_6F#;G2qe$Xe?ot|B zBCpQYxNMPOE}ZnpZhZzaWIzn-6awU{l_|qbUQ|CozyVK37ViVuWJ_J5Qln{=F3*Hn z;vaYZNGs9@>D*s zgM>{>o>M*simS~>m$mZR6d(%aR^GGcH>`e@6&3a_4wPZ( z{??h@45Yj=)-Brkl!>vaEEYF@Kx$ViM2S%!ikn&KL$Wm#;=;Ycb+I$*0tEPpZN^*c z_*PpIq)fd{wq=ZfcP3Wq&hYTlmvsejkpQB==HvG#4`R?QTjzDh+0dqZ{B#IWyBlaK zdZxyyZn!N&C|~UHTusk`&t7hS z0WsPV<-yGMWHtSt{p4{dH03CDt|N@-R#W#xQF|8&QEM7XA+rY0aa0vgB< z3GoUDfB>VeMf0bhhKa%9@+>nU7w^tlc+(c^L8Drb+=nX|C2Cye|KONsZj2w;-zZ#og<(;cEryQAZ~V2XzAlX;!G?Mz4O-h=zCi7r6 zW*1P`^7s3j3&d!qPfmS8ep!>`rDtzFBF2&rORsu64H0_r@C&y3Xm`F1A3?1RO4hf(8#_TKCW=Z)fB|l5a^l=?kG<`c z7+V5aPAyh53HvX=uBEBOyYx$VRLgUU^VLjjRwx19cP#Di>R*>jH0w|4%42bA!Gz9mKox&*Y@&JMF~4J~}2Q%w#ciVv)nHfDi{_ z-;?vMnSS)`-yQu4L~7>nzsAUXE%FbYI@exZRjpeA#IRqrc!F_xpirxeZo&CZlCzwd ztWv37reB&sr2{2{;mh-oxcrDXm+UT|$$;m`vK=>J+DqP-WP`s9NF~ru&d+_f1{0?4 zE8AOKW4UC}M?+Wb;O}xHXUKKDEe`f_mwd*)oSLC{RffkW=*wSXFfz)OwEZy&n>Ao{ zwtoaql?5Zq?}acL%hBCm3RR|Q?fBdv|>NzfPK~Dbyg;e^S&&EPnP!dP>CL} zEU8R*sT^)+lZVGF@|_-U9r_#<*03@7O%VAW5lxLLRDN_c1Y#0{LF=4{$EwwN52tiX zX8>y4F+Y_gJ`QQ6Hn&Lw@`PVvs~&&9eZl7L^6=yJ6IObwj zkKdEm^ra4lsq^o((UZ(l38lZJ!HUq?- z?T4(*bVO`d$?56ewH)yYcB8rGgoJ;~12+5WD#u`eW!SumayagHA+05HN%lak$2v~x z?WCXlhbw#@H>Mt(m?jeI1yERc9gb`TxFfck1=Zgm&5es&eEUh6B}XrZ$YCKX@elv* zTy0#a&6>fghkyv2yGC+jOc+0VXryIvXo`YrW?Pl$ZC40ZR%Q+x%kQuA8wF#Bj{18( zOM-{8{GNzv63jND?sERhcw13R?wD`h^rEE_4$&>p>N2Kd4rtf)_x~_Jd+e3yH@C3I z8GOEKEFWC(-JK*r(D`hRrsdjp_n+ahr(T5gH8q%T{&;4xwq1Dr1Ckl9qxUZupE^Rt zz)(QR5TT*2rZ$Iz7Z6^cj9GxModBUU&wuUYa5JKYLljUR_Hqru=z!&l1~IPQIIpnq z))v0R;^){I@qTM%z^dqf2FRC}=Xa0;>ip(# z1Vc1ba0LKu$<41N;z_9#aO2c8*Bc2m?#Ub__Ow}&%r7U{(o?gCuX^!Ns}H6Jb|rqy zPxQ=w*LvS=%3x~AD)4@3L@#F>BaA#bE=uaHmQt4d)xZ|26MVlHt+Tm!@f8WXDy=m| zmCc7Hz?lv?8uOYW*eE=X2{QNn$cIjbEr!BoUGnrt^LbvBmSd9yg)NcM(XKV?U9h16 z4D!0V$%@nLH#>9nAc%!N!ytqo@M9Sy9ch{qk;w!qG02j`(jPl z?r1d5A3JOcJK0Wdh13@vDhivWE(1q3K2`&S)Mul4Nt#9L(b4_g?2(#Pz4%hFU&Ctp zOc?{EK*)roe`w&TpWV@?Jld|vONpN&iSImDD@TIeFDRK~#DhnS+1ARR&X=z$SUa80 zOxHMz*LHWYNF4Y^gz}yL%uu7%e!Fo|!syzIbpy{!#7Qjz^$J2VqxOkFG;}j64)RZb zWE4ttDs46Zu{aX1vo4h`EgD?N9AaQ7{9WD@N4EK4`3=UbH`UwDAobfj8R41lL+%Gi zVWA>cyXNkN8`vpMw`o0ZyHJ?9nYWY>UEdOMT4_d-#8kPwnBmQzybJ`SAj>H?XwK3j z_Y>!D3TFCEbm6I8NSz{2XDS zp=6cN>dJB(JG*z%V>yeRXl)9lp?TTOI|e@VVHyX}kc`=O9j9VJDrUm?qJoFiifV_1 zYh3o!ntL=rq+?{g8{b$L6nx}y`LE8CoK?N37_jSk{$7l+rIXYNykj=ZMLhA6X(Hsc zyN-76UP_p`S{!WV3%K?$6L@pg4gl5L0^{yQm&PRhocg@coGIu~*VIM*=rrJo4kv4oM5 zwz}OIGqM%kJNAHLMP?hz`Yhh944r@@jfNRc>EKk$sFx~ny*O90-E(}H@bETk*cM7a z=&!f-D1h+C$ScshFJQnauS+Qz^3T#S;f(;mn?`M|3@P{8^hjG$5}K9NCy6%lI>O>M zycnn!XMnWhRz$2AD!V)w!wEXqX zJ0L0wWq_E}X?UZM^s643po7Enu_u1Xdcv9_MQn^2d|K*INgSp70d>2rP!l+Q=-)#<}SSWlY) z8>;Sp0{`meRsxb7H<)hLT&`T|}^y9Y}?bz&hZvCLy1u%LMA( zT%Ax9-k~5JJ=48$cmBmUq5?0FSt4CX+*v*iS$cu`ay2_N^bgSfBqhG@>M;45HvJDP z3z?{?^z0fC+URzU<*3w@WmF}shv4(7j*VR$HX_G`g{q6V(lKy9kcmYAn4w=k_M(?7 z-VZGP%j?q%c*!&`^>-kWsjgmKL7FlryS=&)*6sZpkyBv81rO`VbiXYRO#Z?%m*abo zbj2`IKrPN^dyQ`@=Yv*^=^noLewS{zpZw(YcQdoBzJK&O7sgnU67X8*x00Wu9gkg) z-KuPv1j}6>gxL1u2;Rqs!>==@&VHxUbegNj*Rp2`OhA@p2y8p+h)oQHL-0v%Yb6su zsjHw9VLr8TEV3ky_c?g129MVl)2B@KYZdk>)5&KYTPfrDt!GDb3hR{0d4;`$gSt+* ztly7;M2Vt&X@9KXhNzi#;>`ZpMr-uAC0vbmRt;7wT#6H%Y^Oxc(i*baUIt=Mx;wjEU)5Do`*_U@NpG29aM_TInm{MngE_d(Yc4VL^`R|J*H@!tY^d$;$ zm*Xa`D!XsD*EdW3IP(jdfEmA=?HgL)Fk5}lc8T9)ir0K7(2Q7)J`kBE(}_>wU+Pac19n%c&k>^}$G#?YXf_XH|T^L(#Y2=V6P4S6`_ z?cdfb@LIfs&{I=40gM{g>tA75KMd80NvF|HE0SbaXf17vSf|akH_^~Q3P)ovmTu#O zfzL5UQO{sj)4!2KGm|7{@m0}c`gR6sdFzVao5v~8M-Kuy*gf2M#EB*t^Yv<8G4dFv z5)Fv~SRI^?P;kWOeq34i zH9rt=*tPcd`hUh-;O&(#|NS``Y$Skja=Q&vx1vj8?a>Be732_n87ZlYzvsIcZwVe1 zA2EUlY>W3lb^5!zZ-Jr#K&xc^YkO-|2PqR2GD$oRm#PK^cB-lt^|{BO6q%|!GTi85 znvw-4u}js(T41v@yYy^DHtB5W|lQu?`Lxgp`NvXgUx0m^C5VSw^`!909Yby zZoJUR!$WP2h^YKc`pS+oN@5vhWdghRx&c*sw$5j*OzT-+$N2!C zJ5g#bf>|<;w6wS#-M2M#TzGhi2x0pkUfxb^^4@RX{hcx&J;lPne}}W2GZXUNQ`T{@ zQNvKzzodlC7IJsH7cZq@H>@Bz3 zn}>o&V~R&%7_}p2DW?my6NXm5`P%UW_yh=@9iFj1)Y~5vUX6_sxoMx=4S{Z%$_&~b=VtQoQYL7>Mf_PIB?gdsEx$Yy?HD^N`>>idwTMZ7xZq9+KyNbJ5f3RG zHcxT`?o5Ud0#@2jvirS1y{$0*!V7DmYti2A>b%x`SGV;m0?7XTl{@tdnhJ>oC^mcE(8!(zk*xKL(; z75qBd?152lIFPd*jBY$v)>fy~G??l4FEH#LRNfMP+i!PfG7XHurqjxEw0v&PfNtJa zcP8lzFzU{(CA1fLm~}tvs&eb)#@8s*b8}T?D779d^>43;fSeklylIvaMM{(tYe2(@ zOZWrf;X4#1hH^#f2#wLBOb;%0cFr+A7MP<$P7XQd`bO#JFZzNw@JNcBOh$w+5r4M@ zn9pu)fN~Oue?gVVeVG`|@&e4|+w)hd24qKcx0+=4{?k=kU)0r~F{5GFOHZ*kZ#w>s zvKKu3Y6GNzNCPeR@%@SL1~-PfczkY`#6d;oI}efMo?fP61AIJt+jHxZQsIrND^xIB zyzEhvm9-faT^dMnukv2!5U`T3&e>{E*VY~kZ2xmJ=c%u+ADqIp25^-^?a=k>lf5I? zy9FyAN4Kjx1}9`SSK=V&SuJ!^&Yw^NpI18!;v@AzPmxR3_>6D)1|BKKZ!FO zFd1&15L~}pJwKFyH( z(Zll3WsA|$K5>cgx5YY~_9Q!-RrC9Sqv0gdYXhd>CAsl)M?aAIv|>y^47jWff7 ze0kn8hlO&c7fFCz%~GCg1Pa`SyL$?8dVfOxZU(VP-@ znm?%{c9&>z<4RV-j3`T8aKF%WbMKpC5cE<^6rJ~4QckYy{s+KZn2FqrTf1|iU}Ci1 zY?PCi_YIUDLiD?**qz`aaK7@V+)%%LY4APNlN*2#?&|QIGTpzqMB9fqDbr2X=1Cmx z8ja_9hB|}&EQ3UhJSuyie>C0=#@Y534t!Z!tV(>3i5;`;NZYJ-+vekb4{=JvJ_f)o z+LVi9HFU1ZVMc~T=Qf@!CsQi!UKXm&%N*YQ`_szm2F+jF;*$=xJ|-|=*jRps=FdU8 z{=PNDa{G~3AGJwA!Dhaj#B9DO$@IzHNq92QH`%}0?OzdtQ&`VN%gTY)yrMwTD0ncP z$)v<{p_o+vK80lEoS^P?gJqP$#p$`Z00RVspjSr)-W9W0Qjv>As6a^k6AN_b#0^JAc5uXiSD}C z8$33%1oi`uiY*3X%p%dotTu8rZ}0B#Rg4vxx^}hM!9>T?qm-+&k+mG#7rLP5y|$fQ zKnEhZ@7Z{>JESuGjxOg7%!Ulx<>J{|`&<_K{bu%!obF%W!lcQ?jM{6ts&8*nl;2dR zr&|HeKLm&E?mEbOKT&g}Yy$QdFf{16wp_+}>da$=Di1I>KoNim%S4N(q}nIf_>kvl zqh^QvBmfCX$ok=eocWoQ?^OEO589?%yO}>LZ~1P|5+cWK?l^W{rWZ#7B2@fo<%;J* zm>xx!be-Xfh)x)}>Q+%2Dtze>aeUi2rMP?V^JERRE8Cgy^WS zO-B_>?I39$kHkcw{Mz1NTEC~i(Ve*Lx!5gl$)Ou|O=LOnSPjQ0!lgE`$9t}4(5ufR zSE^QL*^fXj83)?K0b9FS%Le#jmwCe-v2l~SdzQ*33|o6@wk9Tzjl9$Y9HZ&@e&N}8 z*e5V@>Nx5>Suo|vV3QwX+Rh%<^6LE8k0u)X3MHt#s)B)r4u)kJ>|M52vRT23ttYAAs}RgUV6g~29SsZQgKP1v1~P=rKyAz1JOP=&%BmalGv$~ z%+Lp8^QlEdb-iuN(6b8HF%#Whz^(wwIlA5q=rVyK&F*lzrMEYnY)YamMka|vCkK}a zJx@Xa`2zjLtIEnqiLB96YrY;SbkbMIz3L?=cuT!UZGf6+xGPINJxt{x@^qe zdx^w_P0voZ+Z?{ez!yxgva;W8hRe%`5OPIDCVpS8 z18vLs{=SGeRi>obTv>unVBqt;5jr1wH`&yO3Dc>aSINB2X)CjhzkcaYhdx=+O{*Mu zusbrh+`{u&-q~3ML(XvMhq`x7ydYX+dS>q9$E;9(`;A%K-E*U5HC0trG`vy1Y-Y86hC_f>cE?=VPFh@icGKNHJ2uu{sLs##{0Ej(q&AQXEzw<#gxYE)COt#n-8$OR zBQB$)0sZJMZ6mu+z>!4LU9G94h_3k1eU0XU0&FD9+NP#BNeq@9;CN-iZM<}rS$53? zr2a#^P8zXH@M2$cavYN)N#I2}JDTe2>+?dv$HUvJtPJFPSj^q?NW&-O zrJ){7YIeA7$6XAonE}E&bb?Vn%1=Le>46;rC^L+AaQ*yx&dP=-HV_;*+s`tav8D0u z98KPBw@Ed=3!0#LhG68y0wu=6Urb)#Sxxu!zNBfe4tM$x)%%pc)$9P@KLDjfCojjB z^NnuU$k~O-yyN63J9}<_4QZPIti!XFWbE%S0e&&ctj%5T$7eQP1^Ukk7xTL{aPx%6 zJJs66Glg_qyJw&gHf=M|vDFM_LkmA1p31SNyW!hy7izmkr{jU7>@nxZPmA zzGRVONgS^{gX7hVw(lbKeBtk@l1s(90aeH7`%52|cu*W_>yC_)UAt+i6Eo?(NHFb`F>$5HlG_Ax7bKNhfCev0`L) zPc1gJQU)b_$V(zeWLD@BGcAXbXD$8-`;wXmW>ky;*_^7hu6l7Z4@6bo3ZWe zb3ebzud_eIS8XS$KX@dl)R=#qowJm#i=!@=cuyk#R2E&dZsjf_)aGlbbYZ!shs4T9KPc^Em@Um(QK~ietEwt0sH(~%4ig0lJHQA?5$9Q5F%(|X z-b4PDM-^^Z*jdfj$!%Pn0tXT@k!!TX{#;HMHc_+0#KV{&J@-jF{xeCR%?OwflI&RC z5KvekLcwmn*%8`OwJI~(rLE#TI`6mhj!u*1b0!@EIsXteX)h9{FveWcQQ)M;&7%o> zXD)L=9uCW5LOnFnXR6IXMA&X?>G^fqm1M)L9n;BUc?-Zyk-q_N$KBcPPmi3;EWa9& zR^|0eJwC`+d@;f8|%`+8G<$oMsdd$307gj;zDAP0=ta=x*ox_qTMKTt_d(f2Hr>TYdHh zYUqU6Nc=a1*Ed9aIG;Yo%WQ7;exPfLcrUgynPHi5`5P15$koG56L2b*BV8=B*H^SWz^LiZVJ0~%xMyQV_@*(k$qN&g+ z9z3~X4!0Au9lpdpjCje(R4z6K_d$tMAwPgESX#)weC_ymqXv|FRhm_xUm7Npgz4?J zV{dVbF~`trO*Xv47l%xwxSQ*fqp^-^m~t%?8uP{_ux5?E48lVgHo@V{Y^H=X=D0OQBZ*7 z6#gS>Vv8V`~LZ_^trp5sVV8l`&LaC}yp?(LOndMqcHzE5`lq`0cOB^X>`p=u$ z!Wwgg15Z0eW#!&}l(e8~;+2n7h(~RLkYO#=@lLZkvAGR?n^DNfgupOXDstZ9lt;Y} z%NTVDF_GhFXxK$$+*rFzqI}QfH74P8)vT>wTzfjpYiDOJJUqN#{9#E&C;vccu|lPZ3TaV{(OYl-Z3La9upH4ZKX?{l!z}9g2P|b zEg%xCz^YmcjfoAG3tPcv=orxx@P{Q6AR!~!><b#+wTEo%RA7T& zUQro&dL@RElc+9)<<4jB&PSBiCA>@H-&XJsnBBOLqg-S|QwsRzgw5X9cno&}_R~nj zfWjK}E)>T|LV};{D4m&!dt7t!5h)P1Y(VDh53~A|)YN8v`^uRIAI`O+DN9|km2LfG>|ZyA zg5^~`y5P@x=;_&teSZ=C>2Yj)*6%B&VGj&;AbmB6(LP5#)iD+Q>e<%_CLYoNR#H^n zTPE3M+y=4wu!oD;`rzF!F8G!z#f%8E|AebQJpa(bnXTXqD^!L{f;>ayYcV=njBt5y z>U#2_$9+>e9Wd#R9FU@-R{4g5^+$-u6@{RpsG3S60I1EbWS6f~C2vhv6cK^sBsS%HVIut zSu;eCFvNKha|dXq9*qD6^TN1Td9KzK%GhaA-*sCCXm1V`wAZjs0ARbK+mbFgs!G}_ z@cFjtr1&PKyE}J-&!mSf1vK*?dS$53f9oR7)o55g^v_&*Vg&vJ<>jOdQKTkdHp!JF zAEORnd&-Il@e}?>B2MzUZIl6@VE>mNV4|+q+iHNS3i;)>f+;h`WQB9Ty^iZ~`&d=v z;r1&zB8EhU$TW^wpmS(lWIwJBV2Sh#y|u!Z0s_=o#zsd!b?kwj-c$7_5c8?T17=hKtzP1 z332#iJl9)OQ@m>pb@`MxdU9d94RcVQnc5(0E+$fMZCH>z&Kno`%ps<#>b_JPybPOw zVqMclBQlL4CnrJ*u|0W@TAQf)gAKmZ>bZu01osB>422y;{b}z;YeQU33`=;*a8k>Q~0ocv7>TS~d z$riL5?T*Tcl*J|^6$Ezp4hwWNZ!VL5g|a$eg|ePV7(d3mVAz; z7-s?B##&on@5U2xcBo~z+SHTQ9PIC3tlIjMHBEzfeX+Y#gRO-E)cueE-lVH(>%ykt z!(86xwdO`p3`jWc7wfa1{geAcfm2unzB>X3<$S;z$7#7?x}r{Bb;Puz-y=E#X03`n=FU5wFP_d-ud}X z7(pCwCiIk^oE;^Q2v?xM;ypy)fwIPF?nPU!Qc968o%LRuMOJ-~(L8?U=sEr;9|c<@ zPPf&-Ve}W~T(qO*j#G2-ce0Boi5OoZO@p(O(p z9ksGE)cBAco4#@fkXEE8{BmQH@5btHNx{SBrm>cM-IDtaB)dAGrmBpJo+_+IA9c|& zs;?A?-0iX5ItLz3Zgl=jyWTE=|m+dI_FAiHX@DcOTUU|OLhlUYi z?7m;wbbqiu;BzmrvbTp&FMVhP3e3(dhU*i=!6&H38)JuVvDS-?09`(pdqWfLDpqHm zvu&5TY`z4GnLw?RLg&6c+bH;Z`{o{gbAkY;><2vJD&#Pm7mernM>Q>`!Z>NFPy#yAbdK9&*YJ1~D|z!3^ZlnCLPOr`L)6UP7z0OeoK+0CqWrw7 zv9T~gdj@XWdpzy6#mk{)Ta)?CU){kPGU;xW)N4h&-l=Y`SmJEH_j|*iS)n{Od-C=O zJ=M6|wm{S9V^|I7SZ)#2Mvc>m?gr`2Gml(n3dk4EAY%>2V@V7_PZ34 zmzk#!7UB}qadCW9ifu)96wfE<@yVkVEw*5eX2skAmVQfSsyyTP77qJc6*JC-G}m_p z43GYT^RRGVBQ~a}nm!c5J z+Y)HSJMLv?FsgRS`RKD<0S{=Oz8g!1vA?Yy$@SSo1C0vij!o{C-x1hVr9!vf?=&_>pkPWs%8Y5tI2sPwlSC<@?;=T z0qtJr6>9!?kW-_&e};6av4eOHB#EJT)`Yi`#8ngyAK9F~_`fq4nxPEi?P zY@e+pXoUQ}GTAHYS3ODfc+d6~Z_BQ||49{2(P#!B9>plW!$<3Xy^@*rlz_Iu)hHM6 z60-IEP>uc0tIozc+sc2nwI3N6v)#0Z#MIQ(%C6HDCl{LM(0p}2 zs7FB!s`(4Vy65{pW#Rp2mG}b2;v8Q*g_$*@l1p_BHpgE$2;UCDJyTViQ=OZRj>dUs z#3)|WWyYeqMsBn=9~?WwA8!BEBaL1JkO`NLM)UDsb~ayYA@epC8P9_bCA4CSoJW0I zGR_Obai)&*m-kT|v%_0+sTW3*W2fvrq8rsW1?U>~AH?7AJsejRs52Mk%kz8#yq|%- zK4fcs_~BG1i3TN9R!&aWn2JJcqGN`Nf=%Wj+R@NSBqV9HQr5s^?l^o@ewLJ($jZtJ z7U$`ip5lI|z>H`wA0I5JzM*SjBwoHrQyK5yKd>ubS9RZ>YWYYHrUr#+yp0SevZDr+ zZ#W1=(-!wfNlMf@*e9^6U`Fd9l9<(;pHS7)XP;%SAvI8bZhO#uEZY{vL}bn|Y4Aa+ znecPBY#oR9lt;$PSD|WdpjbbPWqtmX z&H6}7fwkrw7-8+8Y887+%}zD}X&rzCwAxYklAN8HvD=+aCJ&#DyDD|vB82+%n(;0N zg$5JMj));$-jrvz3%(L~-pI>?9GKQq_)B>#92Uozh4DXiRI0a^X1hdC2{E{`v{or8 zqEDY7ousn(yBP#?fw$aLvNEASLSXa>LXj6RWvr(2qvnmzOnk5}272F$b};j{_Xl};@@wbYmDao!pH?TPN@Ux}#+>u)!U%`#NE+w+^7a);%tAG4Wu2Bw z1n|adLsD`l^N5l-1l*;y?ZFu-HwErLk&brH+w-bd%hu)hA<)i=EajT4h={eRl*W>{ z2Vk#(#pQOF8Ya64UXG3Ttbrcx(VDz^8E1`61zEVJ)5Os=X9$tubx??n+fCu4dvLm1 z9WttWSlsD%=^p5l5m#1|2N3;|oB9u`7*u!HyNxZN;owLb_dRNEiN|=$5jg4lf`e{l zWTR0K93k>pk1EyZJGFO4^BM(7!o;$8>g@-I7$|9J?QAOTElO)bjnKcpeM8tE6cUVv z&TGRGQ-NF}!ikVN5t@*sYifA3xuTS_gaN37zk0&M{Xmp1oy6;Y|DCn~b(XdU)gU?a zsj0d75T+NI?pUT$^X8foii*COmro)U(Q!r4m_dnGFVO`$pUTb1;q1)osV~p!&_^-H z$}z1^&S1Ubza+_RZEY9J@29k!(KQ3Z_;Eo>bIh*Y2-zA#PO zI|x)c=3f*B+(5H9kX`_AybKb>MT{(vl|CqaYaut9A$DeGW%Ih|J4EfEG@;!xPZ8*6 z)tYS(z7!C%ag7bM&GpXu+#5hLcYD-Go_jz<*=eB_kgSiDs0P)c#RnfkOF7EwQ%iG8 zOGB+^h|h}p$BQXuJ?k+L-Xn$E)Y-}z&y!HhKFeQJXXZP%sPY`2yp1l@ta92!v>!vu z%Tk~SF^nh}0~X`_{K(+Yx9%g#H+6Wizaz@zRh@_xvc;2&pp-WxK(uy%#9IYgXlb7t$S0Zu2voss!K`MFDuEcrac1z@tg9P}I#7dLVI&1uhjUVKBewdYN; z*?97(R`GMoqAOggg$Lt?_|-AH-@}JMdYE{+;{l>FZn#YS%?dyxm`U?;^Hi+}b%glpQ-e` z=5Dy#Hunj0RfN-riRO@dXiSyVaa-=`6g*-k4!ia!Zcp+pZN*;v?PsyDDLtCa0#M>r zTm?27uA{(2!ykl);n39Z(NQ4lo-~d;IM~3hds&ugw2q?fVwpjTJaudS$1Fj2qm zkciRft!@*1l_3Ubp$@^M=;Px>iCh~l%Uwy0$=ZYcA4jkX&UFc8<#V0A&y1wRZye9M zi5HWTt@=!s&c`>Zz*JPV-#7W0jQvkfj~soBA!K7-Qj%mL4%eruO3A^fG$SJ;D9Bb! z3^*?dm2S>z>R$4FqGygOTGS>42s04dl`?=`ez+5yhfc%zqcB%KZ=tt+8mw7U&vqmE zEVF}sME=&dt0gRgJLEwLpZPUt=&66a3z`!YmCifwm2zS`Dox0nFw6dYZGiC8sm$hT zXrcK!R7de@KRXhd#Gxk=#`pHs;F$)qHT>CJY(2&s5GhxGLCC`0EU#vJC;U|E*(L>2 zH2Gv*YJlC@CDr6;Y`1RZQ%S8&1Cy-!iGsP2gN$qV4lw|;kDuEvKUbiog*wKDi3gp3 zCXMLHmcl(+UQXppCa_Fa9 zBSv_54RkJx)hWsYS$OySI1Lf>V@k#RNG(;Oo8&K&KWi&$_CM|t;1UjZSQD2uiY!VJ zuyCl)O!%AiQ3yW$yDBJ2=iBqG@PyCZ>ZD|LBEr+wrvQ9(lN)@nu5l_XDiRgJ60h-wNX_STrh|Ywb$Kf#B_Rkz zPL9rax^2O1)msh=OMYPDBNkQ?m+f6V7KY5gC0=8E<4MWB-f#r>X0B}M zThj%;_+n8sj!ziF<|*uhlY`3EbCn`&Y*Y1zv4Z0E-{nyrJ2`OS2tb57f=tWqzphl< zou`En04nE9By+rdpe^CFOG{Hx^u0Yq2nqZ&;s;%Ig#_}J>k;3|t)oeyfb*A2)&ELe z=!Rm$+PtG@rMyFi=g}-nmJ|Ih{1i%O0W)O`U*_`Rb}oM8-c;UqZr>E~q|#w>7I4a7v&S zyEHU%3e^CnVA+ir<~YB-V#@a8O);;@^f&D_W$LJ#SNOy*Sb2H)yWJMV8z+ZaYd)~O zJ>ojcD|$XeIE=t9qyZ9=C$0i4V^)jx4Y$}ir$xMd?e<3V)PKi_g4g&-+`n@m!q4lk zB(}O=lYy+OXR_%@%VN_Lq$`-vFQlxG9RRdRoP*{mlGm&^I6kVnG;(Qn!7jOzCx+z6 zQCQW+u~S`BvjrqDQcoDn7%&_TpCSC@^yps*@76E(xVuqgP*hqF6-7ZgGnDd5w5O0) ziVCMLjsQp&>3HoggOSL%Y~IE?kl*1UK|B+GW-9*&CS-4~vX~?qB@YAe2yo9p!%jj! zxZPx00@OfLT-7L4baT@Eo3e8BZC#{pTeg`?yVtEJ|Mlev^;%c(x;%K9H>~2ab>98= z#C{ToDoRCE6x4$!L^UH#4`tcu(a}_lO-Kq$Na~qIGx2zwp`=h*&*7JwJ~0@ezJnz} zM(X==^&oPsSd#`wEQI;X*`QjJ7nh{%Ee@*|)77-UU zfHd*)#jADqphg#^G4ku-u(!Zt(6Bn6V}CKAVQ+PM-^A-ueb!H_y|$TlG?x2Dpw=lsj7c0OCZ-iEQNzaC*331`{lA0^BWcmu&evbepnLP&@uuc_FeJJx)~D%b367XpL493T#}IT-az z80hQU8rP?xp}D`1D4RvhSgMWdHt+3g^O6DW>DCs|PK|4pRiDr^?*0MB$%yc9Si6ED z{UXj60Q< zmd0{h(v}>clvPzEx!st#7tOVEVKV-c_eW36$>*Hdm+fst-2~eEaE(#-6D!;2a1iu3 zmT9<_%-RI=3lGOIKm(0Zf=Pd86__v}dU|aDO;#LlBO&~s)%*Czw@KAc!x(3KV_s~j z(vsrR;?k@JW}pmoo6bj>csHwyX8nr|DvU9qOnmYP)K(Qxcd|XUfkc&Gv-!2RY1Low z=$JFCq=Q&1=Dtj^Z`YQkHT8P4f^Lie_3LpZopFH>8SK%jcdHmKtanw+T+xt_lzvl)^X5GUS?_Rx2sV0%`sQ3UYcc=X^2*9*eOW#? zM$qJd*Krb|&Ti-37>ra33(V z=lZ(Zes{MdRsQ3gKBk`R%d3OiCEEYR0-A_Uj`xk}?VT%_o8yu=>`lUbMVc2EoZ;K@ zMJ5mi1_s9#0c3cT{><5HnIV3=qVQtTLJ=~nsXF=0mb^2$nuRwi*I&FiW1py&3 zB2s_Hu1J+b6Redl$$@7!144N{V)0xGnEdt+GzdnL>Q)|(4h|p%jN^h|dT<^opemi} zTUxhX7wMUqj10q-t#t8^xY$@0_mR;juFjj;qlAPn@x~*F#umaO!V!C)twVFzcXw$O zIv@N;+SXZf17>E_-1}pb6!yiMfU}Z}XR6TD;QI2?K{dlphJj&+1BcC^)*B8II)@Pu zDEnt>r^S`>k^gzdXb#Z)pUMHs}y1oI&$(`v&mzkl}f9Vxc_?g zXW)&sjx0zII2+X9w9;WTI2M(XsBi{x!PSs*xuf|I)=#BB`1#W}6Wc_B>C_t&EqgxuwFDWA$d77~AM}%6QUPa8| z&JH$4Q`gge5hEjvAS>ooR!&xDm$0w5a;_=|H=?^+q6mJwH8_k>`{&J7ywPMypj|SfanCQ(H0uaq z_(Zk$h=fYvh(C4X^4M=Cra`I9jiap`*y^7&QtM3yUgt^fTsk^BvzFG6r&#o1d|#WH z7)RXt>4@v~=iRPL279jT2f;z1Ljgx;JKZVn3BXx);mO18wBxn$1i_+v6s@o4#dEZ9 z^0>#Z3@Z;ddKK*?XB8%G+1E4Z7r;x3hgslVuTmwhUG%<}u0-b^Wk4tnH(H;Zl1veC zIkco1;>ZV&;wZrxa9yc{&PWnb9&GG5wpHm#*&qMqxUj4=pUXF+>9d*Es6Lplbxhbv zeiiM0#wZlweDS3*af?5YioyF z#*FA_OAi*pP-T)ii;`seREi0ha#Lnsy|Ow|0;gQ1gkhE zZaz?B3}dJh+pG|xY-cCzq&~j$RyV3`Ay@q)ZjDpbN$&>SaASjTP(niL?8art zw&kBTNJz*U(de|TiK4|Mn@twnXb~O`4o=q`_$CL&AMw6U=H*H?xVo%Q{z#CYURoNe zWzsh>dDAyjS$Xz5B4@qJ0n+x&79M;5gWvN&aP9;-n1E-0{7q%xWoR}K1$ zGJH$sQD&^$n*Kvql8kh?nVt%$Ho+ajB2JFZD*5tBnV>_`gu1xiJ$>rs;zBnfDBn60 z8A(#y5*rouk?ybeB@P3FIjflc%%A1%F$(e16AGtJ0z6xFZJ5Cw4GT6zoHIF|R?J>C zE8lRa^J9CL3BpN)bZSE&SKP=vrEI{aRP{7%&2+vHvn$s^6g#^sGT6Ciq%tO+BWLk<>R=bT*xg2`X13P3QW=56bQ?70Iw&#SleR%E2- zCnx>^91&4bxXK)w$qmZi*4Eqqc~Q^Ww;P<5t`5Pi(Vn!ruR*D0-D8LHtW^{As8BkJ z%71Uj$%$!*pzfBCkup%=hN}_y` zIzK-@%Sj~a5PTl!#UR04Rq$_i2!6L_mH0;VK*Bb*lwKJ@fWJRfwz+ura29rV@8!$H zbdpp!4z>_ihy+!i*_IqG$B%EnP&VLPk)oYuuDXn@Cnpz!NwY*D>~Yc23{4jor>0|d zFgHdH4mCuCKbU1BVWZdis<3#7P;Y|)Izpt|3hS;>3^IzL;W0gjmwgWz>qoWL?bQ-g ziYK1!^X0^OmL}rzFq9}zAo5BzXre%^SWO=iJ`z^4zxvK%l|n@4%kgtCJ^Vm6-CA2A?l}SGa=U}Y zy_FRz^g@lCY=c;A2xwp9$EuI!$C|WEiHL}1&d*NjR%R^&uozTIl!KK(#$u61PPE$= z=yS5OvvG-d^<|fOqiA-pLkL20LEV8&5ZK0Dq%qe4!YfhxYz+mO^8 zOpJW4jfjvc3XV~Z;Gb26c>n^d4t4SfKc01%dgOVc7Qx-0=V z{w7p~rbLa#ConK@pH}%fA_}q2+pBV;EusvV#2dXDAG5D`Ym5;7NIeAD%c4~>6>BIDw0b(x?ma#h+CFAr&faQXYMljP)Nxe>-P%fISk z$8dO_QihX56*Z*qS$m4q@8Doyr~7Su&=t@+G*qKJu-o$WtJOO&(nyMma@vu!Ud1*2 z+vEMY+FV&776W-IIlehRqW$60WaERNwd@^?4y+e^3MK8)w|daN(oMnCx8=pFO z+OL#Oy7|~_KVG$roB!9}zyFJ_gO7jW{og)0pAPrE+I)DE^KNT4J|IWt^tbfd|uC6AE6co%14GoWGoWCF< z*vrD=v-UX&EENUhz()Aq{rrc4L9|aFj?PD97Z(@Lr+uC6oyL?H=k%rM;CpNPV_4r@ zUpqNDu{&-_21w+**j-tX%ad1isLBWP!uPdRnM8I+awcjD3K47fDwE0Wks^hl-3e-C zGzlrGAa=*=KP_#*BSJqwv~)~-cT~Ui`EK<$4OCKGd{X@5jPG!hqOq=u49`E< zZ=uw3FMnchb#(~q`j0YQg6+vy!oI zAFt>E<-dB3u7Z=3&zeXcTfW%ZvlBWANy*t#<38})HYQ9yir=R=pwF3^LeZDAN*+{)C!%=dcM^Jv(+c?(}gCz zi@KKyGRfRUm`W4WQ3Xl`^7QQ8ZDXfrJIp{+5Eu|(zv*GSH=eJ^C~5bJqrbf~g~#d4 zLD5DsEhOA`JWoL(ooHs}yWUz>A(!p7CV$9HcDP^K3X<0{oD;j}gB!T#Fh15_Kg_UBKLn~^>W`M11ZnaEenBb&>S zPU5iRCiz%Zi)7Ukt}@juBQqcn5x)z(iY)g3hodo^oo`rgl+O?G;6p z*n5xGY|Pf4(S@oV#1=~Iy;rCr_TGY231SOD%+Gzk-^cq8@Jk-<*L7XbslTcgc}_Sv zsX@IE5I`kLh+bS=l(R_aiRXC`_A>66fgju>Kkf~=9Vwsw9wOQnwNMxwJk=O~1%(oB zLN7=Z>#Vq)9f452y5gjPL|%^2@q?8|$;UHue?g|}+-I5{hBmFNvdrsu6S`@vYj$P*u(;0v$)vzO{AcEST$iTe~h5FX}~&!8IPvpJYbdXU`gt~Oqw z9gA^CS*1pCUcYWpj^Oe-1%;sX7P*sE#&9Zjw2z53+L+x zPTMKEv1RV_%ojXI zw!7Pj<==|jLrg})F-5i*u(q7j`oqEDR4L~xnF-LCg>H!hQHyzA&CIUZ>bw5E1ui`Q z#;0h;DR)dWGIl63Ie!mkUt;rsuK8(Eq1TU75-H8ScdXt8%X=v?f5(fB?AxN>Vv-Nz z2ql#&FEuk2N7=K!GqDpk)<0s#F-90Y%Bu#uP@{g;*i&t6(02T_t96yrVqRax+)~p! zoL*DhZ(2mi5C0l;{+5Wyi|aGT<0I|1X+a8FXsJE0Z#8?l?cs3t%~m!%_*?K+bAIN# z#Jw^-zk?l$_GaT}Cbeqn5iN0n$zC1g^pi8Ynsak{MvhGKA3P@DI|TA~$S9_+3I^wh zCS=wAdeod*1cPklNLSCUgryWN`ea?m;)mtGv@WdgbB~F{84I@5x@|c2^0Y+cBsq5q zs0nfj{IE*zvprw)VZvyqdMwx65u%wWWIgPKGC+e!+v(WrGviw~sMC#2lA6;Z&FbC( zkEV;eCqQatq0Yr5c)}gQe79~5IMs(VgmceGxS3q{R;}Ri__HZJ{ifylDuUHig>jF9 zi9Y-0WVw{h#to7sTSo`*ocZFbC~cNJR;J+cLoDFeoTW@6PTvd^e*C!4%8gIqOqp2n zC$2G87|FL^O4X!r45x{^0cjtMmw&Y7uTAfVSAu^AZ)wiV4yD-f;1`N4k!m!?S3DoT-=Z<{4cvwYBGEL?V${yz#nEzW`99+B6p_ zwM@IWiAz229d2Wz*B{x<1pdT#XUVy_NDZfk4LX2!SKd#PVN zG|G1A`sEgpos438eFs-p-gYQT=Fo51W1fjI9 z;(&4==;+6#EGr)d1Hz-;aC1~ZoxS*o|KUSpx#N8~5XcAnk^-NCpJ+M%r92SDm2*pz zK_aewSiIP1Trl8AZFdw?2~%&7^u%pr@y&Apv$Diu-iA zeI?C{E-@&7n%Z(9Jy8&Hb^ESqKqZVagQ3Anqs&aSCU6qYk8@2Vv_nzz-p(_LZs`w> z*JZ#AUjgVFo}2DfM&ENfu$4%#rlB=&*olg6`PV&H9TeoN*5t9K)lbEvrDhDr_y0&z z&&k2HUD}v=2cjYfD7X1M-30E3w9m|nE$<4h8iT#?lWm$?k9{VdHtB(ctXUBN>QJlq z(ua>%Rm?ejBPKIYR6=)EZGiNi?WDZ=(_arar>X)I>PnAc?x@5-V3Nl$d6yMivoqB? zul}4bg>Vc>?+2Y^!Ca2OceYUtR`q4{YIP)Fme1e$&~f|;m{WosaLB-#=IUIY4|fk% zanGVbtL(4K02+h25*I0O^Y@@kA=EOd9v#CX{^8f952G&us+Lf%e3%cMlL<%Gz~`Z) z3(uB)uS2=`_ev~_Q&$g9irakqb==z8>Q9lNjh_p4JSs9i+g)kijE*CgwLX{m-&zlk zxQHSTJL0~7FGxb5>Ye*w*m9^beW0U^^ga-9a=eGB8EpY}p5XmqYDg=jdnH|-`S7Um z$oOUOQBF%w4E@?EsCs?D`&<+4oX6m`Gi9scy_j*4#6qDRd%o9Hrd{D?_O|Wf2z8gp zOmyhemlHcs=wpgDR&!SKp`Zva&|dOADJwGzzmnc$vb!&Mg4&eG`H8b&!)?phM|&?B~>s0f1b z-L?XT9=KV{z)M?gnHzY}6yXy%Ed*X~2@ci;byQZ~muvRcYjO7RIbI4Tg9}*8 zIbMZ$oW_jE=r-DzgaVN|?~x2GiJUXo5qO+6p0 zrg1OlR)vZ?b^X=`T^qIgtq_eF5GoAgBkiJwJ_1F5s~uSs3^yGgX0Et<1=T|fVqCd? zPYC4~I0hZ#P6#w^^Nq`wCs7plq-Y_Fz9+6H@)wEF599i=bP35RDUA}?)U3pei=T2U zTQl+!^BedG3QO-x=Ckg=z25d%p7)r8jyT(g+ zSLq=u*+|&J&}$%)#__Gro8MC_uf<&Fb=tvax4z5De7g9AHd+pgHorOUm;3*})s6nDpgd4;wS23RS?ypFul5Gv&R(FQInSx@f%-Goxh z_A$SMob%o1Z4Qui*{jJ1=M+r$|Nc7;bk}yqY=p%!BS?6B_TA#91v)b+>GCqRy9`P}T%-L0q~u99eue$hC0YGIyDjTv$v-l|S5UEu6CFeEwkMpQ%s$sl@6+@HdV1d3c`9B@3t)tpTI7-X7gSrs1){xS>PuX6 zZM(B=b@QrV@MYlILT#<6w=Toq9=F{w`ACMw>|vRvS~=3`Ep#N|)7AdOUQtmI&=0{R znH_6IwJ}##V=(x={fHdEZGo$=Ma^v3i3v_UH_jPXs$k~)7N<1w>UBEztt3gMA5$JU zMF67ay32QT#MQ^qpDYASa{$V=3woQN0`$v(KR(hyTkraxX(U3k1q^U%O^}LJ-T-u2 zLi=t1K5!olQjTe~-)W9UbJX5`3ygo4 zFZ4_yj8y2$<%%x6@n11v^!U%Mt8*{{z6r|+(>xg}ty%G6aL z+IVIbTM~^qvTmr1Go}gdH5c<&9oVX8~|vjMx=?>&Uh5tb$I?;>zhV zEiUd&@Og>*HP2sFYhTU6{>mZTEN>yX5$j-<0h<`sey#HMXu@5!m!*iVo!{O6FpOd( z7sIzH&p7wS2Z&?m6%tN~=_?!Ot==+EWG@z22)G$}Jc^o-q^+UWObRPGGDkje+&R-U z>tGNobY#k4q+%=(GV>-(;wK!3dyOUc_6YB=^;4U5jFa8y=uJj4A1*Ggk<4qvJlrK< zyYOuQNRULVPI8`v-K)mXg=A9KhYY8bxxs8lb2wDUfSV)yH!zj1Ji}z7gPw8E<>qav zF3kdGlhgF1`O-cRSFqt5=e#-;Ki+q|&nW|wHh#&xFqK43Zjw)|&dVs$L+SzAk*@dk z0@9@3Ji_lJlM_yd>(1m@ZqN1U zmi48id5J?bV>u%~^1H!cy(@-SCSp;kl1ZW2XZJ}@WZ1*az5~@Zoxo$3*;f0Ot4vg4 zFKY>_aI&DaZE>zzeD(W->XLFgUZf7m1828hw-~az^`u)DKu&<7I;K%7%55*Qv}3S; z*1T9r^1Y{?s~aEi2z}+$pr-9|P(hnmS{;wJWp10;okjVlPj!xuKUV)mxWntE;7!8t z8+V9UQ7s)fnS{1MG3&``QQPGnmGJxTK6f_l;gCiZMuwX- zdZpb>FU;qGrk2kS9S3&g-=8j#A#^YvE=dw9h>up}3%*!rO^N#AjT=#duLcD)vzw<6 z3Sgn&GSPyX`*#1s0%Ti@?^AQmpRQy5H$ku&)y6kz>3ZZS0FA4XId?62tcmR4R(auF z^<`RGMurCjg6@9qMvI(NlaQ9ubrM5gxxBsnJuotu@sdd&=kGS8;yS0N76x9W0j>h( zqEqfkX{$-bQ979M;>ik%`_E*ut%V@AbO<$%+gpB{U(oy&EHY|lngl)INa15X59sH( z7kJ{98KvNe{Qh>+7p(awr2VRlMq^xf#mQKY%x^oQFaNI;N?Wk=9Gx#Y^8%L6lkE2G z*7G%W;?|TnsMu}MOey>*x%ro;P+NHcLc%Hp84wwDl;cEczuAH8>Jb7-j5Vr77);}g zfr>=fLLT1jco(GQ*?+|KC&xqz`Ka0H`Y%?DPCva zdf2h?-OxFjxUHGq&}WrHR*IIxHhxXJtp|+9*3(T4x`@-l%uk=E&T*w1NLHNi`!L+9 zjF#-HKYS_8$dY*))ZQlN*634e6n8-b(YJjzyMLkvE0O9sP2_z{@@SR|JYfucu)0%s z-FqSQS~}y1JV}Z2U@b%*=*GzHSwKhSdk?GyXt6!~a($V$_b`$9XG%+^=2N?;z5t*a zEi@&nXzg@ey}mIa`PzKI5b3sKh7M%N6@|dJ&gjtsz7 zxg*@HK;7le_0hP*z_ou};0Tm=qS&ymHBfJ9Q@Ej4peLqYqlKE0R=(4rt^{}-#VWHf zY{*p5SizTDMj76(p^!Lr6^F(P!A>_`#J5^^fGf(i=ID8`5>)EJsYjN((!=rCtVfER zaooBN5khhe6n7r)75xx>^j^rSnqBa^_y!P;3=?D;YJQ10vgmhv!$hpGf~jw* z4^+6#)FYt>cyL9lXw%QX3Y7ViS5w|)l)v(Mlk|bVqFrhy1RGL({b=W*EgRsmC_vm~ z59cH;1t0Xa1f)I-_8ZTUblm*`I-#RUDA*BxzhE(kQty?JSvC8UpVpsg^w9#WrdX@Rr~=}< z>E{6e8#f;B-l~V?3ArGR{3pyo)Eyo54i`DRgoh8No*;G}G&*goCfOAUg?42tk(v6i zMN|R@v0ZDyWHZtutHLHDw@>NHy!n^7R1%gSJGbdy=c~L~0_?h2NDV05ULg7}l+r+w z2($yYU*$Jf{O#MXf@ea&%Sfo&Z^s$OG9m+H%*5TD%$_y>-I7Wj%sk)Vwouu2(3zAr z`}rDY5bZ?xD!~K4TVRxRav^uXZJDlS8sHNi3 z7Bp-9m8$j&xa&sb1XTieLuaBJ)cghOxKS^~aR3QpZ22w4Tpl<&7oJQyD02@5eE$Ju zDU7$vq-+3WKHES_><0kB&=9%lJl#!+bP{tL{MK}MEfOPquSCbnX}#Wa796P?U=(uh z>5#KH34q`6rzSPGbH!FGoftz@J71G0tP9Er&t?N8BI+4`Z~7i@2a1ybqv`)(DRTYk zzZ&4_gv$kMB+ZzZakQ}21@VNw&> z@wFM&{>w!pZKq3nthX^>P%;G{cf?rE$PW7I! z$!iO>G?z?iW+@V~XtQSjXn!{G`rLl=%UniC?dgL5H42D;05F%c_2Mdxn2wHO`e8j8-jE(Zr9JyffF4hR8k=-F^Y7epk5O{!T>6dv*`#xyk z+;ZJ*IHYGt)?}CQK)vQ(50@zjXg&tjB{Hey?N@oKC4~2o?P*n6_IU4Sw?%-#KFNt2 z*ak-pjfEaWaIT;l;%KWvgRiiSqvsE68);)&hDG+nz2Kz0YM$k~qc+u0{Q$>?y;;{l z))v#O?R}0g@=-^_ad45Kr9#N1NRQ-qMJx|e`j2z^U0NKnHm4Yv@!a?cb)I$?)r@`a_mZ_p3?d$q*p&;xx*?tpV#7R?0t6M7qjl_i&xtp8?I!U)GXX5-O%%$goqjk(I_M9O;PCtNL_F9~@k zxZHC-LVI)y-I$Okm~-|HQwQPg@x{ozQt5fME-&^fqzY~=*NA|itec+hkmKKp3Y2VJ zY|S9#D_uTrr{=?S<}LIBNxiY7Ux1yMYJF2zHQ>0)Q(kOT*nP_@ zE@*ZC3<%QjU}FFl&de~g=+?L1r0J@1bcPl=deacRz5`q@9PC;HZ|fi{clj{3gD*T<9;BZ&j=i3-XG^bFZ9Q52cxE`;$_IjTfCx zZqI(!`iB-+WhdHeXbYE(VR@<+Zi9`ukwogBvq6CJsEWXr22HoSE3K5~DJg)W5nh z<^jUE0h{yaLOmob08FWWMmiIB9e*l0TWzd*u1~nhGx9UeJr3ksx$Rvs7nG~{-AE88 zL$5e-ydyzz4nwyd6k1lemAPVuf;&Ab1>fBn_~4&=;qs7SR3)~lVxV2C+CHZ>#f0>f zSdgTLt2ZwX#7{NUsed#0r+-~pJc-|Mq1L`-g;}rWwHENG8Q{{l{d%-tWqgz5HJnZp zD=r~+a-{(uacX@XgL3-eUtT&dbf_LAk@M=$&W1iMs?iU*}h!!#$<+M65=drg`t#oiHm(*Gq9kWV4yHgbO zSuY>I&O+lap-QH*uOaE%h{F4zzbcN$PR;Y-GjVZ`Jht(uimJv zBn+jC!?1$dvn`Isj(jxz10(W92qoS+ELZZ`vsGmdcZeI>wd@7BLB~fc#$KFpi|6T` z-j7hZb)nkYz5AE9EnTBzw4L;eQBS&iQ{t5Xgx;qs|GfCg=BH|Tgsi{X{h$3wzw(F% zXslkQ8#dq2z-tfym%IHhouoTvXB#_JVtcx~)!Ak%6)X+Kfxa$g3xpVSfg?B(llLu$QY4vO3d$DW>fcM~g93R$!5OFAJCbnIMul|&BQ4J9j zs(01zc*Pe2n3wcwj?^Yl!7WBRn0hr;jl}y4kBasXl91wRbf~NirnDezn33%-i1@tW#CN#o$O8t%;C`bdQh0U?zNee!k^8yQ-sf?RFyk^_J04)X4sEAnQn0i_Uz51or(@N-J_RulRXD*S|$3 z3;qcEkLi%=JzZOOajnn~*u@6=gdq{fA6b5D_Wt2qN6GKZV<+vE7?$NPFTxj%fylJ$ z!-|l;b*{3;tIGg<^!teRI-AY^VF6*tdWi`$>yXn9ndqT&`Rgn(*E8D9*t3K5*duGN zaJ!>p8#m%fxoL|6>{XHSe_NMlyg}1a$d>&|?c&}Vt0jL&kI0C&|DURJ!$>WL1TY z^1KaGwOpD#Xs{?4=_*TLwgqxG2yXu+sF;BRcW{o;0_#odcN5I=C&nz;?#wXW@sg$E zZ)9zi>GspDfx7^?LiZmE6pRd(s4jbMWm0HD9TY9MZc7qC3my|w1lv~a82j;9+=M)7 zow#O_JWUDB+b*^x(Hw%!T9ffN z`J;Sq6!~@XoLZAgo2y`!{@7-l?(jfPLcaO{1axo9NZG=nFWhncG@Aa1#hMsFvhi7c zVPA#63e{aGOw#IN6>8S(4TvD-)@9kBLb@OQ4OEV5o z{aIGA%uUu>*E8>b6OJB@sz)@Y%YV)=DW}Wkd8uJzB zIebAyPRA-IOB-x&#Wy=H#cA)2$XYzQP^DOeo_pq}4Owr-5kvyI-hdNVPtMwj<5{KBB7-YAd9oIx|R-Z_d>FymlP z*3b&f2$r^-tfq(E-7J&p$;&BdkjZ4_OGchIrj=_tH z4OG9Tu?obQP6V(1yHE@6+-tk|9cI3#sombXHgxMF<$96jOFd$htF!BDBQAX}B%bxM zGOt%yQW`DYHKi0Y(KI0=>Y}5~dRyS(^OZ~7zirSJhk>8VNB?+-6zLXq7Bfv9@g=i| z8(v;mlR5X}jR3_5J#()7r*QitYXI{(JKlw^c&~*0vuO^xe&rX_-PHv>{DRZI!Y9iU zqs+%jn0=C`Q`_G`z(78zum)9p%Emi(%L$eIB@9qO!dL;sch6l9X9?{c&ehzbq-Pv) zp5XZ{XaEqJMAxEe+5O%LC(1Ba86P<@Jk5$+^h6RvpNddwwrGR6>ykKtMqdwv@cBlr zHRXm)sX6!OD5)5t%S^+s`h=$3VZ*mp)W`dvqQLE_Eq_w{^*2nVMY>rb>o{E~KS^Zu zosaPrjN0Vt>t`cZ*cNNRHa(`uBhvsts_}a$2PWNfpbniu-mw1U<5pe9!lH%kDX%I=Q|C+<8UB8CNZA<0BK1lhF?sZ}>y?af!vX(Ff`mv|t$CCyBiI8FCoI)_ zM>f|v1DMBL<5ixGtip2jk8m&hho0Niz;!td^t1`~v;jQR6!j9`krk5}-2sj!IQftFEGk3OJE*Mh>TUk?IZrT;}o~d_Y5$&S(!j0AzL}!oyZi<%C1I zbCL`4qZtby^6qQa*n-JYuI`DuP5w-E^ehtIr7Q}*l=Cb<*ylrjtxcPfT#H#go++C;Hy@Q3YbKd_;}u10it-5}U8jJjH-b zs|$8E$IE-HnlNsCR^-Yv64Y>1%NXXgJ6D6H%Tt>a zoLcaF@Sf(mR79Q&+99@;?_SMOA9&MT`^YF^YU89xhi%d)yS7}phTD1C4fqB&kLZob zNSk?b7gLUVjMgC<8X2xHp8B2xJx`i7f@!<^%YhZll76S<=PbV7F3QBsDNrV8O7gI8 zQ;dcBl)^N>S(^;w!T2IRNEjJ0_VK9wL5$B~r(?eu~Ex@df zo9AOE%hbt8AER=P^9opZCQ8!3@PzEQVcY@TC1%h6XfX9@8E3EYYlhT@1+TKB*ODd& zQ`3$7uY1?Uh&%Y0TOf+SWL)>2%=_mY91|}>Lf>ZTfbAG0iYxB>c*a}L#uQ<#$sYr^ zB7U|l8mdnp60)+H$vr2<)lOmIW+C?mPv)gv@uOk)2oeMwude1wl0Fw-nc8LLe8*nQpX}S5hBHk9l1~ZZUf9oG#`n>b^DtY_KpZ432+>S`^=fRDkUQyn()tIIoh1 zGyLuycv3?Z^iq2KUKxlR>|pqrgVvRY8-80S^P`P#YC`a_R!xH3vW8va?Jd33_#b)| zKH_~PNjvW)6U~Jz6ZG|5Z2vT#)|_pqDp#;!VwRHHRkHwBo{m4+Y6z)qKnw;e*#;|K z@5uP>?sJ_@C5Fy7iNV!Kkkcdv zrsu)nh)B3J+Vlf>A^1}Zfe9PC)bcKe5ICJQ8=LNsgkF(e3dkM&$f|wu&>fbL;HjPT z_kBGf{AtABbp~MuI!I8Z{}ws#S25$1=F3Sg2u4lYQ%_F~3tF7o2t<0|z5|CgX|Xb! z`o>Q~TG2Hv)u+!75<9If1H3tf;1|Oa(vj(le|O3Y-Sb|%K}AiS*zm}2f@u8MtKnq< z*)EtE45)+%9FM?5Y0DmiB?eVCyH7MJ%6wzXBjL~FjrOzEsUWOsHLreXK-|r*?X-Iy zsHxq^OUg)`tj%r}|FUv8VZKK3WwHr#b~A!qgkvI+R#nlJVaL31---0Pf>qLM){87c z;3x%y!CbwW)fC_I=^q$X*(%XTM@`T>JEdmk+^W54P(>@cw}2~ri2vy7Cm`IRxx)G! z6<944bkHY*Zn3wsQ|&Y~tI>0G9dFD52{u|S&a-Lcng0fI&}+&)92cM7j{oQ6KxI#2dN;3|pmJ4=S>{;do_-*tJ+)qv^z5|G zI7{qq;tD!ylqXyu1_-Y*QS`k0A$s#aNzv>YE8P2V8fw}d)B*_AP=vI$)qV%Wl3%$*VveBB#yOP@i1ljVmH zl7hN^Sf;~a+#8*^RStN0;+XN0%gI9DNc&mN+skIFrKNGxr#n=rb!~BAuEztCNaj*S zh|CLT?9~_kL++~hON)6~w7y^-LJ4IFCZ)eru-~D!KnKf+Y+0o8jy%DZ9=n=Dj#aaH zA3hJZ?5dp{@UnR&cboT0S`{Gh$4irR8UyP1s%QGH*4`qMzA8HZ4S_(4gx4!M>F=0)EURdE82GEmjSdFO8wGqS9x5TRP~QnA+=G-Yj0jz0WO%g z3jCMdJa%+@dc+q}=q{|Xi$()02WQ_P1<*kz(t zPAm|Wf=f+bLYGQ%v{oC=|A;vcsd+oQ&R1&=8E{azOf-jHs18ZJc;Pf~Y^f_~^nixv zzxgUxsb9iG<0v6x?{2C(P>^a1nZMoW=b-ITooOSYX`NM2`NK+WttzZW8T;ObxHkZ| zvX!zITAwn1i`Hn;(Go{TzUC{8Eb{~$pr$nr#I&hSC8Ys>GsiAiyZzC(4nVd%A^N^0 zJ2W3~mKY8)Th)CRUh^Rm5p}-Jn@wu&K)oG{^nA0ur=dczSzYyo>U$wF8$3IVJ4d7@ zVa`W+Fb(xm9$=8aPvxqR`vZ?X(L9`dS9|c`jqVRQ>udE~-a?Q`DG|?a4&R;wYMKYA znh#dwWM%K%jh$`)kx#`(2?MbX^qo^WKd30Sa%xQ{UTdKjV9X7WsUNqUDazF})W&Js z;%3Snur-0&D_skJzIs0iL*%H-%H`g6`1iqZ`)v!aTB*N9)_ma-n9fe0SLr+KzsKVy zdLdfd18*ZK-L9_<6LvMA52IBkO~mUyZ!G`pmK^U0;CojmYaDh*^Z{O1Gwr-)*RX1%d>_S@3Q|6u_kdllC0(?V%R+5&07KWC|LMfo}u z;xn8ZE5H6E)msnH^>CiV$8HZ)gowtxl=9>*#{RClBe$81b&P5!N?&@8@jQdAAwNJ_ z@Y(@K5$gblP$sB$2RbiPA>JqBY`BT_UHovk<*vnl)SYqx?2!k5fX#V(8u?`&9{jtl z#Vycro~J(6tS$frYH6kuxBF>Jv7SZ4W<65C1A2NXVfI%ZJ%_cGZECBNW}8U9@7|nH zcP^CW^~=ejk|d}ft^ek3<;G}q+3moI>EZ*|LnBGpN2 z$XAHqrya{0M%}uAs}~yF9M}Y%^+q=_C}vzTeBM^%TbuUfv&>P-V~*Q{?AG>9|FQ%0 zg|L;a1_p{`4_R1@A<58#6+@+x@hn**#BxzujA+BQ*RZ-ZRF`@B<5ke%;)ut$R*!8} z%w|%Me~XvXOp_9(fE)E^b-7Qo&y9mE@}d{P%QgNJiZWnUx=NN%Ic}&9v+4ifs|CzH zo|a^Gb}g_3Y_<2yLRhSLeb|KZk#4V!>E*}WRo{gdH#_?eA}NL=O_4Uwav?-{utZIb=XTa}Hbgd)&Ey}%uxz?|^nndb z@rfH|xC{dfm#w9`Tr-k|PhEhwbGp`Ruv)z}=Oz!3tnWNQb3l~wT}Uoo0gyG~)}j)_ zhk)34YJwiZWwCnh)0c4LQnU3_`c-ZCt>zEBxeZI_vwmbn-?2W2b!Wem?$am&B#E%a zMM?{xx;}dgZhno_;t+cdVN@OEHhr&ibu{sUG1EJ&6o5EG>lgG0XJ3)FK0U*W^}caS zwna>K)O%mmB2@EUa_W&wtjtut;%o{(5R-`pq?XN@lHAM?4+HTS zS~zs&1avxh>^9C`oEl;=Th9zHYK~_9ZQnn8FWrLX!wYN0GCsN2XsdDoMS)y zsV2V3uAIdc3%@Y~Z=W9gt&db2zaLBphZV~WU`~fYRZxpA(KuOaduEwu?>?xKvQyaJK|NV}-RG;R}!!`@8 zc70|Vp)V{%g6BYIZQL!i$bmY~NjrJS^xC9F< z+-Do(PEFT2RcdOPwB$VP`dqVZHK?f;SO|j!+=iN|bMS6chdm2UbLEH2DlRs6+Ec8^x|JvHona$b%wc^O1rCFLz)%g>wtg>k zsQD9W+Y^I`vuy^sM_B1>l?+qA+I!|zJs>Z`V~&KRH@Gwi6yolznm7uGkiUq#)P&}N z78-TG^e6G%y^~~kC)D8U>wg$e0q;<~KGiHv5BLzrv#pqkaI>`M)1SDz=Z03(oiUFD zM=GQ_G!q^n+|eKY(aSyky?8TxM}5i|nuizln)LpEA0Sr`{8q`1ABQ%-fO~PDV$&p@ z5U*aWWphxSS45GgBd*upDW1lwzADSDXVv>GYi~wKWwq(S;{o$|z?cRM(1BzMLtJZ+ ze%_qm%$Bk-yB?p=TrtTN(e={SLs^(O~)H2=I!C$K?JeTxPgyQ~FfhzQt&W!Kq1A znZ5q6%fKS4F=RhM{|foU8$BHfVfoVR?VP&n^3U)%p#lRrMP)`kNVt3FZWx1&kQi$% zujy>PZ39T5%b)%2LW6bCo9)!($K9^JAPb#s6PfGpK`JpRJV6p%I{C}TK4OK|>z%&V zB5ljC)V8Bd%$>!MzVRe<&|y5;UcfR=f*83{PWLvb7_i5uvA9W-v^G85Q8K~P8KG%% zKvfxfdmLWd5!GIv@@RDcm{w^LW~MdApB4{)c2P>zEJ-q%5Hg+1nTSIt?S+y5f%Sl- z!w3_vD=e|-(~hsg4InUbM|+BZBR={hLef6#u0k_7s#A%N4o?4m-{$e~m1aoquWLAp zFyDe+7Zr#tO8$urCgv=Cm87B%9(qVRdoHS;zG?BtbhK7cnzwJHdqE2R1mmUkv5K`@ z(pk;GD+<~hm&~X6r&DD(Nj6iuRu~ikN-a$s1RQxY70KZ#)4CfAMT<=jq|y$ggji7l zt1%pKcutlQe&>11Rz6(7+{iN&Nj1})t}1#sArwNN?jN&0?4jpm^_3?SEjTpXN1x9Y zhYVN8jc-dm0B&Tn%O8mXX#em3aeA^J)%;-|pb@ROn-_-_&^XqiG<7TAq5m|?V*{RZ zuADn4yL`d;kGaj>p>*Jc=)>j>yJYxQi5ac5KFaOv9}y4zc>W*toL)v$d*W{IQXBHB z>yi!lXoSTtySM`yY7-umsEHTfOxAZ~XCC?F%wA!Ka~k+t#eHkRh|)jMOawqJHeF|H z>qf&{%pgrcszc4L=?kA;Jn~QA0Cv#?D=SQj%(G_=(z+M-O5bMz1)9YiX?9(t$E{W4 z+Qj$OxxbL;#zVj-Hd@_; zIg@9S|4I&pHdqp95G<-^kI4wfk7VN;;Urc6Y5ZP41vy0nE}`-8n6{IFmFho{OEd&`-h|5t?9sNrA)+eNW_XsCLl$4 zWxCYUl{WDMzw=C{`Fc$d$%8VN(LIM`sY0tzWfv``#xD1H&d`LqWMdtGGyqf}}wgr#!$gXIl*+F|L(mLf<{o+5@we-PS?+@n%EMh_$Ja zRd8&{y&$A59>=>EMDTCpT2Zr0E7D`!481~R)!9F*2?^KK8H3)8;;fP>G6GgOwa|>MT$-YB4-(yI zMFD7Bzxc6Ju4IepVr#s_Yk&bUV)T*Ga>0l-?jOxL906=!nmD9T+-Bwe_5Y`#V-AZ4 zw^Kr>KkG*CFML<{K9mus=P#0}S86KL?Wv-;`Yt|xY@JBt*Bb>_IY@umpTvt?52lse z9ZnH8)jDb0=v=#DaqHW7OckfD%!z473z5a7OUzZRR2 ze4}HQ^ssc|i|P#k^j`REl_T2Cp_OG+hrS997)-rJ6h#{TcpGe5BdG+>LvV9X0hB01 z0fz<{tlwh`A*Zi!pRSEBaZcku+Z1GD6Z}f9pWk-f|6o0)8fJ5GJV6V1=YT?yye^!p zS>4}5tZ!ANF9!BRCXcIWN}>-{RY3$`adC@6C=mr;gNv5TG5H6s_Fw^ zmU{6p+B$Ql=(*?g#X-Yb*6Py}(pk+3U=%wsESV8=oBW+6<383?`EB;}SHVZ$mp*ua zq563wes+2mMCxm)_l4h&W0-HZAvV7bi!bvG# zKG?x;H;&8L1elSsaXntRxtfR*d05Li>S~QFFZFoGfN2Qm6GQM(U{x&9d_O?x(n=q+>w)TU2^kPbm{?}6Ugw)x;2uj zy2r6OUh{x`p$o8H@P@oe*4lY2V8u3bgr6&eOoW}uA;J5$OS>q)J~dmO@Ky@>Ai2v- z2*Pcl*06d7h!C(A4k$dhvoT;M4HX7<*KNd1C#MWL8PUpN9eqhg-q+CP+2fO+C0=J0 z=b%YH-ykz%Pn_%`Q;B8zp%nvP)I)@V$7Fuw8PG4Z$1?oHAUy*i-&reTD&f~SwzCV7 zsbV8IyV>!6)L;}q5~aeSinqu>62nE`-@hw6F3stTqT$jb;jAIZOfr}yL$1Hn zZ!IXKcyjbvi8MuDL-AqE55akh`>u2Kov^3=CU^O3-%b0aYa&Xhw*&BAoI(YX9!IZk zZGnx)%LJ9Hs-}&BOoJsk0Y_a`>BGg?c1Zbb!*#a7U0}E8+Otb6!}vZvD+HpC8c+Sy zEAql`=cAyP!C|1qPm>5TijA`WBksMUn(D$fZ+x&kpn{5sbU_dS0qN2eDH59WPE>jg zy*E)okxl^VAPEqv^coNgNSEG=KtM_YgdRxvHcwgezH7~_Su^i9GiUJ!uudQ+d!K#o zec#vpyUc?FX0kW1je7;HEBLEf5;WEApJG#o&Vm}g8gt!Aim2S3JNi6Y_V9PQkYJ)k zSa~OQV`Kdf{Hex=3PJU=T2(pF9tEaC`%NXq>%QU%T<{Mqvrm1n51Z(O{3TPIE zKq8u+=yc)0{&tXz?7PdB)FN`4x7}DmNX1iPevuocrE&z?g0Q`xdD|<`y-z}Q$Z$fN zmpZk$K)R#ORHi8vkx(CwhU*eJ?$}{~!Idu6vfEgr-_-BH{Jtk+T4J}dy-FAe+o00* zeT(;0$`w(MVKazi;6c?g)eQ6WCGv(D@zC2#TSarq2{r3>cnnQ>0Y1iqtFGGS`4QBX zz+ZDWWEn3>^SOE}I9;rfB^-IhJc8yWf ztD`@TxcDqBZX0p!##3l&h?#GP6u!tVl*GrY#`4Gw ztS2`Ho(#f3-@$;6^`S|F_nKvTk#5`*2T%eqKC`G9oAhw_STMypeZ_GyboATFkT_t#YioEn9_ zySR>)qy~q*RJqkIRf>x{O+4vSNiH!J2GU{TokNDyFS3fE!h?%spyDaqYitgh6<%zhvpY-G>EDMe*xBDscpS6ElpPt_4fe@t zEVQh_`l8(p-dF(t*&RX&yGbYFV%a@9f$8X{9M2)ID289!6RZ+`(zj)DjjMUCRnUp_ z)b1D+epU>c{(bcol1@K}lClQj66-9tA{mb^g0zN$2NQ zUbOhE962Wuhdfu5Rd}y|@rX6ZxvAO~Gtg4GJnMor4CEK!E`Zgq*iVKZAFn|>I3+LE zYfydDmi;}r)#Zxaj+qtXD@Ph>s+3EqG^EAvh}{v>j~Y#y*#|XS6V=F@sPzsTHkr$Q zte=KDcYHYzn{jBZ=$!bPA6gF!Z7Qf$AF$8nMLe(Hy|Q9EWXeIjP+}sMY-Se44&Ok@ z&bO9JY}2Vezl(}m7d!1z0iC(Wv_L8e&zcgXlvw6zyLvSrvH#>UWn9ot&mukJKK7FDYk-O%oRf@0Sr0JCL?r^x+V5B+evaSM6%-cGdj18&?D_e}H! ziZDbtmDw+gn=|}pkf}6E2Ia<;PcQ|9@{6{a zv9|{fm%^{%Q+eB;q<|*BGG3sw}rcXLK6HpXR)boOlQ{IcFfCczqX$ro)irQQJe z5zYI)*)=-tc$Cm505HL)V))e6s)l$|#k)%8Gre&4#epDYLoHCNMzGM8b+%qoQ^mZ*kgq&aWNI51BVaW;ffsk+@4%d{;^SRizY)_Y2!c7{rHR2dit z{xm>W4?wb7Y*9frE2In=SX_h;BJ?h;6@?yh&_r%sWpJqKAcqgtEMQavdE?-AHjz>R zWZwPu*C+Dy+)-4kMRq+#n^|_Dc4%j|VB?b1VG|7Bm0G7G{$YkVn*?LKd@3iCYTH5~ zoKF6Gq5Eut8#{VElA(q?GFUuyF#nIm8j4nIDSH zI1FZ8!cnT9`Bw_+ekJ)=Z$F;NQs$DiVeop+F&03=(&MbT``76jch^+FPBzFK^y$gi*SPB;>U%gc>vlv=NJ#5V63wJ^CwMG^ z5Ryrgy27DPc2WTkP@jy<8dx5w5|Kpp#?s@}Q^niSllAyNAPrj8s;Nw_Loo>50Ud|# zI*S(zr+`FkWbFH-;#l$`tFEsDJP9muBPFRtky8A<)!Sx^S5V%aVM!P5rYllB#D1{p zQN1}AwKY|3(;`czZvKg>dDs2Hdi=BnHf+jHa?#eX%$ZN32@;xX|0jU@xGgq_+(E`| z$Cf5qD)@0-d?KwmrMJ~B1c?4<%?DY!|C zIo??WHevhYbF)l?_lPhKE47b6WVWZw3(%XJXZjiOZDbC4MbhsB`7DWK6OCfx#XH|o zDX^YSVH7RqVs$R?M6_6&A)e!ozvoHO4VAVkva#B}wommn>@~C>sCzVc=Q3YWm$BR< zi9&vp@`;_;P}xTYj0?S!b-t5;Qn3r=seTJ;6TmV+(MwEbA;}uJ%l1^rh^Rj2SL;aN5ewa*fC9qqH;UnPYl(p@Q*$TMkRc`Ld7bv2eQ8ucn%3K zQbVN;P4J0B9(G>(8U=Qy5%f6{9S$u(<5(VIx$Sm?2=z$GY;hl}6HFSFyYNv2jo6rQ zeQPlaI9t9;huq3BEVcd)fJE2iZ(#1-==w$_V@FlfcOh`HJ_n<9%?1j{9yMR8V0nT&PP3Sl_um?>%?x%ebVWbP zXrhjhkr+3GCi4S5S~6UYQ%brrTbz$qcy=cKdFA$lJ1YBW8)~&u+@IjqWi5vyT}?7;iR&!P+@Cm@V3kwjeh6r!F$Eh3eC1&%Wso6CO~W10x3L8DDVcJ zUOn|V)#C}rWOr79k@p0U^P2ij`&a~zk;ub$1&Y@h^L&im=(!EBvT0MkgG)~5)Onkn z+HqE029wMUcm0<@X<$qy#rTh>+&~A2TMO>kY?U@gp7k((%Q#^xiIQU=r2COz7ymZV$#=II|Wbn0B6o{UfTZx zbTEwlC~+tIgLe#*s(9G9hk38wztU{VUiMDh>gT8ZaVM`GxU9~&tf5Dm*|%jd>IqcB z5=swz&g(FL^_UWw`H*?SJlG|(@`*JN(=$M-HA_^0NUS_+*~@FZEl}@%dohOS-5UzF zE8|%5BopL&Khj5}#8R!ePP0Oio#l40VTE|xt5}{pf!Bv5DY_xsDI3jRC zpu3!@a8cZO%<#u}g7Fp)ACA8<`f;vYu`OY??|Fbn-GBk~`G1?iHdUFabs_bz)b$+j*>N_`%EgqOy~zF!c1U zseRmjrs~!GZJ4lQ*TX}7NNsCb%gMrVL|n_s?uPopMy-iO^7)ly=!pnWy)%$yk4ZCB zzTsq4oYCd^lMb|zE9X2kK!I*C4z6K=Cn}+(C95cV>UT}4oTMJzK%n$19cl~Tw7y%Db>lJ~PSN{nFk~Gn6v`o4Ig|fn6S)m#z zXVB=1H5MICyUq4?IvsrrY+!y^V64{1YiFI&5Uvfw+_2rXp;GRwegH`X$}8?SU7g~N zPs`NTmQMLkB9IEX1qlS566jx-^mH_+)llydD#5DQwL5e759+EfF04(V(CcKfGd$ET zD*xsJWUq2*s$?Z$QzAiEn(szQ^6(x|cAivFzN~HPgUx>sI#TqY-rm(b#K`jlWFRY zq&1)tgb!Ge|Dlof4&I9gtxx82LOP>H6hq3`tdEN_Nr7ZvMQR~YMP z&G@3O+#rN^IWrmaD|iL1NWYDU)M)txgY!XISr3O6(=cj$y3K)Xt&c!6U4^%zV*402 zM<=D1Vfe%C&m?`r9RIvmUOmYC zoegq=H&}n69J3gC>NDp8Lltc!rTV(MD&3F}y(0ajcrM8{p|a@d7YOSp7g6zy*Q2Y~ zols21MSx@@SRT#wMy{+3TA7q$&rmyP^D)95k?_j8oyuvV#`)LAh5#C(opl21pahRe zd59K}%xE;&PZcz7)TuZH6R~}>q|Nu|>u9~zG()uCmR;-3QuW-Zh^p7R0{L*UTMVF% zKXcLrQ$WaE5?%Se(F-rz{!PKHi@fSi?jk2zXrRLgETyA0pPhwpg@v8r3K#KhGll>Ilbs z$FOnKpv^xpUeo^s*;P^f`wfthIsg9x-0cRP4ndq~n$ zQR(UJtwC4+{A23t*W0(U)gEbP!r{ed$RZHt(~{W9YLzEXOwrb6YL3OP{BKlpTp+9C zX)*?4>NF-9uYiqaqF0x>S&9%TGc$8#gP3-v0)rYcRJ`~an~ky4Y5xlB5<{^aap78cUtLd@&eXV*qK zxVT!jNjua2d-h}@?+fMM;QM3`eGCoHjc1a!mWxJ;MQ2tm&+zwm2y1G5Iywf9GN3}5 ze4fmuzu?znAaX|*&1=(iD?{sl;Ss==8a zT`QyOaxR+yntFd1dw6gleKfQHGTb9&9i80-%)XpWPfC6wL@~VzVOk#xa^S{##lk{D zg98Irw{P8gXg@iyjXFcdpDBy>YuY97^3a!K0?wG%Yk&VESUDP8!|>%%Ga2V~TZ*R@ z6&38aa{&o*v2$*2P8=ExPP)G!qgn_?# zGHe*-dzDbn`s-Lt)fC8+*VP>pk8w@Jkx=Kvcr1qQjwozIR}4YrOW2`DsderGk!#>~ zb+Wp55ax{v&jQd9e*2~{zKP#!*jsfCJlYw6AlWsH-A1vsDjx+HkV_33F%2uzQ{k%{8!&&?WuW>l`|%w1C7}-t=X@Ny z-?NkE3fFmsZ-ag4g7aOMn5CblQfl9eVGvvg3BP_w!|CxP7;xzTYV!Eq(A*&6FWmy zA}fpjOC9aR;{aF_Zk1Ctc{1Z7W}c_h))^Hb?qHAdRb@8QEB+2DCSL1PoQ%T&Ga(YG zL~-dJsrvh+9^GkXFbDPd(uyoCH)e>`vXcFKRQ$Aa zw4KSL_iP5at`q#D6az7kL`23jrtSHa6>gcYZpf*UY@664KqN&uPfON>KPRgb@mL)* zr*%y*_feE%^B76@jf>N&V2h&kRPRVy{4S7%N++*lx$YRpyiQ~QJJVeyS;SE3liY$x-ouh$cpEgSW^Za5j&!u@aT zaOmco`S7Hk?Pay&@n#EjJ@e53(w*xNN+~F_a03L^(H>JBN`1KL9bVO2OVOmdyGRbvkEl|Vxmz8a|e}nQfrxF3S?0jl#Y3P)Ddgy6%euq z^#Kea+H7*9Ur>-*ZVwTJJnqp{59ogIHG3zxnLsd!P6jqJH#=HYqHWxl1>g&>u_e89 zbL=7_x}=yWUc{A=_k_{B=Iu@L{omx&`&hY6JRAl3;OmzP%l)v@{QRU2n=J8C^liQE zt=btxJ{MSJfMmyi<5djgdEb4q>&o@9S16ux42hNM-T@x8w_80@gqBBmc~S9AA27m+ z?qta^U@HKz$r+MYAI;AbH9tr6P-;L%;O>RV|%3q`HrrER)l3*va&pFPE%#Sm89chpZkA z%8gt|Y((4@^nOLy6>%UQ{ph!7TjTtZBxKC<6F;vzJu%rezMQNO+^L!XajcU@mSFpV6=567hF9C?>i#TOVvN)3cf zE09s!Wl$V{5*0K2p3u(H42^aI9KoRGAnnYq4Zq#*%U`}0*^Q7VPAy`vdZng6x)Qi# z@CQ1pY(&CN114`x)Pc^`(J_6B#~Zk#U@rE_5ZAW_?8saw4!!qG56jWbplkOKR8qvR zaJR-cJ@|XLXggjxmN|}P2XU?`OoE{vE(^d^i|?;bFVCHh6mPR^v$&~u^=m0@hNHuy zj<&f+#56HG-Ae1r+(A0p+6dibUhs3k@_~sgao%C~E?AD%8!V3bNLI(lqzAj${fiBK zAYbd~Zw1KitBdhpmEBYev$Jmv6Rjst=s;yb2nVEJE29Ok{T4Ubv~QqNngauxkoN;{ zDetsj?(FR)@gnzrFE8_Y`w-r>;{xaUE!VDz&SOzJx|#gtbwe(%UV)an4+mpk6QHT= z)h~J!72nl6j23dAtnYE0a#3jUZHN%B~D&p)lzAMJ+&A4R*Tj64$5j!8a2dXYdsO#%PB z39LB>wvh=~%CWC8k-G{(WOc7W@4aRZ`$qvoXa#4z_G^4fr$k6s!u7BLB;EC!cRSnw z(Uzl8!t3}O*mxmlTV2~q>J8r{#f!1AxqhMPdor)nKhbhmFxGO)?VitZhmFqQ0EfSy z|0+nfC5e{$sQh*Fobg(FkrHd!)>3IEvyaQUfYVc@>Q0q{nhoBCecJ0kUf}r#<|N0G zSV>Qj(MP8&P}Wr+wgXb7Fj=%e#F}q%Hs~Z_6{35Q<%y?m14h5t*n-G+S+|Y2l@Bd& z;4lx~C>ce5@tqbm0QxY-)b|lW;QqpFk(yPaFzfv8u=uRp{uf^D1Fk!Fz@%46xQ`(? zl*M00k%2t$mkwpx93Jz3KHfofaZ`UVV@uI~rXVxSxienwK8olNpc9dMXI8481NC(( zWEj3lPmiSgRHhzi(({^E9B_?v#X?_&?u0*mbN<;{e>g5dTUD|n=x9M8ndO1?(Azw9 zhK{z6-&H^6S_~zgMn=W?)T>`hJ;C0u<0L4o`YasSo~~C7I z{nyF(ITLxp0I*q$I~K%q<{oy%5o>x|?C?I_-ZQ3|d+X!8$n=@Sv0}RfyA8DxYb?|w zURBTb-Iv{KhL!NPtEtth13pLw$jUF@(y@HX%bykO9qhBeN)6^U?tBKxeltP8*Nz~* zo~pZx)OXp)EPOq&cY+s_$LOx5I)rxq%3&6a}(y{@I!)>aS>qzc1cZqfI)WmN}G zR8@JDJlhC9*y<8c!*6ZbjeTl9{t+j{BkA|+Bj*6c`al6anTr5m$(J=9q}SGZ>sybw z3pB;Y#?BpEyhmu>fc={bcuFfEf2f9G`ixm=WDQPY9goGKgV3NYM@C-q7#JLsgT->H z!^QD(a;!7y@_m5%1^bQSjd<(3^YCvRQp*g5za}Mh=sAh*y!xfMk?P8pmJ{&I!EoKa zZE+Nx(Q>>ux<$h!GD>SDvj6LL(!*&lkA|dL%4NY(XvEHQ*}ZSVgL0(f&0o2ze4m(f z341{{t8f6!1rshFte5=ljHox0R1^|EKZLG*vW{9cE>}#K_8bkQKG{2h+Vm(R5|ByR288#q)Vd%Bv)y>K$Yn}8G(z|6o=up%0{3~$-O`I-U{&ULFK&&&$j z5b={)saAR&u@DW-9tbVN=W|}|2PQ+Wp8(wPO_A&@l7&ooX{4t?ZRT^_j1KlnIR#MuL9;OD1bq(AHt4z_$d8>7luTacjd01AlmJ8!Qm@N!0j zEMy6CCNnW*IM;tPQ>Nka`s}EWrrr6Wic-~@$z9tvh3Mzn_^z1XnGfHF9Y*CyLma!5 zQ5UGdb^uJKCO~AR#B`}X+Q(gBb((B`>~40oa9;7@>5cXHV|ppDuf`6n6qe_zi+kFR zDmgih*G!}GPxrW?Qm*XmvZP{cOoN`^cgv%Y=Z*@tw)J4ks?>?Bs;c73^qqOYKmLR- zYqCre26i>X&YV{l&YwN|qYWqHU;P5aR!F!tAp+2BWDnQ7fJV>Wmy5fh@NRBB=H@4> zwY`xNV~}j~o%)~^d`yhl(Wd%6s(d*FG#eK*^XKCt`fnSQn%@?+@O8)xTyHFAgU`os(Sv4u&Ox`cPa*XY6<2N0CMMRWR+^yG=&yywJK%EZ{ zH?bBzo-cv{M+vjM$d;`U`1Sm-gVTrO+0*4*mgCk;y}u@~EcD-9XQ5*~Im!|q+66pp zMwu6NjtcGH9vCU;ygBdkD{yCp&a3QX@4cq9voxs(x{4JCfB;xPc3L`8>Z`=Ik|J*i7-N1HTsnGqm|q`h9Q%h~@0k z2eD_Zbq~X3#*r)%X;M4;@M!t`e9g8HZ4a;=nJBb!UqPlFj6K#ihm12aF>%l#mk#&k zPV7s0=U2~af0C6tv=Y@-h=A#p%lhxFtLo%s%39dCC4r7&`;kv3mC?xHW0cC(t6ZXM zDQOsac}t7lrgLDSH&qvBxpL*!MxDnkCb^*bU+tNZ@1lmu$T+_p{t>W@lL76k^75fp z^_D2fB1%6cr6nN42m%UG$1Dk6DOGn3YipbgG>y>Pba=5+sB+Qe%C&3vS`=%54hq!5 z@{5iRI2L+4JLBi%SCMTA z#ETe9TI0l8?W!Au*YT2eBW%kAQLt5ia9gT9UO^R_u?4Btc=#w%G0F$`-2hDPT%&E1 zL|4^!JG-@V*SEjLbvI%7fuPiHb-bfrFiEoHb(?l}hd{g}mbVOD|6-V-$CKqz;;O^< zr!D5wr`bav+Y3}o6L!R|sKW4YKvwe|uRF=1kZlHJ@sS4}zu%e*Q&CbR&<0>1-*46SC&zMjQ&)qbQPh@Jf-V#MKm zNY~44PTx2!i3=|E+^h@}z@cb?OGDB(O#RVm`btE%@a9N`)%)zv54mZP4|M`zoT>v@ zU?b&kIQoja_rcbD+f+2Y1uoQD_h*_+>ZHd zPuuZNXZ^>-?u0Gw0*-z*PosVlZ>8}K;V5~XYxKD4xb8ez=?{peZ2j-D?t?)Fi0*fu zU$ndi@1+CT@w%^p>x5`(&FZa_jj0L^axyrBwtEu6eR1XOssAb2LvEM-gSEvIU_&rc zNFTKLn}XtFe&EiVZEBnUdE)qgkWc=f|N2Ai(#PT|M7o;5q1SVkiI&J|zlDo@AQqA80-#(Ilnhf+aAj z@MY2q39iU5^kk5$Jbh$OCvCCH#{& zw>V$(Q?j?BU^k<`!jsF@|moCUla_WxVvk1%YgYWL5*cFT6;k}U{y6X-uAYKe7mad$}x>YUC! z?o1uSRN{eq&8yYkA_!qd;Cr{rLXWu^^fr!Pba;3>%62@8F-WB1R5etL8C1FY7}emR_?#0o`+jE z2dycxINy$#w%!8R^}mk)17?<|*O!uBDa@TmRlc8SZsQ zLiO=GaP{-r|J=-EcdrY2yDD<}D{wmeeJ&{1g#H)qPI7*&m(3D-^5oJ`ZpHFGN$rCw3_iwX=UNv5SJTn?kLNqN}? z_1~Y<=fErOc{=efUygfoT=a!hBgMHUaxzjVkPm%J{h~BC) zAikoBM!5R)8s0%V`bGZpH7g=*h4-xdp*;E3XaD0s{rw5`|2>PQE`l-bGH?rPZ@!6* z*Xp0l{g|a}nXYVk{cHJOH`iyY|HVK;I+SPFd*uubOAs)*{ri|Z{_jL|c-wzJJ2D8h z(tp1?|38nW{$nlw|K!=e21WX+|6b33HWUU6NXSuvX-VEOt9eoLaf|CP`(?6-LLgGW zpO7-rM9J>*K(3K-hJHEwaO?lE5%K>6@8kdBzjXKY_a834T6zYl2Z&DpJebhkkYI^1I`Mu!?C(z=AKM7hpNiICvpr8HB z;NF)~YrEH^XuOhbo~SCIBD>W}e$=VF*QyNtM*;It0U zA8rbA0Ba~KG;18#wgqGJ<+PZ{EIfXrNAfH_tLRAOtQ`{J+V zMw>4w(44j((1}2fzi*A?rWfC*N{q$Qj@K91*d8r$?e0-tv}|jCzX;kAeSC?2XrnniQ`+O8U`b#1iDb#x zRuxD7W&T|XnWb(Mkq59ve{PkAsQypF0xErVPOL$)zccPuyrSJ{ge>1YjK^TH)2PRj z#-c?R&L_!u61q#LkM^)~1!qFu156tEADusd1W3u)xMBLJPInFt{wscu=YYYKOZ=NIn zCh&;baiW5_K7O_*2e@frCu&Ny(1iiJ(abTx?dS;D29{LePrZosIY8%V5H?YTh2_nZ zdLch3NNtPHo_tJv!5VzO`uVE{`=kDMeO)$riLxL~)BI}uYh^{nMg#0i>tkh+{T#Qx z7k45RKZt)-PkAvvu*o%AX0ZIyzio|bgsO;ZdbRz9(47=H5A zVL16jywq(c)qj6o^2P6mc1QGOk&zLk<04kX)q*p1>%*`KW72Gkd*wpoaDh2cCp#ww zKx&ZMN1baMpjBx2k7dAar|K?xKS}F~A6kP2W}msco6fCL7;b>xz)k83e(dUZ8!4VX z0MvjRI|ck1lThF9PXsi(RBCzkONf|#`kMWm8gf&ibZ#{O*L49fw8Wk)h-yKtA2tkn za*&^`EK%CwvHo+9P*nDl$q(CO$EBGu5;Cy5X9BbBFD{?ZP*D%Wk0)biut|(%KqFXh zL*fwUmcRhPk4)g5fjv@u=B&?jU5%5mpY0(_1!$k@YdMy^Ev&;E*z~1vXxMald3pb^ zeWT!=EWEqB7UD-@v!yK@db3aa8C#<6RJnJ-No%(c;bGO3JcMNE~^YKd^rRQh7^0Qv2v3gAmOuf~)b1o8hq2&?BtTeuz zt$|_G%r0N67Xr3dJv+F$Rh@Vk74N!)QQ?*`E4FMquMr4Z-cf+BLd&bhgN`>@pIPRh z=4N1F-2-~j zIaj|5+tW&=U4R}dHEHSCV~Ue^0QZo@pB}kB$*P-$@ zOii2gd8Pxt>Jb=8z$!8je^}cuSPooz6TOdCU32xOO9O1{ffU)qoj0Bb(sY;G^6Is= zc;oI(l7AaCxMyr$0ZTn4O!~rszHXGKq+D1>F~YnJ8^rXDJzJ$J@@N2!2e(# zKNfY+G3umTcv}FRC#3~{y#zu7hOFf=SNCpprweu89`LJiP}h@I%L_HG(b7}`P&4Ri zV+DoC_EF_d`X0%$P+c=G+(ePww~kb`a4)B!p=v4b4H;mNfwoscD3~MN=XmBY7*XI( z8)YMEHSxZFqj_No5H;*ZbWn)NrI;4$9+NsCaaWN{!(^|l{2H%OS5@(dqNaCbHYlvY zTsa2D3c#$pzNIDD$A|V%qhr4a*oOj+iY-#@D)2!Ztx0S~d(XCll0=6`c#CznEZ~E} zlQXsGfX6i?hlUP1C-e@e!v3A{xJ%d99kIs=K{2s&w=M*~HLK`5d*NII z;co7duMO*4Ih*d0TADcU}AB36I4s|=l6 zgZWV9-`D&uW_n}g7u>Dk9|Y3k4ZJyM*ivxJ_ovlU6E`VoibVnK!};-)dc@WCFtM z;kl{cxlKCgN%omKk0^Rk-KZZ)hO5J)vpU-ju*qiCAY2o+&03w&Zsc5+b&pXtPVQ4M zZ1{4X5f}RdSG+DTRlv#$S2NF5479*BtIO@y_R3m_SrbVt6;`|>kqm-$uri!mxg8C+ zwp%{GYx5nQQd8$xJZRRi4{1C#q#y+)Dc*}#PQO&9|J`e`+S-gU*EN@5e>BM&G+k#` zG?8R#Le(B4=a;o}S(VrXOo<40X(;55c2{z0z^$7fgCc?z3;2yP2)h+H);bT**UDMn z;pB2as$8|e0WI_n7W6NSq5D#+88x$_i**In%!+mLlTh(&8xyp-;XwGPYilbZdZGe+ z1nf_MJf8@jF$qpHC;0 zS;2jTy$ez_p|!R%i{F^$|GagRZ z)ZyV5i4x-yg08ONRLqGj=xjNthvV#0=_IUB3vGca}C@GVA)9jyzrPA;e%*OQhkm1J5dOwFf{NI_U*{ z{yv0BgPwz7W3xzF(5;m~(@L+$fB<0SKQ>1KHeg6smrquLzu@relcKZ7m`^LqTxRdi zWuOc|yRuzE*qJj{h|vUjTb;`Y2N)`#`YH zq3`E%hIBnGNfjMx%L)dHezwDwS(0DJ1b@Q$TwTQ>x*D^Yv)NbkHG=OG-?zRZOhnbI zB5H28)cRJoxt$P?sOj77AQU<+K^c^o$KJS!HJ?xzzK;rFSW95!u}r;1U^%?6zR<^mE#>91b5rGQsdREjJ=VYvCs464AOylNtFMzM$C z*99sTS-pUJ1)6{9;;)EOScN|X_jbw9*oA_Z$> zV*0AQ&4=j>ECL?!|=Pkv92F+>LBh zc%idfG+eWNH6I%qJ8^DNNu}0JOUM}`q}DLGoZ;k7P(xrTnI8(KS ztu+^5Y}{K<$jx10qhV>T{V1B?N2oVYH>-BrWE6ez)-|+=iO*&f7YOX2U!k}!erCo3 zxI%(Jb60maT(W9f>Uz%S{^G=k)mQ0hOC**t9hwlVX+*Ynejwo|(VHQ;Y^vO$FM0Nu z!>SkXBVs4&WfRJ-DoGyzxivkOB9;*TLln=qSGAL5mp79K z!W3Yxr;Yl=yrvWDP{uM?8io8bzPUlN+P(%hY7BMe;FBR1!GhyQVvie4U=3m-u=E3P zJ@7Oj2b7vr5(G1}ow;_pK#X+go%yQi0dN@&bOldU3;(t9g{wSEVpO&uFin{-Yz#`k zJ_cBjpxrvH3gCAjL+sSX54%(y#E@$n{+^`^&d^2Z0^Xc8-yCmpLi2T)-llP8+c^kU*yTCl6682)da7&6E7P~JUq%%nQ9@e z%UlpJ2M+Po0OpdhR0VqIS-Kd>_%|jQ%VdG@?qFms;lj=avb4MVM}X6+NRN{WqM$Hl ze;I=~r{x_hm8|Pncw(=}-ph7)+t{Rre*aC3!u!pNKJi44N%P>V!p`4!%vA=8HR3+EhUzps7dI8m2#R(rsF zn5?ekv2rx&cycy=)KW72UB%sV(N;V&Q~gssOMBbfi6%&)k{CSy+vMcY+tf2w-F2c} zM$S>VI&2v5|Cl|sy!x`G%_Le3-3L4b;Ta>`_Sj?Zw0Lf|4Bv!IzoEU~WZ56?v`Fuk zb8%?~5ELildhyfAAZ%08^=m_W9`&9QC4thAwycJR!5Fv7tIWuI->nu{&3P zQ}sV4+-Vr10FOYsrug`?b)OON15*2J^U>BQ2<9ecCICoD@66lx??;s*C5;mLi}OqE zN08jZim0d`G$TbOCT1{I>R`!g*d*$)$wK@Dh^vA36G~;_`~YX!1z&>t{)}f4w^P2% zoB5lfFFr|L%Xk+_fOWWC*f(u(Ok{eGS*G*nwnakf6I__2yyROd{CsfSI$jA|e3NeZ zi~3!Tcae)lTHpGoK-x43|uOp?F12u(L&|Z8#7a9*h@I@{yW|K+5 zCqd5NkWG&(S|sH-3Oxpthah?t*+YgxxbwTKaG&D!foC%Vv97$xV6|F(O0d9HFbp!M zj$ZIz^*~HZJ(<+(nc)oQUx?7;j|=)%0erx5|52eqQQ<7K)CleMwJiA9Vt;%{N9BzTW_1_9naw0? z%bu5niaURhnw$etAWWiaqi!d=D9rqopJ&(kv%hUVMl#7h$*A-_{?-ASm=xOERJ2Aa z{g**`8}&YhS@?KYeA?$H*_?S~g2S4@4F(t9?M_&cnDgSihfOhtFj8_>oEbk2!UY*JHxAFAroUWa{ zSDS~(gE)Kbj8Uu*R_$db)jGq0Em6>>7S7kYD*q|dY2VtGYGt%5sqW@rXP4_IEptU< zFDs&-OhZ-UN!KI&40$%Win3L-Lg#m){dK{mOi`OwPXlPqa?>>xai@W&L7C~BA&b3W zPkjK}!iUORuFw}jPfY3dfx6>xJbAVV3k^DIv^WyeF%Qz;J#+S4VxsXE*f|kTj$6&d zOl9@_q+_t<`Kys^e`R|5x;xO-zivi5FWl&1qnPLH8WFg|!4ciKw-*^X1?B2H2D~{H z=<9~NWjf!dKns&!l=}K?@bBKEI|bO&9irx1{)Vi6N7+Ifvz(5^LzfD1ci_fI( z?BwRH0h4+;VC(_HB|W;Moz=V~!|7_9tRmqMs7G6RznD2Zz(@!ec!4S-X0gBHZRL*T zR0OuG`FD9Sq+wW#S1Z&GX&FwCR36F!(J?>gl=d3lv14B2I%tlVy^=-J0y zTd*8AHSQA%Z_xI_hw7h!d+1X6rdGdzMbr!6403Fm+c6*31?y*GkO=q`wB(@S)%z+&w#a6xQ@9UJ zSkr_Sd>8&&q!$+fZ{k@Y(;c-PA$>k#3F!3jpWPw4oC>dQuRT>VxreBSAYA*#EAA5ct z4JR&{+mHL|L><2Bua?^1fWLjK!?U1|z}}#vE1P$-%UPeXup8EnC!;f{kA1d_9kH^q zAs+6pTk+~UlwRye19&9^ZuE-(9`h4TP1w|@mqTiZWL5c13hLQyf(dK-yGS7!{TGjD z7rBA{oRi}YN`)hdA8KVf_+(zX!<{3O*Q6#gh!X^7M&Vv1MSA7M@pl_jw%#uB03Gh( zeMIv9c3IzLL2kxasmIx`B-UsW;_)wV&&Cg<#LcG4lt6Og23YjtJyhvx6+U}y%wZR% zL6!|P->H}E<~WRhL^V6@Zyzj_84&YXAL&^h_tH1P>OIg}ro_9J z!%lJ*7pJ1BeBU#O739+@eIaZ#6E>65N=L{&%rYcJFHo=NLW>P*YnlU>)DTOVy7KnM zU!R&u2h-QiKMP$NNO6UJOT51}S~U1sayxr_&~U8`pdB_VZ%A&Nn@uGus;1}X=P#^f zq*xUGf4seASX5gdJ`5HL0*4k5QIQyi5Rgu#q)R$RknTouL`oXz?(XhTl%-mIoy><7xOtCxP z@+upMLl~<;CDeR8oLIQE8pbNIn2&Ejes16>%2#1wwq=T%+l*CPVGj>22&PNWW$2Jw z^@VNe>-Q;I@2Zdwzw@wKa!UOeH^*_66pbItV7j?;Y)jKSsy>rnU5VpE*il0PEt*N4863UiO*L3b4)_vhCs?g0Y zqP%qX}&}W zb21!==Y&$IpjwtKb)RD3_RXo2Ai0$cwAQ?cd=c(`Dg&{y$cM5XB*yb2?dzj<+}Bj9Mo8 z4YoAYkF0GZT1G#~@s(6(y$4x2eZa*m^TQ)X)xAH)%)tGm8m5VXPY^LGGzws_*z98g zuuL{ky74Tfx$5)sH3sbJi?FcR+~>ED%&&W0MgbFX#T2!>VOPzqb8+0CgRN?1nQh<* z9~+wJ)P9aLX{B_>$8QRph!!vx4f&FtzywwlHI|z2#6;u_B`P;h6gU~Uo^7(0!bt2ifrKt&T9>El79x*22 z=3BkhiSd^b?z5m zbOd~{Ep>~uB{l+1G-chc+CX3|842xNm1~RdO9&l8Mbe_(-4hlSnUl}?PTSsLPImWB zeIcu5-!yujI#znVGSq5-+*&^~C4M9IIb-B2N#>Tbv!oR(Fn@%^*Yxy^6i8+T{CaRs z$(p+WKy?q~B`)@ltYYK){V0#ik)L*!vyC#!4N%Qrd*P=5&JChqYXoML`Ob@J&fd{j zh~c~@~&@H$}LZ^{10%1PD^nBbk^IXAlb}!^TEP0Os+x zpn92Bi!lO$e%tBEIAi&uBL(ei{WML$@B`G_)AQm*uOY4;*4ANjRCy&Gc)%Q;arJD0?#}kAAn!fX>2O; z@#zk$rZj7g#GCb`Tms$4v(Kvt(0ST&s!lSv-_J%p$HQt#RijpOY}-@IzCC>3bo~7% z+_>-a`!0aV+4tyv5)Q*nE(p8HB6I;#TJxz!iPHm9Q|sub0Xz=I&T&e@^(Y|(qyPnqT|fMbSytDI*)LyK-qo0OB`%~mG4@6@F9O_n|gfd zEiB|*h_j6M>ZIPtIIwvHW?Prc%&unM z$kFm~e>zid)fpSdP{cTZE$f)6czN3l$G`#6i z_;$3kv>U65QeiOXU7$dWNJ?_w8YS%$UAQ?Y2&iiV?yR6qf;xrqxE3p+tJvB~e*C!i zWskUz446Gm#fekXP;c9XgAkXGBhkAd`zdOuTi2W2zYu3JXZ{FH&4v;Wit=v7a+xVExvdrSTza4wKM{GBq-P|gwU#&{f z`)L?Zi|N%XQ(o!2d{ZecEG*3GQRxab=gftjcSFTKRIA+6tTC0XdR)6ol=kEB@T_?| zB;n0=s|_6W$>{q@eU;sSHyh)$PI9qfvlH@p0C2?BzQ97u%A0y80c4K1>MGi$(+PtqPn52K-!wU@G^W9J4wx;m2rXyLsM6Gis*YU^1 zy-l`TZ!%&cRLkL?$ zo%;5uuOPmCVVxOZhMk>H`1Fv;6y#%L%G8G1%|~m?D+i)(zU0s82M=M&&BUtcY*R18 zE%j$n{usD`&OzdFUqVh*Tve6!=$N_0es9p19TTaDb)!qe;&UsXDNT3cKB4?Q%F{&x^$lt{Rd~pJxVUpxVUKI^%FeZPZrKg%gTp=MQlv$ zwk68XAZ0evGYU##2#U5%Fs& zgO-gAtJ(Hp%M<_3hA*dnxM^-}TPloXBv0%DVPw2H2b8*aOxkrrbr}aJPQpATCaXA3 zR=U@(-*EmXmVQUKyNS^=U$fPQSsCy15qknPm>inj84>Z^yz@9tUHw9k^ap2&J|)+Qr!>kcdP!d_ zl&sNZm*x()D<< z!yMNTO!{Qp^?L8Fs%h5cf-Gby;@;wRgUz;O4HE_3JSz)_`EciUfU!MoNwgWZ$H(-N z1*KxHe(lEL4}_E4JktsQea=M?GQJS?QZC)O$7UL!__%e{SMj=q}@EVwSZZlCcV5<&FU8$Z&RDZ#X2lD%q|uU*gI65BN{W_CKLk4f zq}o0m$y}mcXMM5RwTy-2vw0(^!vErhf6|(lU&wLRY+tW54U$W*uU@ciBHhFPrh)T# zWvb!i$%o%<%~VtwkP1T#xt|vU5$y~*)na;a-#T-#6PykN@%{|(Ia>~dg{aqhVFiH^`ztx*H9JB6mnYN|Y)YTvMUyZltge87t|8Gh zQ~sX%+je=?dqjK@MaC1mNWJzx<<5XN1_leB8En}hMkFHfoUU7f;<6$lB6VZHwRqA_nw2Rk$u9OmALT z^z52HI3T2~qAdMe!0xhxJhzAV6Eyk|e7D6!ez-q2E(<$Hy0Yc`*&*&{3;hpGhS=g9 z=>Y_5;`@T$)&>D*ydd4!bkJ`s^H6XQZgmHSF$6c!s50*s*j7|j@)DNh?Ic9|Jp>~? zf!`gMm6bJFs224M)NZyN*+ zyyxZFoLm%~6|8KD!7U11PK$j5ftYq>3MbB?PJ@1(^TC9U{5?#(-0d@os$ zLJ46zRMqcQ@j4jy(LZGY&u_yyh2HK;PCquqY|5~*Ea4mza^Zv^XJ80UlHBqzRV`63 zl0$E5q>9|B7C)`xFS4_t()xq)2%c#Tb3H@D=o2ch<>i%|WWJ3Sq@IDn?L_-n&VUXd z{4l^syN0-re5^<*0e0~_uL9|T@aN)LJ)?F+jH<|~ZtPt4a9UpG7UTgVGxN?L9&YUv zWMn!b?R|qA3=Xp895?b{QenP1J;!y1Ukssgv!$Q)U{-vaX*SsX@-`z&q8=X46|Y_3 z=@D|mX%yNle5u|;eA_CPU1K`s?fAw8`Nm^BJ}U4HyN_szi~V+LT3WOIC0Vhw!lJQ- zg^5(nQ0uvlP?TIfw5@gp6BPa~eWizAd<jxz@sQ6g-7Z| zZG1lFO}34Vqcz_3j|~x28$&1x>yFU<4_|SgUbP@Rg*#kNH$^C)M`8|PV9os|Ilz60 z{4~N*T-fyaa%*h+`B_J)PuwFYL2plQ_cE#i6U`vYxkAh>;dbUnjspI>cfSJ3?PAv9 zyjh-d_+L=J*IRC%anuSHe6doS`%zl&`TI_q?oz?H2xq*{-&v!(#%*}oaM3| zWJTw?BgNo|o$wJ@;hM>4#a*i!p==SY9wbKY0&bVXQQI8j#O#UQCDM|rG(i+E1UR`~ z6(;JWXStb09^gkF6Bo@^I|mna;v&ZD zM7c;Qn^JW!*xa(ZYBylvUQ&O0Tq2zEd9ZU)I%&6NZCmPSVNc=8-i z`Tq5RfVcN;l{>C`J#nqd$3b#!ZHm4|XpKDQe)5g@-W*(HE*kll6b%G&8B-_Kygd0_m>ZG!i@&qZa zf?jd7O9EC;?k?03Xlt)#zPt>-w%ab&;pYJqN>yahTa@;>JJNo`vMW$XC9K9b$i0YI z%b#mP3yX4dKVNNH-u`ZOJ=Z<> z_DIXQ9ItIwxz6R0V0?pO5O1-TxF+JslenR#vbxd@#Lpt;L>*F6(uw%~bwe8Z?ryyA z3Inivgcpn&F=Qd;k4KMV(8*D=Z+o^82td14$RR63=%p*g!9s1??dxi2@TlJE8yGM$ z_I^-~Txh)hi-cqVT}UEomsa9YBWe9sgH@ae|gUzt*>V*d>>-IdnYU8pt*N+ zv^F}hB0k=j_uTC{a+PZp2V+q_XmJcH~V7sb1kLJm!L+iK| z)Hh57L(LY~e&TLN|Me8V`ty|42`u=|#&DVJg^dWdQO5vpZ!Hkdd-=&(&XlRXOqpx? zbeHlJ=PAdY7a|4z5Ky*&G)&r~v4e zh<1EsM`5A)bn|ERWB(N~$E`tM59>((k+b~^zQxo;cN=ro4|r_>by=_Q?Cj9w*^wJd zglKMe!5#z6;0`4Xn!HO^K%FyTl#5F(V$&E3g%}!bh=_`|cpUd%@3duRt}+)Fq&~MY zGP5wyWeOwm_V!kwxoxu@LlJeaA%xgR$L8S=oj0$&DNnWT1>~3zZLQ{1rGL^(5rAJF zetM;|pGrF$PL215niozx!o5R~Hi=-W_#^9DRxMfQMWCXhZbSsgEqA*+cG_ zL@@1X%2(Bej<$v{7PJG*>lCda)GuM6E;6_em%Af`0EIMK_dNk=wA2$xwFJ#piB#kYC8ZeWFj zmYSKUwLN^pw$ShtO-SPZ7zMB6|Npo;wdL}C*~#%`5np|qgM^}?X53Tih(k&Ouavgt z`o4cI7`RB^AQruhCHV%6^D<@eFH|k*cye|}g2PV^T*X<#^=gkYt*i$25NC&LQXJpO z-2Tr~4;gNsWmu8*cOySOBAc^Y6Yfd-UhV#A8gF;+S5%P}cdC4Y*hW9Y3ULbE?LpV! z3X=cN2>AQ;;U0L53#LXtt<(xz?ksmJ^#+>RvzZ>DN`*9Mi8iV^r%(UaqgP2h^G57(B64GVE(L^G&ztEP{7r47L)8M(Kwg=T)8xz&OFB;{W21=>{WG+P1*0iywj{%ZS4%< zkgAzFu&6&QC`DgnAlUfd0Tu-n^J6u|p7*#r7*r`m8ysJxO)YyhI3pML;Hrg-s+q9~ zidOMoGs)4Ou(LZ*X!AQ@%ZQ3nlgj!+F*JA{JpJcL-yplxbWlh@j=Gmz%_TndWuQHq z+b|>Ew-J8oxu+jEEUUCYyLTQicF`m8?69ExOwIe@ zl68RM{Bt@Y`C7e<9(`TlkoqQ77hf!iEdn{8X!yc{sub!o@ei#_ev$DvH~`sXmXW1y z8|z-|=D`tG?LPx4RYtU8#5#e62fF{#{A2Q;i~>H-%AsoSvi<^wUO72Z!A8tT z?k~d%N=immlK2EG%^vMVE92@T)t>3aaY3p1@Z`IMfbqk|c%-fMBRChMgZuB3yC7CB zQWJ5btGOpxd|CeLvcn)SbdKrug<;P}At!^O22|>!m39$v@sgPXS`2v}o_d!=x)oW; zZ)?po_t^^sdLmC0n(i%PuO}+U}^!oezcJ+4ckBPx%3Mwy`D5(tllfgZ?5AJLIzqX|y--$`~Mx`e`xvk+#ryR{}MDY=tYUXFiGM8p-DU@(qf@q61+ z%G^_~p4u>~{GF4--J8;b&^B#q;B5sAR^x%v@D8Yc1Dv=K5AWT%d-vwxAarVKO0oD| z2LU-rO1wt9F6C(;9Ua~8_I4t(BE6(U{4ik8sid4mVhw`79cA|*_|+};_4%rt){geC zSy_8KJ0IfUIInKUgvF|zgzW5^n~hud_jbFTTy$+uyv4^F?D`&qY9;5UqGIFcZ`w4t zI@y}9)x9BMzUu*N@rNn}lZ{jIZ1P?`l1=7e1E~1+&WV}g2SSgKs9G)m;40;ac5DNK z`>G{#sY(jJ!{g)QfzQmFiqc>?>61s%8mf|nxpR?*T?PB!w^e18OCUbK)a|U2YK8rta{_R>- zRb{qZ+-SL|(m2suKSBWY9?)B#P7Bh7^|kP2ZF~|B1!gfh_4Pcw$%Vazv;)b^8uiOv zBO6)}c{CpLPN4uIy7o2iw-#c?#*Snbr+5SR=pws@eKUrW*5ubhLt1OK^ zX&I9KYf-(INyU?sli(C?FDK5B&YW3sAo_h|Vj@Cq?R1;d-eqNZg*rMX=d}qkR->4G z%I*c|4Rv?*9}2j7?0dp5+Bk|e8JU>}h`JU~V_&Iz0(G)IV{&|beMKH?(*S2|fbxnr zG02AC$ffWzo99HnRM@!3p4&nTg$5Sz>2r}5i+~%?qCbe{vb>{P@@#I?zvRjS#IT~) zVV`?*m$Z4EBJn!CMQH|wQzG`c(dh7GW*!W8%$^*MRob{q*(u4ckMQyf2rSL|^suCy zw@lAWy*bH;cDjlaY{42oBc}Zp2buS>{<*Pec-Al}fH7VMB^*ia6+mCF6PKv95*hLO zj4>`{u5W&XuYGj_xsw;t(R+}iaOMn4#pYjN!q5<;Zd#gSd9IsH&*a1;Fc;`QXTi#dPgb8yoFofdNf_U*D@K zC2<}{;~w0aoTd*2+3FK>7Uv<1AY8N?#c;MefA*_0&3+Pj*xB7xKVDd7HBlYU#17D! z6F6$m0(EV`lXc9nLKC?f9~b8#n7W^w1osyl2~T>eNp5Ch2`{_;_>yTwBl5kfxbWG^HS#xmdd=>J$!eDPWnT?yzzr@d=#eh4*$vh}l}*rrE^!`aO{*;AB>)oj^>}?kl>V{_jmZ zRu;FjOJ}KTL0ah=8q&0V*SdeuMn-iR81cStnPhjmIZjVS_D$>6t(SP3t}c6G5<_p_ zrpORw4K#+aaChjv9#MdOUdGQ*jbx{y`$bqlUrlh95;tE--cE$eG=Z74mPMd zjc1Fnzv1KKdi8s<^P9o9_;@nEGt^wEx~7NoG~!VBnvT^FcVuMb<^~1=kQH7^cd`2v zRTxUAApjD$s;b`nxw#^O%4|fAW3x|_!ooswmwPDGbz?l(g2z6hSsF8R`C|-Oe_>L5 z{M8;hOcAX5e`5hRe!!v{DkX(EYa+?08dFseeZyrVX(B628qaNXdwovHr66QtVKCKV z#as_OOXImL|0H@%vf9gvJ*MbwJ(r3pN#=HXAu>Ah$;xP^$;=2qLYRj}(n)VzX7xQ@ zJFNJgLP~PlKef;nOcWc9sg90~!Q)ukI!o)5l7h2*xRb2ox$IY;9Ii~&T2|%f=MT(X zx6$8K9BGSNL*PEUvNhOkFXFYo?&d98%!OyEhFXrkmy{;E{Z-qUOJ~YyzIfWAK0+r$ ze&XH&s1t`+BUrTwn>`pO#ZLuIOy5azBysMJvvGkZnbflqbqTsPnf+3-qa+T?CzrLg z7pC!K${_064}}oFC01FzWg$u`E0Mste$JA60|;d zCmm8twIdC+|6XDj-J4>0+1VLizcwB$r39O;=_-@98}SP>DalDrx6htEg4`a7L@WI* z6o<;Mia%V6g3?F5UFOihkEsdl)(l4=US|Rx-}DT?lP%Cxzc2Wi3ssLxd4)s4&cP_d zC7`PET&>w2oi_!0Yl2)re=*vEcS?^fTh=f*ENpO`qW+Nm4V~mnqX#y7WmOgXo6(gO zg2@x@8II#m!SHyVp6eawaTO(HejEE1&*swj-?&tMhb-q|o_Bd1J7v25ga~?!5RZ?K z5A*E1M*Qb%AlNu1efkh&25h|nL9w_ zWSu*^yN_@`vpf9^Z^ODZGS%BLw(1y&KUgv5z&USiiE%^jEGP>bE){9GwyFW8Zv z{Y}?RP4h!=&PJyW0Q!sc5iu@)O#2J7b#P#Aem*+f3uGEY6{5j?ukM7Q5IEI{7|Uy%U?&7S?9`#P{*_8&2&}Y&Er)z zOWT|FB7jW}&ji%-n_Cp!y>n;k1nm7Q+F>)dIBpb~R>n*_&)@u?S(%cISKp>x%T$13xtk-Yty)_E(FJsHC<<{LoQiKw$GnOYk$^V z29C&~Ud#erU;W>M@5i+Z%elYOy#2~8FHt^+6VSA%Q!Oe#PqB%>{rTeVgV0S8 z?wCX^dwc0yqKu3T3~b6iqwnfdQ-l({++48<3B$kME}U;f%))UfGY0wmR_GoRl@Q1% zOdpdlLmDz2LH5Tv#a}pNVw8{-si?K1cg;l7V4?Y1W0VnQA4E9Xob%FAj% zsG=?wu;=t0sPz$lLNEefEET?XdL%;9lqrPznW6W}hMRk~*~`ycPLRfNmy*2Ah*1L= z=|0m&Sb1OvY>bT_8AT4KB-%QhAw>g5qavdw-zv77>M|j}^4+Aiv02U|MSE`5jtf9T zM3o;`VBJu7B0@2onmF62=3|%b)mzXQvNh=V`SwY73ntRuy+@;%>63V{JpQ$$hqQDr zikJbIs2SiEsB)^6>7%n7FK^-rBVHuP4k3eQoT~R{`${H(ic<0bgbPqVNDkb$zu8Zb zEFOqGBbb@H=o-mA1k4c)4UL-p*_%%*DZuCu|8%@it!lk&LCN%FXFf}~fdeoXBMF`M z!!;LHeBfwFxv0K)OI=;DJR7yopMPEHm=il+@7(~K`!?8nLi)bgtBCCE2ZGdm$dzRS zC_=d}DzARe(mo|Nw$o^~H(0YqeP1DIy6Pf!X1qO~;1uJjz*Sz)?r@fry_IEVn?%tX zS>*8px@TKT`q=qwWOT=%!PH}TXAdpI*AE`CS z2Yu4P{Ih0erny)J8LO*)>P}0Fj zS4QjS$YHqv@u@Xv;5YjPusPJT^lQZHSis%vA$#=bQNaZAJNz<@s$ES(L;gA=_o07W z-XqHUx2#TSzmJ%2U&+nsxwLT_bn19`L7ls`mHRlR}aXNSavI&*v6rXP#S zhpBGPS4ET(OFGJ`II7eeYqbce)ZT|mVQFfb@yJdKP|(so%`xFQ&GE0;SZn@rdTJlT z5Gyqh8J(u3sV*S_R*@e(^UsG}cWdBHRR2T=X znMpDXaZbVhbk`zfqX~+r`0DQK?cOU>JT~eL6H6Nq^S5deQE*~+nD6fVK(Ngh@%*NK zdV1{M9d!UmgB~98^fl=CYMXKy8X0j<&Xi{?dtWv@b_c0+?j<5->Dk9Y{jmDBzemjn4`t}OA@<>9yrQ{4Ka(|MbdZ#En#@{gOWiWz&Y6I# z?)pgzBq$;(;s$u5e*4x5ov{3z!VmA*&>1qBrbubHq;1A=5jHD(6V_@l{p~`Fo4f9T zPBL(V&=|`%F;gBH9W@`R_xVk1ZeFOil9bzPkAB_cnqrCk1-2EJXBTA|N6c2{k*-8< z2?#!ot8wkegzV1P`b;*RfBEcyBPJ}|q;Qwn-Q9iZyV(frDxP75N&1njtn*_Y(ni>;RMD?6%j0K1-; zSke8ebIPw9y_zB$VP~f+o8QL@$ko(hO-zk{w{?u%p_s188iSa`-}Y#!xcGR`3CcXB zWoDjew%7*CnDv^Kkf1IEpU0P0E~OlD!t^mF&kfe;N`Tfqu$@|iz3mD%<%900TOjX5 zPuJ3v39}jEB-5x1@JOqbtz zyBm*lZV6lYd(oq>qbIZ@DJqqcA+CL;Y6>zdMvVHj>y4SnHwtocW-44s>W5NNaVh!d zYcEUgFi*uu!Z8BHa@I)z2R=(Vm_Ye_eZqV^b0`s(jo3zXtzuu(2PJQs`mG`=ND>L9 zk6X=H?GMgH8M;CO2tu-|GBZ;T+5`%kUR(C&qTjff7!&d!8C#Z<6K#hZ)d`s8a813} zqjJZGh;<^^fT9aNWC}8I23)bc4Elv&fY<|iP)wSaGm#Q2ki@K@3=e*}`bQDf6DLt{y)Z zxtpbK5x2TZcuOmKgDEI??Gv6Fir3a0Zu$txXTs;2wg-w%HZ~_MBkYY?(|0=o8E6OW zLpck3?tU02yRt1U=7o%i_5m)pSzdbjcz*nEJiC|-YbkM2kb$nAZsMz=Nc=+Ab)7MEH z#+Q~X9%KK-LDZCO-qdgjOh>b>Gg-g?d|NlnWz&7#CWSiu1UDWX3><*pzw?%&Mgwk% zyy+~9s95ifQKNd~5D$;=&j7*{ zDMC`Mrmt`5qo?kPUv+jIjh zQX6~1=2)-!#-$v$hdApZL@R5ntCdyxcz}N4cy<&j(P)2K(VJwOauA9q<~vrzz*xJy zQz<LpR4b z-e)PSyxfxPnMu`MWJ5zk*a=lphEWnqhVLuX2A*kO23`OBC60P3a5lLQ;fHXnpD$F< zUPw_$hTX14GP2_xLI~*?w8|54si(3;AhKDjHKm!u)KW}oWk8qoUU;HI4rvih8 zLf2)bnAr*j)0r-N%101UL~dJLQ%;NW78CLIlK?0Kxi{J5ZL>~pd$)G0Pqw?j{7TNL zKVdHeHm%%-UoRM%J+Rj1tX`**FK(NTXTD`0vI>igj3noDpLc&aG%8;L8<-m`hv|6Ys^udFxJD%nd?WZ8x zm4q_oFET)9K`9$gPF&>w31&k>`=KgdrME{5!pj}!-bc%i+(CxBd9^rF3pV`-N`PTZ z52*ix1!TR#I6L8{BA^L(D@UX6wEVE{_Km%=8XnW zZych3Yw5}iw@NtMc8}j+gkB6agWh(H<${EyBr$AJc)pYu02MxMvuD+Hg5Sdi2U{Ii zDGvfH8_u3xeCjKSbU&zKknC!o4v6v~F41T6%XO-a-II(6L=>^L_i zZ?BQSZw{EV2sjfP+i{LbMI#@7?m*Y%Kz66e3nAH`t-R0IjgW?d)UG)+8 zx`;BCSV+?I!>gt3hYFOFr{pQcgcF78Z1uQD%_0Nj7pPlElbpW3VGR(Hwn&Q!m9q)NLWRcH0mRVPPKhAK3tXYa^pi zqEp=NBB}-15}e`~yfPL|GftHM1?{6?QRIC|aC;8&KA9T#Uw$NI#OKtYY3zEwnO#^Y z68uYC?$s-rqmf^&f_t+~Ry(*QlA{=j_zu zf?wyP3f<~@mYm%!H%KTOgo6RG}CHMCbD6`UL5IV_5uj za%!3(UNGTnPGN5^B`p%kvLKOR`5>MeN^8_!>=M&qv7;j^_A#oc6#FQPASJbWQpH|s20!F zyWCXQ#rX$E`7}2d=jR{v@JI#(1e4b;G}tx$F#Gu5)$Zk;;(+fDGaxPflyb+PY9xaD zliDbWt5t0LY*DXmH!C3#o7Gn59RBLTq8a(zOM{D@NT?zmhtc?(Lx}JLoLpwi$VYI4 zcOGHp+UL{DqN0#e0oSefx0n5%1(=nEg}9`I>mxLQ-ar)RSO?PK4snG1Pb{QTO7emmq>z9}RHCZj(Bp=Q9C z%5*B7^d@#vxO8YM&vZ%xF{{^9X@JJetphUC72LT)`KqOpD8MN z+V^0pu>i0orBq7Rx2ejQOG=q_TVQ3aVBfXjC5Pfd+!l@1Ou+F{4d}+qq znv+pNf-~VA*72`;bryo=dy+Pv_FAfU6WN7pnX-Pw45ZZW+Qob|!NNKQ0DsxHF!Q$UM|2` zt+%bsKRV_KQO~ta>j1{tP8gnh(&d5Y94IR7-$$>ne-7)Hx56%Sk)m6C&=Q1pl5>)#~SmbA69YxVi zNfx(Iqv)MgH+M7CWbOKd0qdPOpwUu{wgC&QXNlM5hmqs0*)skD4%v;ZA8AzP&Msbj zaK!$qF>GQkxE}L%`j*qRXK*T+)r5~+@do&76*H7h%y{{wn8Ggj-P_3)&i5M(=X`oPfprrXM_3`_mk&BQn+MBw9V@6w}<_qW*~V&NmW%!N-E`zf!MoucWAe`bTifg zP39+awc(oLSxBl_hh^Oj@Q}nVEhF~0(VO2v@L%BoaW%i?CyTa64I$Z;+oMW)PSevG z}w6qD} z&5)@X4Of?k%q}(;n|ZmLv-tC)a51^=>2Xg22hV@jj#)T?ol@_{>ekO2ak`@H&+)-S z*$)%4<{5IbT?Iy?f5b59LZfL`Xk2lhZcS%pR2WMaG&espNU|q@eZ20U1At?+8WhMR zSYZM4fX7b0hJ9sN&RWqPg7Qb1+P%|T_Vn^|J>Tm0&*zu7?k)c$Yd_T_-W)n-IuSm3 zF=E6_%%CISy8eL_?-NzVp0*$_(TS2~g(Vd#41ZVQ6OH+NX|#JzLU}(>Xaz9KVT@!9 zoNKXA2sr8qq2{YO#uIbD23&C2xLg6=n~(OS{8hy9=}=kG!pTOg0u>#qXn#$DljoJ> zuE9~h#z+}J5DPBod2i?Wtx(S|9uTJtjg)MMaOuu__c^Y|@e2rCf69vkh}W1sTPLd-yW1J&UnNA*d`TU!8+nTwH47Ee?P zEr9lPWjhyR_;x(oDLo{F!=2E6cl2zw4cgTOIk_&gOws^M+@`vd>&SPerhKkT`+Mt> zO~0dw4M9W0?IwB1N-VyW3HJ7;PfvUYRa zbgnPv8qVEY$d7n(7X^(W@koz@_x=i5gl7tyb^4e2{(>?wYPbD!*SoyuCv~o{Wj!xK zCq;(ITIYtvuy@S4YEJ?P8%{RGyRt<>oWXz&(RL-FP?mz*eugMc`1U&`k`{ia)}D8n z*_a2QFiscmi^${hAkYHXKvZlHz>}G593)-1EQce1{CMtYNhCUX1b~EOJ_kcviBPSl z1>ck6!@D0aA7}T+_Tfa`p}=%RV7M%%{Cc5#UzGz^X-^FmY;E2C{rfk706^?E)52G7 z(f!Cmod$t-AA)?&3evtOkcuR`S;_hDjM}`tF_o* z&3p&lm;0KPRk)O25mA&B1Q9hcy-M?~Ibwn_f;D@K6HSgc(bl6zvi<%eMi-!@+Z(`Z zBD0!pq)N7l$P<*&SB+p^!Hhj`~t?!a}3B`zYiJMH>~z-Pxyg;BHo04_z+N6`P- zF5(U^4qB#|RUDv=NVFgkY$Q`LCb>{2p@nBaok}iH+hQhTq$0a|G?susv@YDYho5_H zZbMjEi`mT{KOJm4qe$yr4;@~Wav`E%FSjr9P zZKeVO=Jv1Rt;EPg{M$0~{EvUbfJi_6(Nl}s@bW#@z`eVn?oAqh8Ptk*S6$}D6F@({ z!{ZgZ!p>5RSbj=MD%uei9j&v-n#IQrONmxt$Kd6~B+=_YJ zUlgsgv$Kn~f(~qQDx33R(

n4|i@GvxPtG39Z`{Ej4`nMkp=3G0|L4vHUl2G-L9< z%1jh=79f%t@WfXF28&KNSLeH;f{~@~b(W)60;%J}Nj1B(jv0m29>@6?<=7dlBwft%BXiH#q@&a)oR> z*k?b~>AgCgO-k~1c(*yR4LYa+RZIpWCy)NRxaw<(DLE#P3BsPK!t>Zg{LZsmZgHEb zHZ?Rgl}<^Yaa>m+2Y$fKRZ(q5Yc006`h*{>t}XWh(lFXxRPS4 zpqRcs;T8Wg0|V-etp`AjHagbb?jlw4W?RxL*{52Cosm)Isxu%^?psg*P-zge{alDZ zlP7zhjg~6?_sQmm-e%;+Q>OMURItu2r&V8MLF!n^eq$dju(B4)+OV-(&R@B#T--(^ zMb}hTexMdLxG$P@Qh8K_N(Sop{j7p3-&Q{DoO&V*G?)|+6M-(C{ro9;U|4uK*^>{g zvL(ZXP;3`sg&`INMLw+m%*bCPF<`a>eTC||eYO^du8#=eBdsu_zc_{JZ0$>PI;p6s zeVF;Q8MwKp3d1D~RalB2sCOr+NIyEXk#y5fNu?t&Vx0_cwT&4vjICh|;eRyxR&=?XBG&nS-G%@%`d%2_!sJ(x` z=-^qwb!_2!0$NiV|Ik@IoVC8Q((*$2Z$IfBum!C=_=3sA!s2PE1+pzlGh8u9aQ{bY zlq9!fBNU?Y`V%4X_v=fE+>d{*HF)QP#{VBIXX*CA>4^VugeOPCtuR_}m;X7Pe7?Hh z-gL#SrHCu6xfY)KFz~6bnXk47o4f)s`~Z(yvZJetX-@FCsRtb`k`M??{`>6Ug3{*S z*Z)`SZ0V2!Newkf11W^8pr?=*z(a|K3m%{UQNF*Fr04&ht*0*+o*nIb z({p&KXfHdHLr#sDdG<<##>nR16GfBn&zJ1&W8M3b`*{D0e*H$bIIm{ZnInBA)%0Az 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

    rW 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 +} + export interface ServerSpan { traceId: string; spanId: string; @@ -20,8 +26,10 @@ export interface ServerSpan { endTimeUnixNano: bigint; attributes: Readonly>; error: boolean; - /** OTLP span kind; SERVER (2) unless given. Worker activities use INTERNAL (1). */ + /** OTLP span kind; SERVER (2) unless given. Worker activities and workflows use INTERNAL (1). */ kind?: number; + /** Optional OTLP span links (for continue-as-new and background continuations). */ + links?: readonly SpanLink[]; } export interface SpanExporterStats { @@ -52,6 +60,70 @@ export interface OtlpSpanExporterOptions { const hexId = (length: number) => new RegExp(`^[0-9a-f]{${length}}$`); const traceIdPattern = hexId(32); const spanIdPattern = hexId(16); +const safeIdentifierValuePattern = /^[A-Za-z0-9:._-]{1,240}$/; +const safeRouteTemplatePattern = /^[A-Za-z0-9:._/*-]{1,240}$/; +const forbiddenSecretValuePattern = /(?:^|[^A-Za-z0-9])(sk-[A-Za-z0-9_-]{6,}|Bearer\s+)/i; + +export const ALLOWED_SPAN_ATTRIBUTE_KEYS: ReadonlySet = new Set([ + "http.request.method", + "http.route", + "http.response.status_code", + "space.request_id", + "space.trace_id", + "space.parent_trace_id", + "space.workflow_type", + "space.workflow_id", + "space.run_id", + "space.parent_workflow_id", + "space.parent_run_id", + "space.continued_from_run_id", + "space.source_run_id", + "space.activity_type", + "space.activity_id", + "space.attempt", + "space.outcome", + "space.status", + "space.reason_code", + "space.error_code", + "space.category", + "space.failure_category", + "space.duration_ms", + "space.queue_ms", + "space.room_id", + "space.pane_id", + "space.mission_id", + "space.agent_run_id", + "space.agent_session_id", + "space.action_id", + "space.goal_id", + "space.dot_id", + "space.turn_id", + "space.cli_task_id", + "space.revision_id", + "space.change_set_id", + "space.link_kind" +]); + +/** Filters span attributes against the strict key and identifier allowlist. */ +export function sanitizeSpanAttributes( + attributes: Readonly> | undefined +): Record { + if (!attributes) return {}; + const cleaned: Record = {}; + for (const [key, value] of Object.entries(attributes)) { + if (!ALLOWED_SPAN_ATTRIBUTE_KEYS.has(key)) continue; + if (typeof value === "number") { + if (Number.isFinite(value)) cleaned[key] = value; + continue; + } + if (typeof value === "string") { + if (forbiddenSecretValuePattern.test(value)) continue; + const pattern = key === "http.route" ? safeRouteTemplatePattern : safeIdentifierValuePattern; + if (pattern.test(value)) cleaned[key] = value; + } + } + return cleaned; +} /** The collector base URL, or null when export is off or the value is unsafe. */ export function otlpTracesUrl(value: string | undefined): string | null { @@ -76,18 +148,38 @@ export function otlpTraceRequest(spans: readonly ServerSpan[], serviceName: stri resource: { attributes: [{ key: "service.name", value: { stringValue: serviceName } }] }, scopeSpans: [{ scope: { name: serviceName }, - spans: spans.map(span => ({ - traceId: span.traceId, - spanId: span.spanId, - ...(span.parentSpanId ? { parentSpanId: span.parentSpanId } : {}), - name: span.name, - kind: span.kind ?? 2, - startTimeUnixNano: span.startTimeUnixNano.toString(), - endTimeUnixNano: span.endTimeUnixNano.toString(), - attributes: Object.entries(span.attributes).map(([key, value]) => ({ key, value: attributeValue(value) })), - // STATUS_CODE_ERROR or STATUS_CODE_UNSET - status: { code: span.error ? 2 : 0 } - })) + spans: spans.map(span => { + const safeAttrs = sanitizeSpanAttributes(span.attributes); + const validLinks = (span.links ?? []).filter( + link => traceIdPattern.test(link.traceId) && spanIdPattern.test(link.spanId) + ); + return { + traceId: span.traceId, + spanId: span.spanId, + ...(span.parentSpanId && spanIdPattern.test(span.parentSpanId) ? { parentSpanId: span.parentSpanId } : {}), + name: span.name, + kind: span.kind ?? 2, + startTimeUnixNano: span.startTimeUnixNano.toString(), + endTimeUnixNano: span.endTimeUnixNano.toString(), + attributes: Object.entries(safeAttrs).map(([key, value]) => ({ key, value: attributeValue(value) })), + ...(validLinks.length + ? { + links: validLinks.map(link => { + const linkAttrs = sanitizeSpanAttributes(link.attributes); + return { + traceId: link.traceId, + spanId: link.spanId, + ...(Object.keys(linkAttrs).length + ? { attributes: Object.entries(linkAttrs).map(([key, value]) => ({ key, value: attributeValue(value) })) } + : {}) + }; + }) + } + : {}), + // STATUS_CODE_ERROR or STATUS_CODE_UNSET + status: { code: span.error ? 2 : 0 } + }; + }) }] }] }; @@ -142,7 +234,21 @@ export function createOtlpSpanExporter(options: OtlpSpanExporterOptions): SpanEx stats.dropped += 1; return; } - queue.push(span); + queue.push({ + ...span, + attributes: sanitizeSpanAttributes(span.attributes), + ...(span.links + ? { + links: span.links + .filter(link => traceIdPattern.test(link.traceId) && spanIdPattern.test(link.spanId)) + .map(link => ({ + traceId: link.traceId, + spanId: link.spanId, + ...(link.attributes ? { attributes: sanitizeSpanAttributes(link.attributes) } : {}) + })) + } + : {}) + }); if (queue.length >= batchSize) void flush(); }, flush, From 92312538cf8aaca697c7791a43f874a20a682346 Mon Sep 17 00:00:00 2001 From: texnari4 <39917166+texnari4@users.noreply.github.com> Date: Wed, 28 Oct 2026 22:45:03 +0300 Subject: [PATCH 2/2] Fix workflow OTLP span duration after worker restart. Fall back to Temporal runStartTime when the in-memory start timestamp was lost because workflowStarted is skipped during history replay. Co-Authored-By: Cursor --- apps/worker/src/workflow-observability.ts | 9 +++- .../tests/workflow-otlp-observability.test.ts | 49 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/apps/worker/src/workflow-observability.ts b/apps/worker/src/workflow-observability.ts index 5c661169f..753ef8680 100644 --- a/apps/worker/src/workflow-observability.ts +++ b/apps/worker/src/workflow-observability.ts @@ -125,10 +125,17 @@ export function createWorkflowObservabilitySinks( workflowFinished: { callDuringReplay: false, fn(info, payload) { + // After a worker restart/cache eviction, workflowStarted was skipped + // during replay (callDuringReplay: false), so the in-memory start + // time is gone. Prefer the remembered start, then Temporal's + // runStartTime, so long-running workflows do not export duration 0. const existing = lookupActiveWorkflowTrace(info.workflowId, info.runId); + const runStartMs = info.runStartTime instanceof Date ? info.runStartTime.getTime() : Number.NaN; + const startedAtMs = + existing?.startedAtMs ?? (Number.isFinite(runStartMs) ? runStartMs : undefined); recordWorkflowSpan(payload, { ...options, - ...(existing ? { startedAtMs: existing.startedAtMs } : {}) + ...(startedAtMs !== undefined ? { startedAtMs } : {}) }); } } diff --git a/apps/worker/tests/workflow-otlp-observability.test.ts b/apps/worker/tests/workflow-otlp-observability.test.ts index 003625e2d..819466e47 100644 --- a/apps/worker/tests/workflow-otlp-observability.test.ts +++ b/apps/worker/tests/workflow-otlp-observability.test.ts @@ -434,6 +434,55 @@ describe("OTLP workflow and activity trace tree (#61)", () => { expect(JSON.stringify(metadata)).not.toContain("PRIVATE PROMPT"); }); + it("uses Temporal runStartTime for workflow span duration when the in-memory start was lost after restart", async () => { + const collector = await startFakeOtlpCollector(); + try { + const exporter = createOtlpSpanExporter({ + endpoint: collector.endpoint, + serviceName: "space-worker", + flushIntervalMs: 60_000 + }); + activeExporters.push(exporter); + + const sinks = createWorkflowObservabilitySinks({ spans: exporter, now: () => 1_700_000_000_500 }); + // Simulate a worker that finished a long-running workflow after restart: + // workflowStarted was suppressed during history replay, so no active entry. + sinks.workflowObservability.workflowFinished.fn( + { + workflowId: "dot-goal:dot_goal:restart", + runId: "run-after-restart", + runStartTime: new Date(1_700_000_000_000) + } as never, + { + traceId: w3cTraceId, + spanId: workflowSpanId("dot-goal:dot_goal:restart", "run-after-restart"), + parentSpanId: apiSpanId, + workflowType: "dotGoalWorkflow", + workflowId: "dot-goal:dot_goal:restart", + runId: "run-after-restart", + parentWorkflowId: null, + parentRunId: null, + continuedFromRunId: null, + outcome: "COMPLETED", + reasonCode: null, + category: null, + correlation: { "space.goal_id": "dot_goal:restart" }, + links: [] + } + ); + + await exporter.flush(); + const [exported] = collector.allSpans(); + expect(exported).toBeDefined(); + expect(exported!.name).toBe("workflow dotGoalWorkflow"); + expect(attrMap(exported!.attributes)["space.duration_ms"]).toBe(500); + expect(exported!.startTimeUnixNano).toBe(String(1_700_000_000_000n * 1_000_000n)); + expect(exported!.endTimeUnixNano).toBe(String(1_700_000_000_500n * 1_000_000n)); + } finally { + await collector.close(); + } + }); + it("runs a real Temporal workflow with stub activities in @temporalio/testing and exports a connected trace to the local HTTP OTLP collector", async () => { // createLocal uses the Temporal CLI dev server, which has an arm64 build. // createTimeSkipping's Java test server does not run on Apple Silicon without Rosetta,