testing/cross_backend/differential_normalize.ts

Response normalization for the cross-backend differential.

The differential (describe_differential_conformance_tests) sends one conformance case to two backends, each over its own database with its own keeper, so every id, timestamp and token differs between the two answers even when the backends agree. normalize_differential_response maps a response onto a canonical {status, body, header_names} that keeps what a client can rely on and drops what is fresh per run:

  • Positional placeholders. Each volatile value is replaced by its kind and the order it was first seen in that response: the first UUID becomes <uuid:1>, the next distinct one <uuid:2>, and a repeat of the first is <uuid:1> again. So identity relations within one response survive (the account_id of a listed row equals the caller's), while the values themselves may differ across backends. The kinds, each matched anywhere inside a string, are VOLATILE_VALUE_PATTERNS: API tokens, API token ids (tok_…), lowercase UUIDs, second-precision UTC timestamps (ISO8601_SECONDS, the canonical wire shape) and bare 64-character lowercase hex digests (random session ids). Only the canonical spelling of each is placeholdered — an uppercase UUID or a millisecond timestamp stays raw and shows up as a difference, since a format divergence is a wire divergence.
  • Content addresses are compared verbatim. A blake3:-prefixed hash names bytes, and equal bytes hash equal on both spines, so it is left as is: a differing fact hash is a real divergence. A fact whose bytes embed a volatile value differs by design, which is a ledger row's job.
  • Object key order doesn't matter. Keys are sorted, and the walk that numbers placeholders visits them in that order, so two bodies whose keys differ only in order normalize the same.
  • Array order does. Arrays keep their order — a list's order is part of its contract.
  • Header names, not values. header_names is the sorted set of response header names minus VOLATILE_HEADER_NAMES: framing and per-request headers (date, content-length, transfer-encoding, connection, keep-alive) that each HTTP server sets its own way. Values are not compared at all — set-cookie carries a fresh credential on every login, and the header values that are contracts are pinned per backend by the conformance table's expect.headers and its no-fingerprint floor. A header one backend sends and the other doesn't is still a difference.

diff_differential_responses lists every place two normalized responses differ, one entry per leaf path, which is what a known-divergence ledger entry names.

Placeholders are positional, not tied to fixture identity: they keep which values repeat within a response, not which fixture entity each one is, so two ids that appear once each and trade places normalize equal.

Placeholder numbering is per response and per kind. Object keys are normalized too, but sorted by their raw text, so a record keyed by ids would order its entries by values that differ across backends — no conformance response is shaped that way today.

view source

Declarations
#

10 declarations

diff_differential_responses
#

testing/cross_backend/differential_normalize.ts view source

(a: DifferentialResponse, b: DifferentialResponse): DifferentialDiff[] import {diff_differential_responses} from '@fuzdev/fuz_app/testing/cross_backend/differential_normalize.js';

Every place two normalized responses differ, in a stable order: status, then header names, then the body depth-first with keys sorted. Empty when the responses are equal.

a

one backend's normalized response

b

the other's

returns

DifferentialDiff[]

the differences

differential_path_covers
#

testing/cross_backend/differential_normalize.ts view source

(covered: string, path: string): boolean import {differential_path_covers} from '@fuzdev/fuz_app/testing/cross_backend/differential_normalize.js';

Whether path is covered or lies under it — body.error.data covers body.error.data.reason and body.error.data[0], not body.error.database.

covered

a declared path

type string

path

type string

returns

boolean

true when path is covered or under it

DifferentialDiff
#

testing/cross_backend/differential_normalize.ts view source

DifferentialDiff import type {DifferentialDiff} from '@fuzdev/fuz_app/testing/cross_backend/differential_normalize.js';

One place two normalized responses differ. path is dotted from the response root (status, headers.x-example, body.error.data.reason, body.result.accounts[0]); a / b are the values on each side, undefined where a side has nothing there. A header one side sent and the other didn't is headers.<name>, with true on the side that sent it. An object one side lacks is compared key by key, so each of its keys is its own difference (body.error.data.reason, not the whole body.error.data); an empty object against a missing one is a difference at the object's path.

path

type string

readonly

a

type unknown

readonly

b

type unknown

readonly

DifferentialRawResponse
#

DifferentialResponse
#

testing/cross_backend/differential_normalize.ts view source

DifferentialResponse import type {DifferentialResponse} from '@fuzdev/fuz_app/testing/cross_backend/differential_normalize.js';

The canonical form of one response: what the differential compares across backends.

status

HTTP status.

type number

readonly

body

The JSON body with volatile values placeholdered and keys sorted.

type unknown

readonly

header_names

Sorted response header names, minus VOLATILE_HEADER_NAMES.

type readonly string[]

readonly

format_differential_diffs
#

testing/cross_backend/differential_normalize.ts view source

(diffs: readonly DifferentialDiff[], labels: { readonly a: string; readonly b: string; }): string import {format_differential_diffs} from '@fuzdev/fuz_app/testing/cross_backend/differential_normalize.js';

Render diffs one per line for an assertion message.

diffs

the differences

type readonly DifferentialDiff[]

labels

names for the two sides

type { readonly a: string; readonly b: string; }

returns

string

one indented path: a=… b=… line per diff

normalize_differential_response
#

VOLATILE_HEADER_NAMES
#

testing/cross_backend/differential_normalize.ts view source

readonly string[] import {VOLATILE_HEADER_NAMES} from '@fuzdev/fuz_app/testing/cross_backend/differential_normalize.js';

Response headers left out of header_names: framing and per-request headers each HTTP server sets its own way, which say nothing about the application. Lowercased.

VOLATILE_VALUE_PATTERNS
#

testing/cross_backend/differential_normalize.ts view source

readonly VolatileValuePattern[]

type readonly VolatileValuePattern[]

import {VOLATILE_VALUE_PATTERNS} from '@fuzdev/fuz_app/testing/cross_backend/differential_normalize.js';

The volatile values normalize_differential_response placeholders, applied in order to every string (object keys included). Tokens come first, so the random tail of a token is never read as a hash. A blake3:<64 hex> content address matches none of them and is compared verbatim.

VolatileValuePattern
#

testing/cross_backend/differential_normalize.ts view source

VolatileValuePattern import type {VolatileValuePattern} from '@fuzdev/fuz_app/testing/cross_backend/differential_normalize.js';

One kind of volatile value: its placeholder name and what matches it.

kind

The placeholder's kind, as in <uuid:1>.

type string

readonly

pattern

Matches every occurrence inside a string; must carry the g flag.

type RegExp

readonly

Depends on
#

Imported by
#