B. Conformance
An implementation conforms to a version of the profile when it passes every vector for that version that applies to it. A vector is a scripted situation and the exact result the implementation must produce. The Apsio SDKs and the Apsio ingestor run the same vectors in CI; any other OpenTelemetry SDK or backend can run them too.
Vectors live in vectors/v0.1/<area>/<name>.json and validate against
schemas/v0.1/vector.schema.json.
They are published with each version as
vectors-0.1.tar.gz, with the schemas. Vectors, schemas and
runners are Apache-2.0.
B.1 Kinds
Section titled “B.1 Kinds”| Kind | Who runs it | Input | Expected |
|---|---|---|---|
function |
SDKs and backends | Values for a pure function of the profile (the sampling decision, clock correction) | The function’s output |
ingest |
Backends | HTTP requests, each with the time it is received | Each response’s status, the normalized records stored and, when relevant, the session states |
sdk |
SDKs | Steps: launches, API calls, clock changes, network conditions | The requests the SDK sends |
Every vector also has given, when and then: the same situation in plain sentences,
for people.
B.2 Functions
Section titled “B.2 Functions”| Function | Input | Output |
|---|---|---|
sampling.keep |
session_id, rate |
true or false (9.1) |
clock.corrected |
original, sent_at (or null), received_at |
The corrected time (5.2) |
crash.images_kept |
images (each a name, the bytes of its JSON entry as the SDK encodes it, and frame, the index of the first stack frame in it, or null) and limit |
kept, the names in the order sent, and truncated (1.6) |
crash.image_in_app |
platform (apple or android), path (the image’s resolved path) and, on Apple platforms, bundle (the crashed process’s main bundle path, an extension’s own), on Android package (the package name) |
true or false (6.5) |
screen.views |
shows (each a session, the screen name and the screen’s instance) |
The views sent, in order: each screen and its previous (absent for a session’s first) (8.6) |
replay.text_mask |
text |
The masked text (10.3) |
replay.apply_delta |
nodes (a frame’s nodes) and delta (its update, remove and add) |
The nodes after the delta, by ascending id (10.1) |
replay.mask |
options, policy (optional), unresolved (optional) and tree |
Per node id, in tree order: content (shown, masked or hidden) and touches (true or false) (10.4) |
A replay.mask tree is neutral: each SDK maps its views onto it. A node has an id, a
kind (container, text, input, image, webview, map, video or keyboard;
container when absent), classes (the class names it belongs to), setting (mask,
unmask, hide or null, the instance API), identifier (the accessibility identifier
or tag), secure and children. options has text (all or inputs), touches
(show or hide) and mask_classes, unmask_classes, hide_classes; policy has the
policy’s text, mask_classes and hide_classes; unresolved (optional) lists the class
names that do not resolve in the app, which start strict mode when a hide or mask list
names one (10.4).
Times are decimal strings of Unix nanoseconds.
B.3 The normalized record
Section titled “B.3 The normalized record”A backend stores data its own way. To compare backends, an ingest runner converts each
stored record to this neutral form:
| Field | Value |
|---|---|
signal |
log, span or metric |
app_key |
The app key the record arrived with |
batch_id |
The Apsio-Batch-Id of the request, or "" |
session_id |
Per 2.4, or "" |
event_name |
Logs: the event name, or "" |
name |
Spans and metrics: the span or metric name |
severity_number |
Logs |
body |
Logs: the body as a JSON value |
time |
{ "original", "observed", "corrected" }: logs use timeUnixNano; spans and metric points use their end time |
start_time |
Spans and metric points: { "original", "corrected" } |
resource |
The Resource attributes as an object, with the overrides of 2.5 applied |
attributes |
The record attributes as sent, as an object |
sample_weight |
Per 9.3 |
metric |
Metric points: type (histogram, sum, gauge), temporality (delta, cumulative), and the point’s count, sum, min, max, bucket_counts, explicit_bounds or value, each only when present |
Attribute values convert from OTLP AnyValue to JSON: strings, booleans and doubles as
themselves, integers as numbers, arrays as arrays, key-value lists as objects, and bytes
as base64 strings. Times are decimal strings.
An ingest vector can also expect session states: per session, the outcome the
backend holds after all requests (3.4).
B.4 Matching
Section titled “B.4 Matching”A runner compares the actual result with the expected one by these rules:
- Objects match as a subset. Every key in the expected object must be in the actual one with a matching value; other actual keys are ignored.
- Arrays match exactly, element by element, with the same length.
- Expected records in an
ingestvector match as a set. The stored records must be exactly as many as the expected ones, and each expected record must match a different stored record, in any order. - Expected records in an
sdkrequest match as “contains”. Each expected record must match a different record of the request; the request may carry others (session events, breadcrumbs). No record of the request may match an entry ofabsent_records. - Expected requests in an
sdkvector match in order, as a subsequence of the requests the SDK sent: each expected request matches the next sent request that matches it. Requests to/v1/policyare ignored unless the vector expects one. - Captures are tried in the order the expected values are written. When a set or “contains” match can pair records in more than one way, the runner tries the pairings until one satisfies every capture and reference.
- Special values in the expected side:
| Value | Matches |
|---|---|
{ "$absent": true } |
The key is missing |
{ "$type": "string" } |
Any value of that JSON type (string, number, boolean, object, array) |
{ "$capture": "name" } |
Any value, remembered as name |
{ "$ref": "name" } |
The value remembered as name |
{ "$not": "name" } |
Any value other than the one remembered as name |
{ "$gt": "name" } |
A time (decimal string) later than the one remembered as name |
B.5 SDK steps
Section titled “B.5 SDK steps”An sdk vector drives the SDK through a test harness that controls the clock, storage,
process lifecycle and network. Draft 0.1 defines these steps; each SDK maps them to its
platform in its own test driver.
| Step | Does |
|---|---|
{ "do": "install" } |
A fresh install: storage is empty |
{ "do": "launch" } |
A cold start of the process; the SDK is started with the vector’s start options and fetches the policy before the step ends |
{ "do": "terminate", "how": "normal" | "crash" | "kill" } |
Ends the process: a normal exit, a crash in the app’s code, or a kill without warning |
{ "do": "background" } / { "do": "foreground" } |
Moves the app between states |
{ "do": "advance", "seconds": n } |
Moves the device clock forward |
{ "do": "network", "mode": "online" | "offline" | "timeout" | "status", "status": n } |
How the next requests are answered: 200, no connection, a timeout, or a given status |
{ "do": "call", "api": "<name>", "args": { … } } |
A call of the public API (13), or of recordHybridCrash for a cross-platform layer (14.1); args.attributes, when present, is an object of attribute values: strings, numbers, booleans and arrays of strings |
{ "do": "flush" } |
Asks the SDK to send what is queued, and waits until it has tried |
On a platform with no termination callback (Android), the driver models
{ "do": "terminate", "how": "normal" } as a move to the background followed by the end
of the process, with the platform’s record of a normal exit for the next launch to read
(on Android, an ApplicationExitInfo such as REASON_EXIT_SELF or
REASON_USER_REQUESTED).
The harness answers GET /v1/policy with the vector’s policy document, or with 404
when the vector has none (the SDK then uses the profile’s defaults).
Each expected request has path, headers (matched without regard to case), records
and absent_records. A request’s records are flattened from the OTLP body, whatever its
grouping into Resources and scopes, to: event_name, severity_number, body,
session_id (per 2.4), name
(spans and metrics), attributes and resource.
B.6 Runners
Section titled “B.6 Runners”The repository ships a validator for the vector files (pnpm validate). Runners for
Rust (the Apsio ingestor), Swift and Kotlin load the vectors of a pinned version and run
them in each implementation’s CI.