# Aja Domain — Technical Specifications

> **Aja** — Motion AI and Animation Platform

This document specifies the implemented surface of the Aja domain: the domain
objects and their fields, enums and union types, state values, HTTP API surface,
in-process event model, persistence, validation rules, configuration, and
acceptance criteria. Every type, field, enum value, endpoint, and event named
here is traceable to source under `libs/aja/*` or `apps/aja/*`.

A note on naming: the motion-pipeline service, its SDK, and several libraries
were authored under the `@lilith/` / "Lilith Motion Pipeline" name and retain
those internal labels in code comments and one package name
(`@lilith/svc-reference-video`). The domain itself is Aja. Where the code uses
`@lilith/...`, this document notes it.

---

## 1. Domain Inventory

Aja consists of **39 library projects** under `libs/aja/` and **5 application
projects** under `apps/aja/`.

### Applications (`apps/aja/`)

The table below lists each application, its published package name, runtime
framework, default port, and primary responsibility.

| Project               | Package                       | Runtime   | Default port | Responsibility                                                                                    |
| --------------------- | ----------------------------- | --------- | ------------ | ------------------------------------------------------------------------------------------------- |
| `svc-motion-pipeline` | `@aja/svc-motion-pipeline`    | Fastify   | `8090`       | Pipeline orchestration: configs, jobs, delivery                                                   |
| `svc-motion-ai`       | `@aja/svc-motion-ai`          | Hono      | `3040`       | Video-to-motion, retargeting, skeletal animation, embodied instruction                            |
| `svc-reference-video` | `@lilith/svc-reference-video` | library   | —            | Reference video library: ingest, scene/metadata, search, annotation, collaboration                |
| `cli`                 | `@aja/cli`                    | Commander | —            | Command-line interface (`aja` / `aja-motion`)                                                     |
| `docs`                | _(unnamed)_                   | —         | —            | Markdown documentation site (`README.md`, `api/`, `examples/`, `integration/`, `best-practices/`) |

`svc-reference-video` ships as a TypeScript library (`main: dist/index.js`, no
HTTP server entry) — its module exports the reference-video subsystem.

### Libraries (`libs/aja/`)

All libraries publish under the `@aja/` scope except the Python SDK. The 39
libraries listed below are alphabetically ordered for lookup.

`animation-blending`, `asset-storage`, `avatar-integration`, `avatar-library`,
`avatar-preview-ui`, `batch-inference`, `blend-shape-retargeting`,
`bone-mapping-ui`, `consent-management`, `content-moderation`,
`content-security`, `content-watermarking`, `data-retention`, `depth-sensing`,
`distributed-workers`, `domain-motion-pipelines`, `film-pipeline`,
`fitness-animation`, `human-mesh-recovery`, `model-optimization`,
`motion-formats`, `motion-integration`, `motion-pipeline-sdk`,
`motion-pipeline-sdk-python`, `motion-processing`, `motion-quality`,
`motion-validation`, `multi-view-reconstruction`, `neural-retargeting`,
`optimization-ik`, `pipeline-cache`, `pipeline-parallelism`, `pose-lifting`,
`privacy-protection`, `proportional-adaptation`, `result-aggregation`,
`semantic-retargeting`, `skeleton-mapping`, `video-chunking`.

`motion-pipeline-sdk-python` has no `package.json` `name`; its Python
distribution (`pyproject.toml`) is `aja-motion-pipeline` and its import package
is `aja_motion_pipeline`.

---

## 2. Core Motion Data Models (`@aja/motion-formats`)

`@aja/motion-formats` is the foundation library: it defines the canonical motion
data model used across all 39 Aja libraries and the per-format file structures
for import and export. No other Aja library defines its own motion primitives —
they all re-export or depend on this one. Source:
`libs/aja/motion-formats/src/types.ts`.

### 2.1 Math and Color Primitives

The math primitives below are used everywhere joint positions and rotations are
stored. All rotation representations are provided so callers can work in
whatever convention their rendering pipeline expects.

| Type          | Shape                                    |
| ------------- | ---------------------------------------- |
| `Point3D`     | `{ x, y, z: number }`                    |
| `Point2D`     | `{ x, y: number }`                       |
| `Quaternion`  | `{ x, y, z, w: number }`                 |
| `EulerAngles` | `{ x, y, z: number; order: EulerOrder }` |
| `AxisAngle`   | `{ axis: Point3D; angle: number }`       |
| `Matrix4x4`   | 16-number tuple, row-major               |
| `Matrix3x3`   | 9-number tuple, row-major                |
| `Color3`      | `{ r, g, b: number }`                    |
| `Color4`      | `{ r, g, b, a: number }`                 |

`EulerOrder` is one of `'XYZ' | 'XZY' | 'YXZ' | 'YZX' | 'ZXY' | 'ZYX'`.

### 2.2 `SkeletonJoint` and `SkeletonDefinition`

A skeleton is a tree of joints. `SkeletonJoint` represents one node in that tree
— its position, orientation, scale, and its connection to its parent and
children via index references.

`SkeletonJoint` fields:

| Field           | Type         | Notes               |
| --------------- | ------------ | ------------------- |
| `name`          | `string`     | required            |
| `index`         | `number`     | array index         |
| `parentIndex`   | `number`     | `-1` for root       |
| `localPosition` | `Point3D`    | required            |
| `localRotation` | `Quaternion` | required            |
| `localScale`    | `Point3D`    | required            |
| `worldPosition` | `Point3D`    | optional            |
| `worldRotation` | `Quaternion` | optional            |
| `children`      | `number[]`   | child joint indices |

`SkeletonDefinition` wraps the joint array with top-level metadata: `name`,
`joints: SkeletonJoint[]`, `rootIndex`, optional `restPose: AnimationFrame`,
optional `metadata`.

### 2.3 Animation Model

The animation model builds from individual keyframe values up to a full clip.
Understanding this hierarchy is important before reading the type definitions.

`KeyframeInterpolation` describes how values are interpolated between keyframes:
`'linear' | 'step' | 'cubic' | 'hermite' | 'bezier'`.

`AnimationProperty` identifies which aspect of a joint a curve animates — one of
`position.{x,y,z}`, `rotation.{x,y,z,w}`, `euler.{x,y,z}`, `scale.{x,y,z}`,
`blendShape`.

`Keyframe<T>` is a single timed sample: `time`, `value: T`,
`interpolation: KeyframeInterpolation`, optional `inTangent`/`outTangent`.

`AnimationCurve<T>` is the full set of keyframes for one property on one joint:
`jointName`, `property: AnimationProperty`, `keyframes: Keyframe<T>[]`.

`JointTransform` captures the transform state of a single joint at one moment:
`jointIndex`, `jointName`, `localPosition`, `localRotation`, `localScale`,
optional `worldPosition`/`worldRotation`.

`AnimationFrame` is the full skeleton state at one point in time: `time`,
`frameIndex`, `jointTransforms: JointTransform[]`, optional `rootPosition`,
`rootRotation`, `blendShapes: Map<string, number>`.

`AnimationClip` is the central motion object — a complete recorded or generated
animation sequence. Its fields are:

| Field        | Type                       | Required |
| ------------ | -------------------------- | -------- |
| `id`         | `string`                   | yes      |
| `name`       | `string`                   | yes      |
| `skeleton`   | `SkeletonDefinition`       | yes      |
| `frames`     | `AnimationFrame[]`         | yes      |
| `duration`   | `number` (seconds)         | yes      |
| `frameRate`  | `number`                   | yes      |
| `frameCount` | `number`                   | yes      |
| `loopable`   | `boolean`                  | yes      |
| `curves`     | `AnimationCurve<number>[]` | optional |
| `events`     | `AnimationEvent[]`         | optional |
| `metadata`   | `AnimationMetadata`        | optional |

`AnimationEvent` marks a named moment in the timeline: `time`, `name`, optional
`data`.

`AnimationMetadata` carries provenance and categorization for the clip:
`source`, optional `captureDate`, `performer`, `action`,
`category: MotionCategory`, `tags`, `quality: QualityMetrics`, `customData`.

`MotionCategory` classifies the type of motion:
`'locomotion' | 'combat' | 'dance' | 'sports' | 'gesture' | 'expression' | 'interaction' | 'idle' | 'transition' | 'other'`.

`QualityMetrics` carries the six scoring fields inline with the clip:
`jitterScore` (0–1, lower better), `footSlidingDistance` (meters),
`boneLengthVariance` (0–1, lower better), `smoothnessScore` (0–1, higher
better), `physicalPlausibility` (0–1, higher better), `overallScore` (0–1,
higher better).

### 2.4 Standard Skeleton Types

`StandardSkeletonType` is the union of all skeleton conventions that Aja knows
about out of the box:
`'mixamo' | 'unity-humanoid' | 'unreal-mannequin' | 'maya-hik' | 'motionbuilder' | 'cmu' | 'h36m' | 'coco' | 'openpose' | 'mediapipe-pose' | 'smpl' | 'smplx' | 'custom'`.

Two exported constants drive all cross-skeleton work and must stay index-aligned
— that is, index `i` in `STANDARD_JOINT_NAMES[type]` must correspond to index
`i` in `SKELETON_PARENT_INDICES[type]`:

- `STANDARD_JOINT_NAMES: Record<StandardSkeletonType, string[]>` — the canonical
  joint name list per skeleton (e.g., 65 names for `mixamo`, 22 for
  `unity-humanoid`, 22 for `unreal-mannequin`, 17 for `h36m`, 17 for `coco`, 34
  for `mediapipe-pose`, 24 for `smpl`, 55 for `smplx`).
