# Runtime Observability Querying

Activix owns activity storage and activity queries. Runtime packages should expose their package-owned Activix instance by reference through their debug-only `runtimeObjects.activixClient` when they need activity diagnostics.

Do not query MongoDB, xronox-store internals, playground files, or private package state from parent packages or debug UIs. Use the official Activix query method instead.

## Official Query Method

```ts
type ActivixQueryableClient = {
  getJobActivities(input: {
    jobId: string;
    graphId?: string;
    nodeId?: string;
    limit?: number;
  }): Promise<{
    jobId: string;
    graphRun?: unknown;
    activities: unknown[];
  }>;
};
```

`jobId` is the required root correlation id. `graphId` and `nodeId` are optional filters for graph and node diagnostics. `limit` caps the final activity list after Activix merges configured collections and sorts the result into timeline order.

Activix returns full activity records in `activities` so debug UIs can inspect all persisted `outer`, `inner`, `runContext`, status, timing, error, and metadata fields without knowing the backing store.

`graphRun` is best-effort. Activix sets it only when it can clearly identify a graph-level activity for the requested `graphId`.

## How Runtime Packages Should Expose Activix

Runtime packages should expose the same Activix instance they use for writes:

```ts
import {
  Activix,
  createActivixTrackingManager,
  type ActivixQueryableClient,
  type ActivixTrackingManager,
  type PackageRuntimeObjects,
} from '@x12i/activix';

const activix = await Activix.create({
  collection: 'my-package-activities',
  mongoUri: process.env.MONGO_URI,
});

export const runtimeObjects: PackageRuntimeObjects | undefined =
  process.env.mode === 'prod'
    ? undefined
    : {
        name: '@my-scope/my-package',
        activixClient: activix satisfies ActivixQueryableClient,
        activixTrackingManager: createActivixTrackingManager(activix) satisfies ActivixTrackingManager,
        packagesRuntimeObjects: [],
      };
```

The collection name above should be a stable package-owned constant in source code. Do not resolve runtime package collection names from `.env`; `.env` is for deployment-specific settings such as Mongo URI and database name.

Parents may include child package runtime objects, but they should preserve package identity and client references. They should not wrap child Activix clients or flatten child activity rows into a parent-owned client.

## Activix tracking controls (Config / studio)

For operator toggles (`track` vs `off`) before a graph run, expose **`activixTrackingManager`** alongside **`activixClient`**:

```ts
import {
  composeActivixTrackingManagers,
  createActivixTrackingManager,
} from '@x12i/activix';

const activixTrackingManager = composeActivixTrackingManagers([
  createActivixTrackingManager(parentActivix),
  ...(childRuntimeObjects ?? [])
    .map((child) => child.activixTrackingManager)
    .filter((m): m is ActivixTrackingManager => m != null),
]);
```

- **`listTrackingTargets()`** — one row per runtime component (from legend `owner.component` or `owner.package`), with collection names and whether the row is configurable.
- **`setTrackingState({ componentId, state })`** — updates legend `state` for every collection owned by that component on **that** Activix instance (same persistence rules as `setCollectionTrackingState()`).

Integrator packages keep calling `startRecord` / `completeRecord`; Activix gates writes. Query APIs (`getJobActivities`, `getRecord`, …) continue to read historical rows when tracking is `off`.

Optional **`isTrackingChangeLocked`** on `createActivixTrackingManager()` rejects changes while a run is in progress (`ActivixTrackingManagerError` code `TRACKING_CHANGES_LOCKED`).

## Write With Querying In Mind

Pass the root `jobId` in `runContext` on every activity that belongs to the same runtime run:

```ts
await activix.startRecord({
  runContext: {
    sessionId,
    jobId,
    graphId,
    nodeId,
  },
  outer: {
    input,
    output: null,
    metadata: { kind: 'node:start' },
  },
});
```

Activix stores these values under the configured `runContextField` (default `runContext`). `getJobActivities()` queries those configured paths, so custom `runContextField` names continue to work.

For MongoDB-backed packages, add indexes for the fields you expect to query often:

```ts
new Activix({
  collection: {
    name: 'my-package-activities',
    indexes: [
      { keys: { 'runContext.jobId': 1, startTime: 1 } },
      { keys: { 'runContext.jobId': 1, 'runContext.graphId': 1, startTime: 1 } },
      { keys: { 'runContext.jobId': 1, 'runContext.graphId': 1, 'runContext.nodeId': 1, startTime: 1 } },
    ],
  },
});
```

## Query Semantics

`getJobActivities()` queries every collection configured on the Activix instance. This lets one package split activity streams across collections while still exposing one package-owned queryable client.

The configured store is the source of truth:

- In `database` mode, Activix queries MongoDB through xronox-store so results survive process restarts and can include activities written by other workers.
- In `local` mode or automatic fallback, Activix queries the playground/local store because that is the configured backend for that instance.

Activix uses cache-aware reads with `mergeCache: true`. The cache is a freshness layer for hot in-process writes; it is not the only backend and should not be treated as complete across processes.

Soft-purged records remain hidden by default, matching `getRecord()` and `findRecords()` visibility behavior.

## Debug UI Consumption

```ts
const result = await runtimeObjects?.activixClient?.getJobActivities({
  jobId,
  graphId,
  nodeId,
  limit: 500,
});

for (const activity of result?.activities ?? []) {
  // Render by package, graph/node, status, timing, outer/inner payload, error, and cost metadata.
}
```

Debug UIs should ask each package-owned client for that package's activities and group results by package. They should not reconstruct activity history from MongoDB collections directly.