- `SKELETON_PARENT_INDICES: Record<StandardSkeletonType, number[]>` — parent
  joint index per joint for the same skeletons.

### 2.5 Supported Motion Formats

`MotionFormat` is the union of all format identifiers:
`'bvh' | 'fbx' | 'gltf' | 'glb' | 'usd' | 'usda' | 'usdc' | 'usdz' | 'alembic' | 'abc' | 'c3d' | 'trc' | 'asf' | 'amc' | 'json' | 'internal'`.

Each format has a corresponding file-structure interface for typed I/O. The
per-format structures are:

- **BVH** — `BVHFile` (`hierarchy: BVHHierarchy`, `motion: BVHMotion`),
  `BVHJoint`, `BVHChannel` (`Xposition` … `Zrotation`), with `BVHImportOptions`
  / `BVHExportOptions` and `DEFAULT_BVH_*_OPTIONS`.
- **FBX** — `FBXNode`, `FBXProperty`, `FBXTransform`, `FBXAnimationStack`,
  `FBXAnimationLayer`, `FBXAnimationCurve`, `FBXSkeleton`; `FBXVersion`
  (`'7100' … '7700'`); `FBXNodeType`; import/export options + defaults.
- **glTF/GLB** — `GLTFFile` and the full glTF 2.0 object graph (`GLTFNode`,
  `GLTFSkin`, `GLTFAnimation`, `GLTFAccessor`, `GLTFBufferView`, …); GLB chunk
  magic constants `GLB_CHUNK_JSON` / `GLB_CHUNK_BIN`; import/export options.
- **USD** — `USDFile`, `USDPrim`, `USDAttribute`, `USDSkeleton`,
  `USDSkelAnimation`, `USDBlendShape`; `USDSchemaType`;
  `USDExportOptions.format` is `'usda' | 'usdc' | 'usdz'`; import/export
  options.
- **Alembic** — `AlembicFile`, `AlembicObject`, `AlembicProperty`,
  `AlembicXformSample`, `AlembicPolyMeshSample`, `AlembicSkeletonData`;
  `AlembicExportOptions.backend` is `'Ogawa' | 'HDF5'`.
- **C3D** — `C3DFile`, `C3DMarker`, `C3DAnalogChannel`, `C3DForcePlate`,
  `C3DEvent`, `C3DHeader`; `DEFAULT_C3D_IMPORT_OPTIONS` (`scaleFactor: 0.001`,
  mm→m).
- **TRC** — `TRCFile`, `TRCHeader`, `TRCMarker`; `DEFAULT_TRC_IMPORT_OPTIONS`.
- **ASF/AMC** — `ASFFile`, `ASFBone`, `ASFHierarchy`, `AMCFile`, `AMCFrame`;
  `DEFAULT_ASF_AMC_IMPORT_OPTIONS`.

The library's public entry point (`index.ts`) exports the functions
`importAnimation`, `exportAnimation`, `convertAnimation`, and
`detectFormat(filenameOrContent)` (returns `MotionFormat | null`). Three type
maps associate each format with its structures and options: `FormatFileTypeMap`,
`FormatExportOptionsMap`, and `FormatImportOptionsMap`. Note that
`FormatExportOptionsMap` defines export options only for `bvh`, `fbx`,
`gltf/glb`, `usd*`, and `alembic/abc` — C3D, TRC, and ASF/AMC have import
options but no export options entry.

### 2.6 Motion Clip Database

To make motion clips discoverable and reusable, the library provides a typed
database record and a rich query model.

`MotionClipEntry` — a database record for a motion clip: `id`, `name`, optional
`description`, `category: MotionCategory`, `tags`, `duration`, `frameRate`,
`frameCount`, `skeletonType: StandardSkeletonType`, `jointCount`,
`sourceFormat: MotionFormat`, optional `filePath`/`fileSize`,
`quality: QualityMetrics`, `metadata: AnimationMetadata`, `createdAt`,
`updatedAt`, `version`, optional `parentId`, `variants`.

`MotionClipQuery` supports filtering by `name`, `category`, `tags`
(`tagsMatchAll`), duration range, `skeletonType`, `minQuality`, `performer`,
`action`, date range, with `limit`/`offset` and `sortBy`
(`'name' | 'duration' | 'quality' | 'createdAt' | 'updatedAt'`) / `sortOrder`.
`MotionClipSearchResult` returns `entries`, `total`, `limit`, `offset`.
`MotionClipCollection` groups clip IDs.

---

## 3. Skeleton Models (`@aja/skeleton-mapping`)

`@aja/skeleton-mapping` extends the core skeleton model with richer typing for
retargeting operations. It re-exports the math and skeleton primitives from
`@aja/motion-formats` and adds its own broader skeleton modeling needed for
automatic bone matching, partial skeletons, and the normalized primal skeleton.
Source: `libs/aja/skeleton-mapping/src/types.ts`.

### 3.1 Skeleton Templates

`StandardSkeletonType` in this library is a wider union than in
`@aja/motion-formats`, covering more conventions: animation-software standards
(`mixamo`, `unity-humanoid`, `unreal-mannequin`, `maya-hik`, `motionbuilder`,
`blender-rigify`), mocap formats (`cmu`, `h36m`, `coco`, `openpose-body25`,
`openpose-body18`, `mediapipe-pose`, `mediapipe-holistic`), body models (`smpl`,
`smplx`, `star`), fitness/sports skeletons (`fitness-standard`, `yoga-enhanced`,
`dance-extended`), and `custom`.

`SkeletonTemplate` is the full definition used for matching and retargeting:
`id`, `name`, `type`, `description`, `joints: JointDefinition[]`,
`bones: BoneConnection[]`, `rootJointId`,
`defaultProportions: SkeletonProportions`, `regions: BodyRegion[]`,
`hasFingers`, `hasFacial`, `referenceHeight` (meters), optional `metadata`.

`JointDefinition` provides the per-joint detail needed for automatic matching:
`id`, `name`, `aliases: string[]`, `parentId` (`string | null`),
`defaultPosition`, `defaultRotation` (quaternion), `region: BodyRegion`,
`jointType: JointType`, optional `rotationLimits: RotationLimits`,
`isEndEffector`, `index`.

`BoneConnection` describes the relationship between two joints: `parentJointId`,
`childJointId`, `lengthRatio` (relative to skeleton height), optional `name`.

`BodyRegion` classifies which part of the body a joint belongs to:
`'root' | 'spine' | 'head' | 'left_arm' | 'right_arm' | 'left_hand' | 'right_hand' | 'left_leg' | 'right_leg' | 'left_foot' | 'right_foot' | 'face'`.

`JointType` is a 50+-value union covering the full human anatomy. The values
span core joints (`root`, `pelvis`, `spine`, `chest`, `upper_chest`, `neck`,
`head`), arm joints, the complete set of finger joints (`thumb_base/mid/tip`,
`index_*`, `middle_*`, `ring_*`, `pinky_*`), leg/foot joints, face joints
(`jaw`, `eye`, `eyebrow`, `nose`, `mouth`, `ear`), and generic types (`twist`,
`auxiliary`, `unknown`).

`RotationLimits` specifies anatomical range-of-motion constraints per axis:
per-axis `{ min, max }` for `x`, `y`, `z` (degrees).

`SkeletonProportions` captures 14 named body ratio fields — `headRatio`,
`neckRatio`, `torsoRatio`, `armRatio`, `forearmRatio`, `upperArmRatio`,
`legRatio`, `thighRatio`, `shinRatio`, `shoulderWidthRatio`, `hipWidthRatio`,
`armSpanRatio`, `handRatio`, `footRatio` — used for proportional adaptation
during retargeting. `DEFAULT_PROPORTIONS` provides average-adult values.

### 3.2 Skeleton Mapping

A `SkeletonMapping` records how joints from one skeleton correspond to joints of
another, with enough detail for the retargeter to handle merged joints, split
joints, and procedurally synthesized joints.

`SkeletonMapping` fields: `id`, `sourceTemplateId`, `targetTemplateId`,
`sourceSkeletonType`, `targetSkeletonType`, `jointMappings: JointMapping[]`,
`confidence`, `scaleFactor`, `createdBy` (`'manual' | 'automatic' | 'hybrid'`),
`createdAt`, optional `metadata`.

`JointMapping` describes one source-to-target joint connection: `sourceJointId`,
`targetJointId`, `confidence` (0–1), `mappingType: JointMappingType`, optional
`weight`, `rotationOffset`, `positionOffset`, `localScale`,
`additionalSources`/`additionalTargets`, `blendWeights`.

`JointMappingType` specifies the relationship kind:
`'direct' | 'merged' | 'split' | 'interpolated' | 'procedural' | 'ignored' | 'synthesized'`.

### 3.3 Automatic Matching

The automatic matcher combines four complementary strategies to handle skeleton
variations it has never seen before. `AutoMatchConfig` toggles each strategy
independently (`useNameMatching`, `useHierarchyMatching`,
`usePositionalMatching`, `useSemanticMatching`) and sets `minConfidence`, plus
fine-grained options for each strategy:

- `NameMatchOptions`: fuzzy matching, threshold, name normalization, alias use,
  custom mappings.
- `HierarchyMatchOptions`: match by depth/sibling/child count, `depthTolerance`.
- `PositionalMatchOptions`: position normalization, distance threshold,
  proportional matching.

`DEFAULT_AUTO_MATCH_CONFIG` enables all four strategies with
`minConfidence: 0.5` and `fuzzyThreshold: 0.8`.

`AutoMatchResult` carries the full outcome of a matching run: `mapping`,
`statistics: MatchStatistics`, `matchDetails: JointMatchDetail[]`,
`unmatchedSourceJoints`, `unmatchedTargetJoints`, `warnings`. `MatchStatistics`
records how many joints were matched by each of the four strategies.
`JointMatchDetail` records per-joint `matchMethod`
(`'name' | 'hierarchy' | 'position' | 'semantic' | 'manual' | 'none'`) and the
four strategy sub-scores.

### 3.4 Partial and Primal Skeletons

Partial skeletons allow retargeting only a subset of the body — for example,
applying captured upper-body motion to a procedurally animated lower body.

`PartialSkeletonConfig` specifies the subset: `includedRegions: BodyRegion[]`,
`includeIKTargets`, `boundaryHandling` (`'include' | 'exclude' | 'blend'`),
`preserveHierarchy`, optional `customRootJoint`. `PartialSkeleton` carries the
extracted joints, bones, root, and `boundaryJoints: BoundaryJoint[]`.

`PartialSkeletonPreset` names the common presets:
`'upper_body' | 'lower_body' | 'torso_only' | 'arms_only' | 'left_arm_only' | 'right_arm_only' | 'legs_only' | 'left_leg_only' | 'right_leg_only' | 'hands_only' | 'face_only' | 'head_and_hands' | 'full_body'`.

The primal skeleton is a topology-agnostic 18-joint humanoid skeleton used as a
normalization intermediate. `PrimalSkeleton` represents it; `PrimalJointId`
enumerates its 18 joints (`root`, `pelvis`, `spine`, `chest`, `neck`, `head`,
and left/right shoulder/elbow/wrist and hip/knee/ankle). `PrimalMapping` maps
any template to and from this primal form. Supporting constants:
`PRIMAL_BONE_LENGTHS` (primal bone length ratios) and `REGION_JOINTS` (maps each
`BodyRegion` to its constituent `JointType`s).

---

## 4. Motion Quality Models (`@aja/motion-quality`)

Source: `libs/aja/motion-quality/src/types.ts`.

Quality assessment in Aja is multi-dimensional: technical metrics (noise, foot
sliding, bone length variance), perceptual metrics (naturalness, style
consistency), and ground-truth benchmark metrics (MPJPE) are all first-class
types. The library is organized into four source files corresponding to these
dimensions.

`QualityRating` is the human-readable quality category:
`'excellent' | 'good' | 'acceptable' | 'poor' | 'unusable'`.

`QualityThresholds` defines the score cutoffs: `excellent` (default 0.90),
`good` (0.75), `acceptable` (0.60), `poor` (0.40); anything below `poor` is
`unusable`.

`MotionCategory` (this library's variant) covers body-part subsets in addition
to activity categories:
`'locomotion' | 'dance' | 'combat' | 'sports' | 'gesture' | 'facial' | 'full_body' | 'upper_body' | 'lower_body' | 'general'`.

`JitterAnalysis` is the detailed output of jitter measurement: `score` (0–1,
lower better), `averageAcceleration`, `peakAcceleration`,
`perJointScores: Map<string, number>`, `problematicFrames: number[]`, optional
`dominantFrequency` (Hz).

The four source files are: `quality-metrics.ts` (technical quality metrics),
`perceptual-metrics.ts` (naturalness, style consistency),
`ground-truth- comparison.ts` (MPJPE, PA-MPJPE, PCK, AUC), and `qa-pipeline.ts`
(automated QA pipeline configuration and reporting).

---

## 5. Motion Pipeline Service — Domain Objects

Source: `apps/aja/svc-motion-pipeline/src/types.ts` (and `config/`, `jobs/`,
`delivery/`). The service file headers label themselves
`@lilith/svc-motion-pipeline`.

The pipeline service is the job-management heart of Aja. Its domain objects
represent the full lifecycle of a motion processing job: configuration, job
submission, stage-by-stage execution, and result delivery.

### 5.1 Pipeline Domain and Stage Enums

These core enums define what kind of work the pipeline does and what states it
can be in. Every job submission references a `PipelineDomain` and
`QualityPreset`; every running job tracks a `JobStatus` and per-stage
`StageStatus`.

`PipelineDomain` — `'yoga' | 'fitness' | 'dance' | 'martial-arts' | 'general'`.

`QualityPreset` — `'draft' | 'standard' | 'high' | 'ultra'`.

`PipelineStage` (11 stages) —
`'ingestion' | 'validation' | 'preprocessing' | 'pose-estimation' | 'skeleton-fitting' | 'domain-analysis' | 'quality-assessment' | 'retargeting' | 'format-conversion' | 'postprocessing' | 'delivery'`.

`StageStatus` — `'pending' | 'running' | 'completed' | 'failed' | 'skipped'`.

`JobPriority` — `'critical' | 'high' | 'normal' | 'low' | 'background'`.

`JobStatus` —
`'pending' | 'queued' | 'running' | 'paused' | 'completed' | 'failed' | 'cancelled' | 'timeout'`.

### 5.2 Pipeline Configuration

A `PipelineConfiguration` is the complete description of how the pipeline should
process a job — which stages are enabled, what quality preset applies, input
constraints, and output format specification.

`PipelineConfiguration` fields: `id`, `name`, `description`, `version`,
`domain: PipelineDomain`, `qualityPreset: QualityPreset`,
`stages: StageConfig[]`, `inputConstraints: InputConstraints`,
`outputSpec: OutputSpec`, `globalTimeoutMs`, `failFast`, `metadata`,
`createdAt`, `updatedAt`.

`StageConfig` controls one stage: `name: PipelineStage`, `enabled`, `params`,
`timeoutMs`, `retry: StageRetryConfig`, `dependencies: PipelineStage[]`,
`parallel`.

`StageRetryConfig`: `maxAttempts`, `initialDelayMs`, `maxDelayMs`,
`backoffMultiplier`.

`InputConstraints`: `acceptedFormats`, `minFrameRate`, `maxFrameRate`,
`minDurationSec`, `maxDurationSec`, `maxFileSizeBytes`, optional
`requiredSkeletonType`.

`OutputSpec`: `primaryFormat`, `secondaryFormats`, `includeIntermediates`,
`compression: CompressionSettings`, `includeAnalytics`, `includeQualityMetrics`.
`CompressionSettings.algorithm` is `'none' | 'gzip' | 'lz4' | 'zstd'`, `level`
1–9.

`QualityPresetConfig` captures the parameters for one preset: `name`,
`description`, `processingTimeMultiplier`, `targetQuality` (0–1),
`resolutionScale`, `targetFrameRate`, `features: QualityFeatures`.
`QualityFeatures` has eight boolean toggles: `noiseReduction`,
`motionSmoothing`, `jitterCorrection`, `footSlidingCorrection`,
`contactDetection`, `physicsRefinement`, `ikCleanup`, `multiViewFusion`.

The four built-in presets (from `config/pipeline-config.ts`), seeded into the
service on startup, cover the full quality/speed range:

| Preset     | `processingTimeMultiplier` | `targetQuality` | `resolutionScale` | `targetFrameRate` |
| ---------- | -------------------------- | --------------- | ----------------- | ----------------- |
| `draft`    | 0.3                        | 0.6             | 0.5               | 15                |
| `standard` | 1.0                        | 0.8             | 1.0               | 30                |
| `high`     | 2.0                        | 0.92            | 1.0               | 60                |
| `ultra`    | 4.0                        | 0.98            | 1.0               | (60+)             |

`draft` enables no quality features. `standard` enables noise reduction, motion
smoothing, jitter correction, and IK cleanup. `high` adds foot-sliding
correction and contact detection. `ultra` is the maximum-quality preset.

On startup, the service also seeds **20 default configurations** — one for every
`PipelineDomain` × `QualityPreset` combination (5 domains × 4 presets), named
`{domain}-{preset}`.

### 5.3 YAML Pipeline Definitions

Pipeline configurations can be written as YAML files and imported or exported
via the API. The YAML schema is typed as `PipelineDefinition`: `apiVersion`,
`kind: 'PipelineDefinition'`, `metadata: PipelineDefinitionMetadata`,
`spec: PipelineSpec`.

`PipelineSpec` holds `domain`, `qualityPreset`, `stages: StageSpec[]`,
`input: InputSpec`, `output: OutputSpecification`, `settings: GlobalSettings`.
Supporting types: `StageSpec`, `RetrySpec`
(`backoff: 'exponential' | 'linear' | 'fixed'`), `InputSpec`, `FrameRateSpec`,
`DurationSpec`, and `OutputSpecification`.

The conversion functions `parsePipelineDefinition` and
`serializePipelineDefinition` convert between `PipelineConfiguration` and YAML.

### 5.4 Job Objects

These types cover the complete lifecycle of a submitted job, from initial
request through execution state to final results.

`JobSubmissionRequest`: optional `jobId`, `pipelineConfig`
(`string | PipelineConfiguration`), `input: JobInput`, `options: JobOptions`,
optional `webhook: WebhookConfig`, optional `metadata`.

`JobInput`: `sourceType` (`'url' | 'upload' | 's3' | 'reference'`), `source`,
optional `formatHint`, `params`.

`JobOptions`: `priority: JobPriority`, optional `timeoutMs`, `tags`,
`scheduledAt`, `idempotencyKey`, `streamProgress`, `parentJobId`,
`notifyOnComplete`.

`JobEnvelope` is the full job state object returned by the API: `id`,
`tenantId`, `userId`, `status: JobStatus`,
`pipelineConfig: PipelineConfiguration`, `input`, `options`,
`progress: JobProgress`, `stages: StageExecution[]`, optional
`result: JobResult`, optional `error: JobError`, optional `webhook`, `metadata`,
`stats: JobStats`, `createdAt`, optional `startedAt`/`completedAt`, `updatedAt`.

`JobProgress`: `percentage` (0–100), optional `currentStage`, `stagePercentage`,
`message`, optional `estimatedRemainingMs`, `framesProcessed`, `totalFrames`.

`StageExecution` tracks one stage's run: `stage: PipelineStage`,
`status: StageStatus`, `progress`, `attempt`, optional
`startedAt`/`completedAt`/`durationMs`/`outputRef`, optional
`error: StageError`, optional `metrics`.

`StageError`: `code`, `message`, optional `stack`, `retryable: boolean`.

`JobStats` tracks resource usage: `totalDurationMs`, `queueWaitMs`,
`retryCount`, `peakMemoryBytes`, `cpuTimeMs`, `gpuTimeMs`, `inputSizeBytes`,
`outputSizeBytes`.

`JobError`: `code`, `message`, optional `stage`, `stack`, `details`,
`retryable`, `timestamp`.

`JobResult` holds the deliverables: `primaryOutput: OutputReference`,
`secondaryOutputs: OutputReference[]`, optional `analytics: AnalyticsResult`,
`qualityMetrics: QualityMetrics`, `intermediates: IntermediateResult[]`.

`OutputReference`: `format`, `storageType` (`'local' | 's3' | 'gcs' | 'azure'`),
`path`, `sizeBytes`, `checksum`, optional `downloadUrl`, `urlExpiresAt`.

`AnalyticsResult`: `domainAnalysis`, `motionStats: MotionStatistics`,
`qualityAssessment: QualityAssessment`. `MotionStatistics` carries duration,
frame count, frame rate, joint count, `rootVelocity: VelocityStats`
(`mean/max/min/std`), and `rangeOfMotion`.

`QualityAssessment`: `overallScore` (0–100), `smoothness`, `naturalness`,
`jitterAmount`, `footSliding`, `penetrations`, `issues: QualityIssue[]`.
`QualityIssue`: `type`, `severity` (`'low' | 'medium' | 'high'`),
`frameStart`/`frameEnd`, `description`.

`QualityMetrics` (service variant): optional `mpjpe`, `paMpjpe`, `pck`,
`custom`.

For list views, `JobSummary` is the lightweight projection: `id`, `status`,
`domain`, `progress`, `priority`, `tags`, `createdAt`, `updatedAt`.

`JobListQuery` supports filtering by `status[]`, `domain[]`, `tags[]`, `userId`,
`parentJobId`, created-after/before, `sortBy`
(`'createdAt' | 'updatedAt' | 'priority' | 'status'`), `sortOrder`, `page`,
`pageSize`. `JobListResponse` returns `jobs: JobSummary[]`, `total`, `page`,
`pageSize`, `totalPages`.

`BatchOperationRequest`: `jobIds`, `operation`
(`'cancel' | 'retry' | 'prioritize'`), optional `params`.
`BatchOperationResponse` returns `succeeded` and
`failed: BatchOperationFailure[]`.

### 5.5 Result Delivery Objects

These types handle the outbound side: notifying callers via webhooks and
providing signed URLs for downloading results.

`WebhookConfig`: `url`, `method` (`'POST' | 'PUT'`), optional `headers`,
`secret`, `events: WebhookEvent[]`, `retry: WebhookRetryConfig`.

`WebhookEvent` is the union of all events that can be delivered to a registered
webhook endpoint — 9 values:
`'job.queued' | 'job.started' | 'job.progress' | 'job.completed' | 'job.failed' | 'job.cancelled' | 'stage.started' | 'stage.completed' | 'stage.failed'`.
See §11 for per-event data shapes.

`WebhookPayload`: `event`, `eventId`, `jobId`, `timestamp`,
`data: WebhookEventData`, `delivery: WebhookDeliveryMeta`. `WebhookEventData` is
a discriminated union over per-event shapes (`JobQueuedEventData`,
`JobStartedEventData`, `JobProgressEventData`, `JobCompletedEventData`,
`JobFailedEventData`, `JobCancelledEventData`, `StageEventData`).

`WebhookDeliveryStatus` tracks delivery state: `deliveryId`, `eventId`, `jobId`,
`event`, `status`
(`'pending' | 'in_progress' | 'delivered' | 'failed' | 'retrying'`), `attempts`,
optional `lastAttemptAt`/`nextRetryAt`/ `responseStatus`/`errorMessage`,
`createdAt`.

For polled job status, `PollResponse` is
`{ job: JobEnvelope, polling: PollingHints }`. `PollingHints` carries
`recommendedIntervalMs`, `isActive`, optional `estimatedCompletionTime`,
`serverTime`.

For streaming, `ProgressStreamMessage`
(`type: 'progress' | 'stage' | 'complete' | 'error' | 'keepalive'`) carries SSE
payloads.

For downloads, `DownloadRequest` / `DownloadResponse` / `DownloadMetadata` cover
signed-URL generation. `DownloadMetadata.checksumAlgorithm` is
`'sha256' | 'md5'`.

### 5.6 Service Configuration Objects

These objects are passed to the service at startup to configure its runtime
dependencies.

`ServiceConfig` is the top-level service configuration: `serviceName`, `port`,
`redis: RedisConfig`, `storage: StorageConfig`, `queue: QueueConfig`,
`webhook: WebhookServiceConfig`, `rateLimit: RateLimitConfig`,
`metrics: MetricsConfig`.

The nested configs are:

- `RedisConfig`: `host`, `port`, optional `password`, `db`, `keyPrefix`.
- `StorageConfig`: `type` (`'local' | 's3' | 'gcs' | 'azure'`), optional
  `endpoint`, `bucket`, optional `accessKey`/`secretKey`/`region`, `pathPrefix`,
  `signedUrlExpiration`.
- `QueueConfig`: `namePrefix`, `concurrency`, `defaultTimeoutMs`, `maxRetries`,
  `cleanupAfterMs`.
- `WebhookServiceConfig`: `maxConcurrent`, `deliveryTimeoutMs`,
  `maxPayloadSize`, `signatureAlgorithm` (`'sha256' | 'sha512'`).
- `RateLimitConfig`: `maxRequests`, `windowMs`, `perTenant`.
- `MetricsConfig`: `enabled`, `endpoint`, `latencyBuckets`.

`QueueStatistics`, `ServiceHealth`
(`status: 'healthy' | 'degraded' | 'unhealthy'`), and `ComponentHealth` cover
runtime observability.

### 5.7 Pipeline Processing State and V2 Deferral

The job processor is `createPipelineProcessor()` in
`apps/aja/svc-motion-pipeline/src/jobs/pipeline-processor.ts` (authored under
task `V1-P2-1775`). It drives each enabled stage and, for V1, executes
ingestion, validation, preprocessing, quality-assessment, format-conversion,
postprocessing, and delivery using `@aja/motion-formats` for ingest/export.

**Four stages are V2-deferred** per descope decision `V1-P2-0331`. The constant
`V2_DEFERRED_STAGES` is the set
`{ 'pose-estimation', 'skeleton-fitting', 'domain-analysis', 'retargeting' }`.
When a pipeline configuration enables one of these stages, the processor marks
it `'skipped'` and emits a structured log line citing `V1-P2-0331` — it never
silently fails. The `naturalness` field of `QualityAssessment` is pinned to the
smoothness score as a physical proxy because a real naturalness score requires a
V2 ML model.

---

## 6. Motion Pipeline Service — HTTP API

The motion pipeline service is a Fastify server built via `@lilith/fastify-core`
(`apps/aja/svc-motion-pipeline/src/app.ts`). All routes are under the
`/v1/pipeline/...` prefix except `/health`. Tenant and user identity are read
from the `x-tenant-id` and `x-user-id` request headers, defaulting to `default`
/ `anonymous`. There is no dedicated auth middleware in code; authorization is
header-driven.

### 6.1 Health and Statistics

| Method | Path                 | Purpose                                                    |
| ------ | -------------------- | ---------------------------------------------------------- |
| `GET`  | `/health`            | `ServiceHealth` — component health + uptime                |
| `GET`  | `/v1/pipeline/stats` | `QueueStatistics` — waiting/active/completed/failed counts |

### 6.2 Configuration Routes

Pipeline configurations can be created from presets, from a JSON body, or from a
YAML definition, and can be exported back to YAML.

| Method   | Path                                  | Purpose                                         |
| -------- | ------------------------------------- | ----------------------------------------------- |
| `GET`    | `/v1/pipeline/configs`                | List configs (filter `domain`, `qualityPreset`) |
| `GET`    | `/v1/pipeline/configs/:configId`      | Get config by ID                                |
| `GET`    | `/v1/pipeline/configs/by-name/:name`  | Get config by name                              |
| `POST`   | `/v1/pipeline/configs`                | Create config from `domain`/`qualityPreset`     |
| `POST`   | `/v1/pipeline/configs/yaml`           | Create config from a YAML body                  |
| `GET`    | `/v1/pipeline/configs/:configId/yaml` | Export a config as YAML (`text/yaml`)           |
| `DELETE` | `/v1/pipeline/configs/:configId`      | Delete a config (204 on success)                |
| `GET`    | `/v1/pipeline/presets`                | List the four `QUALITY_PRESETS`                 |

### 6.3 Job Routes

| Method  | Path                                | Purpose                                                          |
| ------- | ----------------------------------- | ---------------------------------------------------------------- |
| `POST`  | `/v1/pipeline/jobs`                 | Submit a job; returns `202` with `jobId`, `pollUrl`, `streamUrl` |
| `GET`   | `/v1/pipeline/jobs/:jobId`          | Poll job — returns `PollResponse`                                |
| `GET`   | `/v1/pipeline/jobs`                 | List jobs for the tenant (`JobListQuery`)                        |
| `POST`  | `/v1/pipeline/jobs/:jobId/cancel`   | Cancel a job                                                     |
| `POST`  | `/v1/pipeline/jobs/:jobId/retry`    | Retry a failed job                                               |
| `PATCH` | `/v1/pipeline/jobs/:jobId/priority` | Update job priority                                              |
| `POST`  | `/v1/pipeline/jobs/batch`           | Batch `cancel` / `retry` / `prioritize`                          |

### 6.4 Download Routes

Outputs are only accessible after a job reaches `completed` status. Download
URLs are time-limited signed URLs; callers should not store them permanently.

| Method | Path                                | Purpose                                          |
| ------ | ----------------------------------- | ------------------------------------------------ |
| `GET`  | `/v1/pipeline/jobs/:jobId/outputs`  | List available outputs (job must be `completed`) |
| `POST` | `/v1/pipeline/jobs/:jobId/download` | Generate a signed download URL                   |

### 6.5 Webhook Routes

| Method | Path                                      | Purpose                           |
| ------ | ----------------------------------------- | --------------------------------- |
| `GET`  | `/v1/pipeline/jobs/:jobId/webhooks`       | List webhook deliveries for a job |
| `GET`  | `/v1/pipeline/webhooks/:deliveryId`       | Get a webhook delivery status     |
| `POST` | `/v1/pipeline/webhooks/:deliveryId/retry` | Retry a webhook delivery          |
| `GET`  | `/v1/pipeline/webhooks/stats`             | Webhook delivery statistics       |

### 6.6 Streaming Routes (Server-Sent Events)

Job progress is delivered over **Server-Sent Events** (`text/event-stream`) —
not WebSocket. The stream emits an initial `connected` message, then `progress`,
`stage`, `complete`, and `error` messages as the job runs. The stream endpoint
requires the job to be in an active state (`pending`, `queued`, or `running`).

| Method | Path                              | Purpose                                         |
| ------ | --------------------------------- | ----------------------------------------------- |
| `GET`  | `/v1/pipeline/jobs/:jobId/stream` | SSE stream of job progress (job must be active) |
| `GET`  | `/v1/pipeline/streams/stats`      | Active SSE connection count                     |

Error responses across the API use a JSON body `{ code, message }` (with
optional `errors` for validation). Defined error codes include:
`CONFIG_NOT_FOUND`, `JOB_NOT_FOUND`, `JOB_NOT_COMPLETE`, `JOB_NOT_ACTIVE`,
`INVALID_CONFIG`, `YAML_PARSE_ERROR`, `JOB_SUBMISSION_FAILED`,
`CANCELLATION_FAILED`, `RETRY_FAILED`, `UPDATE_FAILED`, `DOWNLOAD_FAILED`,
`DELIVERY_NOT_FOUND`.

---

## 7. Motion AI Service — HTTP API

The Motion AI service is a Hono application
(`apps/aja/svc-motion-ai/src/app.ts`) that listens on `PORT` (default `3040`).
It applies `cors()` and `logger()` middleware globally. There is no auth
middleware in code.

The service composes five internal services: `VideoToMotionService`,
`MotionRetargetingService`, `SkeletalAnimationMotionService`,
`VideoAnalysisService`, and `EmbodiedInstructionService` (the embodied service
depends on the skeletal-animation and video-analysis services).

| Method | Path                                              | Purpose                                                              |
| ------ | ------------------------------------------------- | -------------------------------------------------------------------- |
| `GET`  | `/health`                                         | `{ status: 'healthy', service: 'aja-motion-ai' }`                    |
| `GET`  | `/api/v1/embodied-instruction/capabilities`       | `EmbodiedInstructionCapabilities` — version, domains, endpoints      |
| `POST` | `/api/v1/video-to-motion/process`                 | Process a video into motion (`videoSource`, `sourceType`, `options`) |
| `POST` | `/api/v1/retargeting/retarget`                    | Retarget a motion clip (`motionId`, `targetSkeletonId`, `config`)    |
| `POST` | `/api/v1/skeletal/animate`                        | Generate a procedural idle frame (`config`, `deltaTime`)             |
| `POST` | `/api/v1/analysis/analyze`                        | Run video analysis                                                   |
| `POST` | `/api/v1/embodied-instruction/demonstration-plan` | Build structured study moments from a coached demonstration          |
| `POST` | `/api/v1/embodied-instruction/coaching-overlay`   | Build a real-time coaching overlay                                   |
| `POST` | `/api/v1/embodied-instruction/session-handoff`    | Build a session handoff to a downstream destination                  |

### 7.1 Embodied-Instruction Request Validation (Zod)

Three of the embodied-instruction endpoints validate the request body with Zod
schemas (`demonstrationRequestSchema`, `coachingOverlaySchema`,
`sessionHandoffSchema`). On failure they return HTTP `400` with
`{ error, issues }` where `error` is one of `INVALID_DEMONSTRATION_REQUEST`,
`INVALID_COACHING_OVERLAY_REQUEST`, or `INVALID_SESSION_HANDOFF_REQUEST`.

The validated enum values shared across these schemas are:

- `domain` —
  `'fitness' | 'yoga' | 'dance' | 'martial-arts' | 'sports' | 'rehabilitation'`.
- demonstration `modality` — `'live' | 'recorded' | 'annotated'`.
- coaching `learnerStage` — `'novice' | 'developing' | 'advanced' | 'expert'`.
- session-handoff `destination` — `'lesson_path' | 'study_pack' | 'tutoring'`.
- capture `format` — `'mp4' | 'webm' | 'mov'`.

`getCapabilities()` reports `apiVersion`, `stability: 'stable'`,
`service: 'aja-motion-ai'`, the supported domains, and the embodied-instruction
endpoint list with method, path, and purpose.

### 7.2 Service-Internal AI Modules

Beyond the exposed routes, `svc-motion-ai/src/` contains internal module trees
wired by the five services. These modules are not all directly exposed as
routes:

- `video-to-motion/` — video preprocessing, multi-model pose estimation
  (`PoseEstimationModel`, `MediaPipePoseLandmark`, `MoveNetKeypoint`,
  `OpenPoseBody25Keypoint`, `UnifiedKeypointName`, `HandKeypoint`), temporal
  processing, keypoint normalization.
- `motion-retargeting/`, `skeletal-animation-motion/`, `video-analysis/`,
  `embodied-instruction/`.
- `live-motion-capture/` — webcam capture, real-time pose estimation, streaming
  pose lift, real-time retargeting, live avatar driving.
- `mobile-live-capture/` — iOS and Android capture, on-device processing, mobile
  streaming.
- `motion-enhancement/` — motion synthesis, motion style transfer, motion
  super-resolution, physics refinement. Key types in this module:
  `DiffusionModelType`, `RotationRepresentation`, `MotionSequence`,
  `MotionTensor`.
- `providers/video-router.ts` — routes video input to the appropriate processing
  path.

---

## 8. Reference Video Service — Domain Objects

Source: `apps/aja/svc-reference-video/src/types.ts`. Package
`@lilith/svc-reference-video`. This module exports the reference-video
subsystem: video ingestion, scene detection, metadata extraction,
categorization, search, annotation, and collaboration. Being a library rather
than a server, it is imported by other Aja code rather than called over HTTP.

### 8.1 Branded Identifiers

The reference video subsystem uses branded string IDs throughout for type
safety. There are 12 ID types, each with a matching `generate*Id` factory:
`VideoId`, `SegmentId`, `AnnotationId`, `ClipId`, `CategoryId`, `TagId`,
`WorkspaceId`, `UserId`, `CommentId`, `ReviewId`, `AssignmentId`, `VersionId`,
`EmbeddingId`.

### 8.2 Video Metadata

The metadata model covers full ffprobe-style stream descriptions for any
ingested video.

`VideoCodec` —
`'h264' | 'h265' | 'vp8' | 'vp9' | 'av1' | 'prores' | 'dnxhd' | 'unknown'`.
`AudioCodec` — `'aac' | 'mp3' | 'opus' | 'vorbis' | 'pcm' | 'ac3' | 'unknown'`.
`ContainerFormat` —
`'mp4' | 'mov' | 'mkv' | 'webm' | 'avi' | 'mxf' | 'ts' | 'unknown'`.
`ColorSpace` — `'bt709' | 'bt2020' | 'srgb' | 'p3' | 'rec601' | 'unknown'`.
`PixelFormat` —
`'yuv420p' | 'yuv422p' | 'yuv444p' | 'rgb24' | 'rgba' | 'unknown'`.

`VideoStreamMetadata`, `AudioStreamMetadata`, and `VideoMetadata` capture the
full per-stream and file-level metadata.

### 8.3 Video Entity

`VideoSourceType` describes where a video came from:
`'upload' | 'url_import' | 'youtube' | 'vimeo' | 'clip_extraction' | 'processed'`.

`VideoStatus` tracks the processing lifecycle:
`'uploading' | 'processing' | 'transcoding' | 'analyzing' | 'ready' | 'error' | 'archived'`.

`Video` is the top-level entity: `id`, `name`, optional `description`,
`originalFilename`, `sourceType`, optional `sourceUrl`, `status`, optional
`errorMessage`, `workspaceId`, `createdBy`, timestamps, optional `metadata`,
`originalPath`, optional `hlsManifestUrl`/`dashManifestUrl`,
`renditions: VideoRendition[]`, optional `posterUrl`, optional
`thumbnailSprite: ThumbnailSprite`, `categoryIds`, `tagIds`, `currentVersionId`,
optional `parentVideoId`, `isArchived`, `customMetadata`.

### 8.4 Scene, Person, and Activity Detection

Aja automatically analyzes ingested videos to identify scene cuts, detect
people, and classify activities — making the library searchable without manual
annotation.

`SceneDetectionAlgorithm` —
`'content_aware' | 'adaptive_content' | 'threshold' | 'histogram' | 'deep_learning'`.
`SceneTransitionType` —
`'cut' | 'fade_in' | 'fade_out' | 'dissolve' | 'wipe' | 'unknown'`.
`DetectedScene`, `SceneDetectionConfig`, and `SceneDetectionResult` model scene
cuts.

`DetectedPerson`, `ActivityClassification`, `PersonDetectionResult`, and
`ActivityDetectionResult` model person tracking and activity classification.

### 8.5 Segments, Annotations, and Clips

`SegmentType` classifies regions within a video:
`'scene' | 'shot' | 'action' | 'movement' | 'rest' | 'transition' | 'repetition' | 'custom'`.
`Segment` is a named temporal region with frame/time bounds and labels.

`AnnotationType` enumerates the spatial forms an annotation can take:
`'point' | 'bounding_box' | 'polygon' | 'polyline' | 'skeleton' | 'label' | 'text' | 'timestamp' | 'correction'`.
An `Annotation` carries the type-specific payload as `boundingBox: BoundingBox`,
`points: PolygonPoint[]`, `skeleton: SkeletonPose`, or `text`. `SkeletonPose`
holds `format`, `joints: SkeletonJoint[]`, `confidence`, `is3D`.

`Clip` is an extracted region of a source video with extraction `status`
(`'pending' | 'processing' | 'ready' | 'error'`).

### 8.6 Categories, Tags, Search, and Collaboration

`Category` is hierarchical: each category has a `parentId`, `path`, and `depth`.
`Tag` carries a normalized name and usage count. `AutoTaggingConfig` governs the
automatic tagging pipeline.

The search system uses vector embeddings for semantic retrieval. `EmbeddingType`
identifies the model used:
`'clip_visual' | 'clip_text' | 'videomae' | 'optical_flow' | 'motion_signature' | 'audio'`.
`VideoEmbedding` stores a `Float32Array` vector with `dimension`, `modelId`,
`modelVersion`.

`SearchQuery` supports four search modes — `text`, `visual`, `motion`, and
`hybrid` — with filters, pagination, and sort. `SearchResult`/`SearchResultItem`
carry scored results. Three backends are present in tests:
`video-search-clip.test.ts` (CLIP embeddings),
`video-search-meilisearch.test.ts` (text search via Meilisearch), and
`video-search-qdrant.test.ts` (vector search via Qdrant).

The collaboration model covers workspaces, review workflows, and version
history:

`WorkspaceRole` — `'owner' | 'admin' | 'editor' | 'reviewer' | 'viewer'`.
`Workspace`, `WorkspaceSettings`, and `WorkspaceMember` model collaboration
spaces.

`Comment`, `Review` (`ReviewStatus`:
`'pending' | 'in_progress' | 'needs_revision' | 'approved' | 'rejected'`), and
`Assignment` (`AssignmentType`, `AssignmentStatus`, `AssignmentPriority`) model
review workflows.

`Version` (`VersionType`: `'video' | 'annotation' | 'segment' | 'metadata'`)
models the version history of any of these entities.

### 8.7 Errors

`ReferenceVideoErrorCode` is a TypeScript `enum` with values organized by
category. General codes include `UPLOAD_FAILED`, `UNSUPPORTED_FORMAT`, and
`FILE_TOO_LARGE`. Processing codes include `PROCESSING_FAILED` and
`TRANSCODING_FAILED`. Analysis codes include `SCENE_DETECTION_FAILED`. Search
codes include `SEARCH_FAILED`. Collaboration codes include
`WORKSPACE_NOT_FOUND`. Version codes include `VERSION_NOT_FOUND` and
`ROLLBACK_FAILED`. `ReferenceVideoError` is the corresponding `Error` subclass
with `code` and `details`.

---

## 9. Cross-Domain Integration (`@aja/motion-integration`)

`@aja/motion-integration` provides the typed contracts and adapter
implementations for moving Aja motion data into other Oshun domains. Rather than
allowing downstream domains to import Aja's internal types directly, all data
crosses the domain boundary through these adapters. Source:
`libs/aja/motion-integration/src/`. The library file header labels itself
"cross-domain integration adapters for Lilith motion pipeline."

`IntegrationDomain` — `'yemaya' | 'isis' | 'bellona' | 'sophia'`.

`AdapterStatus` — `'disconnected' | 'connecting' | 'connected' | 'error'`.

Branded IDs used across all adapters: `MotionAssetId`, `MotionProjectId`,
`MotionSessionId`, `MotionModelId`, `MotionJobId`, `MotionKnowledgeId`,
`MotionCitationId`.

`BaseAdapterConfig` is common adapter configuration: `domain`, `endpoint`,
optional `auth: AuthConfig`, `timeoutMs`, `retry: RetryConfig`, `debug`.
`AuthConfig.type` is `'api-key' | 'bearer' | 'oauth2' | 'none'`.

Shared exported types include `MotionDataFormat`, `MotionDomainType`,
`MotionQualityLevel`, `MotionAssetMetadata`, `MotionSessionMetadata`,
`SubjectInfo`, `CaptureEnvironment`, `SessionQualityMetrics`,
`IntegrationEventType`, `IntegrationEvent`, `IntegrationEventHandler`,
`IntegrationResult`, `IntegrationError`, `PaginationOptions`,
`PaginatedResponse`, `SortOptions`, `DateRangeFilter`, `CrossDomainReference`,
`LinkedAsset`, and the `IntegrationAdapter` interface.

The four adapter modules are each implemented as a separate directory with
`adapter.ts`, `index.ts`, and `types.ts`: `bellona/`, `isis/`, `sophia/`,
`yemaya/`. They are importable from subpaths (e.g.,
`@aja/motion-integration/yemaya`).

---

## 10. Motion Pipeline SDK (`@aja/motion-pipeline-sdk`)

The TypeScript SDK is the recommended way for Node.js applications to interact
with the motion pipeline service. It provides a fully typed client, builder
patterns for configuration and job submission, and SSE streaming for progress
monitoring. Source: `libs/aja/motion-pipeline-sdk/src/`.

### 10.1 Client

The exported client class is **`MotionPipelineClient`** (not `AjaClient`). The
SDK also exports `ConfigBuilder`, `JobBuilder`, and a `createClient(config)`
convenience function. `MotionPipelineClient` is constructed with an `SDKConfig`.

`SDKConfig` fields: `baseUrl` (required), optional `auth: AuthConfig`,
`tenantId`, `userId`, `timeoutMs`, `retry: RetryConfig`, `debug`, custom
`fetch`, custom `headers`. `DEFAULT_SDK_CONFIG` sets `timeoutMs: 30000` and a
retry policy of 3 attempts, exponential backoff (`multiplier: 2`, 1s initial,
30s max), retrying on status codes `408, 429, 500, 502, 503, 504`.

`AuthType` — `'api-key' | 'bearer' | 'oauth2' | 'none'`. For `api-key` the
client sends `Authorization: ApiKey <key>`; for `bearer`/`oauth2` it sends
`Authorization: Bearer <token>`. OAuth2 uses the client-credentials grant and
caches the token with a 30-second expiry buffer.

### 10.2 Client Operations

All `MotionPipelineClient` methods return `SDKResult<T>` — a discriminated union
`SuccessResult<T> | ErrorResult`. The client covers every route exposed by the
pipeline service (§6):

- **Health/stats**: `health()`, `getStats()`.
- **Config**: `listConfigs()`, `getConfig()`, `getConfigByName()`,
  `createConfig()`, `createConfigFromYaml()`, `exportConfigAsYaml()`,
  `deleteConfig()`, `getQualityPresets()`.
- **Jobs**: `submitJob()`, `getJob()`, `listJobs()`, `cancelJob()`,
  `retryJob()`, `updateJobPriority()`, `batchOperation()`.
- **Downloads**: `listOutputs()`, `generateDownloadUrl()`.
- **Webhooks**: `getWebhookDeliveries()`, `getWebhookDeliveryStatus()`,
  `retryWebhookDelivery()`, `getWebhookStats()`.
- **Streaming**: `streamJobProgress()` (Server-Sent Events via `EventSource`),
  `closeAllStreams()`, `activeStreamCount`.
- **Convenience**: `submitAndWait()` (polls until terminal state),
  `configBuilder()`, `jobBuilder()`.

### 10.3 SDK Domain Types

The SDK mirrors the service's domain types with minor differences noted below.

`PipelineDomain`, `QualityPreset`, `PipelineStage`, and `StageStatus` mirror the
service. The SDK's `JobStatus` is the narrower set:
`'pending' | 'queued' | 'running' | 'completed' | 'failed' | 'cancelled'` (no
`paused` or `timeout`). `JobPriority` matches the service.

Branded IDs: `JobId`, `ConfigId`, `DeliveryId`, `TenantId`, `UserId`, with
constructors `jobId()`, `configId()`, `deliveryId()`.

Job-side types: `VideoInputSource` (`InputSourceType`:
`'url' | 'upload' | 'storage' | 'stream'`), `WebhookConfig` (SDK events:
`'queued' | 'started' | 'progress' | 'completed' | 'failed' | 'cancelled'`),
`JobOptions`, `JobSubmitRequest`, `Job`, `JobResult`, `StageResult`,
`JobListQuery`, `JobSubmitResponse`, `JobListResponse`, `JobCancelRequest`,
`BatchOperationRequest` (`BatchOperationType`:
`'cancel' | 'retry' | 'update-priority'`), `BatchOperationResult`.

`StreamEventType` enumerates the 12 SSE event types observable on the
`/v1/pipeline/jobs/:jobId/stream` channel:
`'connected' | 'job:queued' | 'job:started' | 'job:progress' | 'job:completed' | 'job:failed' | 'job:cancelled' | 'stage:started' | 'stage:progress' | 'stage:completed' | 'stage:failed' | 'heartbeat' | 'error'`.
`StreamEvent<T>` carries a type-mapped `data` payload per event type.

`SDKErrorCodes` is a constant map of SDK error codes: `SDK_INVALID_CONFIG`,
`SDK_INVALID_REQUEST`, `SDK_TIMEOUT`, `SDK_STREAM_ERROR`, `SDK_AUTH_FAILED`,
`SDK_AUTH_EXPIRED`, `SDK_PERMISSION_DENIED`, `SDK_NOT_FOUND`, `SDK_CONFLICT`,
`SDK_RATE_LIMITED`, `SDK_SERVICE_UNAVAILABLE`, `SDK_INTERNAL_ERROR`,
`SDK_NETWORK_ERROR`, `SDK_CONNECTION_FAILED`. `SDKResult` carries `isSuccess`,
`isError`, and `unwrap` helpers.

### 10.4 Python SDK (`aja-motion-pipeline`)

`libs/aja/motion-pipeline-sdk-python` is a Python distribution (`pyproject.toml`
name `aja-motion-pipeline`, import package `aja_motion_pipeline`). Its modules
provide sync and async clients plus specialized helpers:

- `client.py` / `async_client.py` — synchronous and asynchronous clients.
- `types.py` / `errors.py` — typed request/response models and error classes.
- `batch.py` — batch submission helpers.
- `data_science.py` / `jupyter.py` — data science and Jupyter notebook
  integration.
- `training.py` — ML training workflow helpers.

The package marks `py.typed` for type checker support.

---

## 11. Event Model

Aja does **not** publish to a named domain event bus. There is no `aja.*` event
topic. The domain's eventing is in-process and delivery-channel-specific, split
across three channels: an internal EventEmitter for job lifecycle, outbound HTTP
webhook deliveries to caller-specified endpoints, and Server-Sent Events
streamed to the API caller.

### 11.1 Job-Manager Events (In-Process EventEmitter)

`apps/aja/svc-motion-pipeline/src/jobs/job-manager.ts` defines a `JobEvent`
union and an `on(event, handler)` subscription interface. The service's `app.ts`
subscribes to these events to drive webhook deliveries and SSE broadcasts.

The 10 in-process events are: `'job:created'`, `'job:queued'`, `'job:started'`,
`'job:progress'`, `'job:stage:started'`, `'job:stage:completed'`,
`'job:stage:failed'`, `'job:completed'`, `'job:failed'`, `'job:cancelled'`.

Each handler receives the `JobEnvelope` and optional `data`. Stage events carry
`{ stage }` (started/completed) or `{ stage, error }` (failed).

### 11.2 Webhook Events (Outbound HTTP)

`WebhookEvent` is the set of events deliverable to caller-registered webhook
URLs — a subset of the in-process events, throttled and reformatted for HTTP
delivery. There are 9 deliverable webhook events:

| Event             | Data shape              | Notes                                          |
| ----------------- | ----------------------- | ---------------------------------------------- |
| `job.queued`      | `JobQueuedEventData`    | `queuePosition`, optional `estimatedStartTime` |
| `job.started`     | `JobStartedEventData`   | `startedAt`, `estimatedDurationMs`             |
| `job.progress`    | `JobProgressEventData`  | throttled — delivered only at 25 / 50 / 75 %   |
| `job.completed`   | `JobCompletedEventData` | `result`, `stats`                              |
| `job.failed`      | `JobFailedEventData`    | `error`, `retryable`                           |
| `job.cancelled`   | `JobCancelledEventData` | optional `reason`, `cancelledBy`               |
| `stage.started`   | `StageEventData`        | `stage`, `execution`                           |
| `stage.completed` | `StageEventData`        | `stage`, `execution`                           |
| `stage.failed`    | `StageEventData`        | `stage`, `execution`                           |

Webhook progress events are throttled to the 25 %, 50 %, and 75 % marks only —
not on every progress update — to avoid flooding the receiving endpoint.

### 11.3 Stream Events (Server-Sent Events)

The SSE stream (`/v1/pipeline/jobs/:jobId/stream`) delivers the SDK's
`StreamEventType` values (§10.3) in real time as the job progresses. The stream
includes `connected` and `heartbeat` events in addition to the `job:*` and
`stage:*` lifecycle events.

### 11.4 Cross-Domain Integration Events

`@aja/motion-integration` exports `IntegrationEventType`, `IntegrationEvent`,
and `IntegrationEventHandler` for adapter-level event subscription within the
integration layer. These are integration-adapter events scoped to the adapter
lifecycle — not a shared domain event bus.

---

## 11A. V2 Cross-Domain Contracts (Aja → Bellona)

The sister-monorepo V2 fighting-game project consumes Aja motion data through
two reciprocal contract services that live under `V2/services/`. Both are thin
contract layers that depend on real Aja libraries (`@aja/consent-management`)
and Bellona libraries (`@bellona/mocap`, `@bellona/unreal`) rather than
re-implementing them. They exist so that the Aja → Bellona → Unreal handoff is
deterministic, auditable, and gated on performer rights before any capture data
is processed.

### 11A.1 Frame-Snap Live Link Export (`AJA-BELLONA-V2-FRAME-SNAP-001`)

`@v2/aja-bellona-livelink-export`
(`apps/v2/aja-bellona-livelink-export/src/livelink-export.ts`) builds the export
plan that carries an Aja-retargeted skeleton clip into Unreal as a frame-aligned
animation asset. The contract route is
`@aja → @bellona/mocap → @bellona/unreal`: Aja produces the markerless,
neural-retargeted motion; `@bellona/mocap` resamples and frame-snaps it; and
`@bellona/unreal` receives the editor-only Live Link handoff that imports and
cooks the `AnimSequence`.

The pipeline is built around determinism. Markerless capture and neural
retargeting produce continuous-time motion, but a rollback-netcode fighting game
cannot tolerate sub-frame jitter, so `@bellona/mocap`
(`createBellonaFrameSnapLiveLinkExport`) resamples every accepted clip to a
fixed 60 Hz grid and assigns integer frame numbers. Notify segments —
hit-active, armor, cancel-window, invulnerability, root-motion, and foot-contact
windows — are normalized to **integer 60 Hz frame boundaries**
(`integer-60hz-frame-boundaries`); any segment that resolves off a frame
boundary beyond the 0.5 ms tolerance is rejected rather than silently rounded.
The export plan therefore guarantees that two clients replaying the same input
sequence land on identical frame numbers for every gameplay-relevant event.

The build plan (`buildV2AjaBellonaLiveLinkExportPlan`) drives four stages:

1. `aja_retargeted_motion_acceptance` — accepts the Aja-retargeted motion (URI +
   `sha256:` content hash) for the target V2 skeleton.
2. `bellona_mocap_frame_snap_export` — produces the
   `BellonaFrameSnapLiveLinkManifest` at `targetFrameRate: 60`.
3. `bellona_unreal_live_link_handoff` — hands the subject off on the Bellona
   mocap Live Link port for editor-time import.
4. `ue_notify_segment_alignment_gate` — verifies every notify segment is
   authored in the Unreal animation montage and aligned to integer frame
   boundaries.

The handoff is **editor-only**: the plan sets `editorOnlyHandoff: true` and
`runtimeLiveLinkPluginRequired: false`, and the cooked output uses the
`cooked-animation-only` rollback policy. **V2 runtime Live Link plugins stay
disabled** in shipped builds — the Live Link path is used at author/cook time
only, and gameplay runs against the cooked, frame-snapped `AnimSequence`. This
mirrors the Bellona-side contract (`BELLONA-MOCAP-V2-FRAME-SNAP-001`, Bellona
spec §17.2) and the engine configuration that keeps the runtime Live Link plugin
off.

### 11A.2 Performer-Consent NIL Ledger (`@v2/aja-consent-nil-ledger`)

`@v2/aja-consent-nil-ledger`
(`apps/v2/aja-consent-nil-ledger/src/consent-nil-ledger.ts`) records
per-performer capture consent and ties it to the Themis name/image/likeness
(NIL) ledger so that no captured likeness is processed without an active,
revocable rights grant. It depends on the real `@aja/consent-management` library
(`ConsentManager`, `WithdrawalManager`) and references the forthcoming
`@themis/likeness` ledger by event contract only — it does **not** take a build
dependency on it.

`grantPerformerConsent` records consent through `@aja/consent-management`,
stamps an immutable `rightsManifestSha256`, captures `consentChainRefs[]`
linking the Aja consent records to the Themis likeness license, and installs a
rights gate (`requiresActiveAjaConsent`, `requiresThemisNilLedgerStamp`,
`blocksCaptureIngest`, `blocksBellonaCook`). A grant is rejected if its rights
manifest is not a stable `sha256:` digest or if a required capture purpose
(studio-mocap, face-capture, voice-capture, ai-derivative-retarget) is not
consented.

`revokePerformerConsent` runs a `WithdrawalManager` withdrawal and emits a
cook-blocking revocation cascade. The service emits a
`v2.mocap.performer_consent.revoked` event, links it to the upstream
`themis.license.revoked` event on the same likeness license, and carries the
revoked `consentChainRefs[]` so downstream consumers can trace exactly which
captures lose their rights. The cascade sets `blockFutureAjaProcessing` and
`blockBellonaCook`: once consent is withdrawn, the revocation must propagate
within the 15-minute cascade SLA, and any in-flight or future **Bellona cook**
that depends on that performer's likeness is blocked. The
`capture-rights-gate-only` rollback policy keeps this gate off the deterministic
gameplay path — it gates ingest and cook, not rollback simulation.

---

## 12. Configuration

The following environment variables and configuration objects control each
application's runtime behavior.

### 12.1 Motion Pipeline Service

`apps/aja/svc-motion-pipeline/src/server.ts` reads the following environment
variables:

| Variable                 | Purpose                                | Default            |
| ------------------------ | -------------------------------------- | ------------------ |
| `PORT`                   | Service listen port                    | `8090`             |
| `HOST`                   | Service bind host                      | `0.0.0.0`          |
| `MAX_CONCURRENT_JOBS`    | Max concurrent jobs in the job manager | `10`               |
| `DEFAULT_JOB_TIMEOUT_MS` | Default per-job timeout (ms)           | `1800000` (30 min) |
| `SERVICE_NAME`           | Logical service name                   | `motion-pipeline`  |

The service declares `@oshun/cache`, `@oshun/database`, `@oshun/event-bus`, and
`@oshun/queue` as dependencies in `package.json`. In `app.ts` the storage
provider is pluggable via `MotionPipelineServiceOptions.storageProvider`; when
none is supplied a built-in mock provider is used.

### 12.2 Motion AI Service

`apps/aja/svc-motion-ai/src/server.ts` reads `PORT` (default `3040`).

### 12.3 CLI

`apps/aja/cli/src/index.ts` reads `AJA_API_URL` (default
`http://localhost:3000`) and `AJA_API_KEY`, and loads a `.env` file via
`dotenv`.

### 12.4 SDK

`@aja/motion-pipeline-sdk` takes all configuration through the `SDKConfig`
object passed to `MotionPipelineClient`; it does not itself read environment
variables. `DEFAULT_SDK_CONFIG` supplies the default timeout and retry policy.

---

## 13. CLI

`apps/aja/cli` publishes the binaries `aja` and `aja-motion` (`bin` in
`package.json`), built with Commander. Source: `apps/aja/cli/src/`. The CLI
depends on `@aja/motion-pipeline-sdk`, `@aja/motion-formats`,
`@aja/motion-quality`, and `@aja/motion-validation`.

Global options (available on all subcommands): `-v, --version`, `-q, --quiet`,
`--no-color`, `--json`, `--api-url <url>`, `--api-key <key>`.

Seven subcommands are registered (`commands/index.ts`), each implemented in a
dedicated source file:

| Command       | Source       | Purpose                                      |
| ------------- | ------------ | -------------------------------------------- |
| `aja process` | `process.ts` | Process a video / mocap file                 |
| `aja convert` | `convert.ts` | Convert animation files between formats      |
| `aja inspect` | `inspect.ts` | Inspect a motion file's metadata/quality     |
| `aja debug`   | `debug.ts`   | Run a pipeline stage with diagnostics        |
| `aja config`  | `config.ts`  | Manage named pipeline configuration profiles |
| `aja jobs`    | `jobs.ts`    | List/monitor/manage submitted jobs           |
| `aja health`  | `health.ts`  | Check pipeline service health                |

CLI utilities under `cli/src/utils/` provide shared building blocks: `client.ts`
builds the service client, `output.ts` handles formatted output (including
`--json` mode), and `progress.ts` renders progress indicators.

---

## 14. Validation Rules and Invariants

These rules define the correctness constraints that the Aja domain enforces at
runtime. A change that violates any of these invariants is incorrect.

- **Pipeline configuration validation** — created configs are validated with
  `validatePipelineConfiguration` before being saved; invalid configs return
  HTTP `400 INVALID_CONFIG` with an `errors` array. YAML configs additionally
  fail with `YAML_PARSE_ERROR` on a malformed body.
- **YAML schema** — `parsePipelineDefinition` enforces the `PipelineDefinition`
  shape (`apiVersion`, `kind: 'PipelineDefinition'`, `metadata`, `spec`). The
  `spec` quality preset must be one of `draft`, `standard`, `high`, `ultra`; job
  priority (when present) must be one of the five `JobPriority` values.
- **Job lifecycle invariants** — a job's outputs can be listed only when its
  status is `completed` (`JOB_NOT_COMPLETE` otherwise); an SSE stream can be
  opened only when status is `pending`, `queued`, or `running` (`JOB_NOT_ACTIVE`
  otherwise).
- **V2-deferred stages** — if a pipeline config enables `pose-estimation`,
  `skeleton-fitting`, `domain-analysis`, or `retargeting`, the processor marks
  the stage `'skipped'` (it does not fail the job) and logs the `V1-P2-0331`
  deferral.
- **Embodied-instruction request validation** — the demonstration-plan,
  coaching-overlay, and session-handoff endpoints reject malformed bodies with
  HTTP `400` and Zod-flattened `issues`.
- **Webhook progress throttling** — webhook `job.progress` deliveries fire only
  at progress percentages 25, 50, and 75.
- **SDK retry policy** — the SDK retries only retryable errors on the configured
  retryable status codes, with exponential backoff plus jitter.
- **Format scale conventions** — C3D and TRC import default to
  `scaleFactor: 0.001` (millimetres to metres); USD export defaults to
  `metersPerUnit: 0.01` (centimetres).
- **Skeleton parent invariant** — in `SkeletonJoint`, `parentIndex` of `-1`
  denotes the root joint; `STANDARD_JOINT_NAMES` and `SKELETON_PARENT_INDICES`
  arrays must be index-aligned per skeleton type — index `i` in the names array
  must correspond to index `i` in the parent-indices array.

---

## 15. Technology Stack

The table below covers the key technology choices for each layer of the Aja
domain.

| Layer                 | Technology                                                                                                                                                    |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Language              | TypeScript (Node.js); Python for the `aja-motion-pipeline` SDK                                                                                                |
| Pipeline service      | Fastify, via `@lilith/fastify-core` (port `8090`)                                                                                                             |
| AI service            | Hono + `@hono/node-server` (port `3040`)                                                                                                                      |
| CLI                   | Commander, with `chalk`, `inquirer`, `ora`, `dotenv`, `glob`                                                                                                  |
| Validation            | Zod (AI service request validation; pipeline YAML/config)                                                                                                     |
| Job progress delivery | Server-Sent Events (not WebSocket)                                                                                                                            |
| YAML                  | `js-yaml` (pipeline definitions)                                                                                                                              |
| Motion format I/O     | Custom TypeScript parsers in `@aja/motion-formats`                                                                                                            |
| Build                 | `tsc` per project (`nx` for the CLI build script)                                                                                                             |
| Testing               | Vitest (TypeScript); pytest tooling for the Python SDK                                                                                                        |
| Shared infrastructure | `@oshun/cache`, `@oshun/database`, `@oshun/event-bus`, `@oshun/queue`, `@oshun/config`, `@oshun/logging`, `@oshun/errors`, `@oshun/health` (declared as deps) |

---

## 16. Acceptance Criteria

A change to the Aja domain is acceptable when all of the following conditions
hold:

1. **Type integrity** — all motion data flows through `AnimationClip` /
   `SkeletonDefinition` from `@aja/motion-formats`; new skeleton types are added
   to the relevant `StandardSkeletonType` union and to `STANDARD_JOINT_NAMES` /
   `SKELETON_PARENT_INDICES` index-aligned.
2. **Pipeline-stage discipline** — new pipeline stages are added to the
   `PipelineStage` union; ML-only stages remain in `V2_DEFERRED_STAGES` until a
   real implementation lands, and the processor skips (never fakes) deferred
   stages.
3. **API consistency** — pipeline routes stay under the `/v1/pipeline/...`
   prefix and return the documented error-body shape `{ code, message }`; AI
   routes stay under `/api/v1/...`.
4. **Validation** — request bodies that have a Zod schema (the
   embodied-instruction endpoints) or a config validator are rejected with HTTP
   `400` and structured issues on malformed input.
5. **Event coherence** — job lifecycle changes emit the corresponding
   `JobEvent`; webhook and SSE deliveries derive from those events; webhook
   `job.progress` stays throttled to 25/50/75 %.
6. **Build and test** — every changed library and app type-checks
   (`npx tsc --noEmit`) and its Vitest suite passes; the Python SDK's tooling
   passes for `motion-pipeline-sdk-python`.
7. **No fabricated capability** — documentation and code claims are limited to
   what exists under `libs/aja/*` and `apps/aja/*`.
