PipeWhy Local Fixture Adapter Contract C4 version 1.0 October 30, 2026 PW-06.01 Stable fixture identity The reference source namespace is pipewhy.local.fixture.v1. It is a deterministic test source, not a filesystem watcher or an enterprise connector. Identity comes from explicit IDs in the source manifest, never Python hash, paths, enumeration order, process addresses or timestamps. Moving the fixture directory preserves identity. The source namespace is retained with every local ID. Artifacts are 0=fixture root, 1=A, 2=B, 3=C, 4=D. The root has parent NULL_ID; datasets A/B/C/D have parent 0. Names use append-stable label IDs in this exact order: fixture, A, B, C, D. Artifact kind 0 means container, 1 means dataset. Containment and data lineage are distinct relations. Version IDs are baseline-v1, mutation-v1 and missing-v1, with local source-version IDs 0, 1 and 2 respectively. Version strings and integer IDs are explicit mappings, not hashes or fingerprints. A requested historical version is pinned for the whole operation; there is no implicit latest fallback. Unknown versions fail visibly. Baselines and deltas are immutable fixture data; reads never mutate stored input. Each dataset has two records identified by field 0=row_id, values 0 and 1. Field 1=amount is integer cents for A/B/D and Decimal dollars for C. A field's source schema records that distinction; normalization does not coerce it away. The row identifier is part of the record payload and preserves row association. Baseline A/B/D amounts are 100 and 250 cents; C amounts are Decimal 1.00 and 2.50. The mutation changes exactly B's row_id=1 amount from 250 to 251 cents. C and D remain unchanged, creating deliberate divergence along the declared lineage. The missing version retains baseline identity/schema but makes C unreadable with an explicit injected-read-failure reason. Unreadability is not an empty dataset. Lineage edge IDs are 0=A->B COPY, 1=B->C TRANSFORM, 2=B->D COPY. Evidence IDs 0/1/2 identify explicit source declarations. Transform ID 0=cents_to_dollars_v1 declares exact amount_dollars * 100 == amount_cents per matching row_id, with unchanged row count and keys. COPY edges have NULL_ID transform. These declarations are evidence of intended provenance; they do not prove current content equality. PW-06.02 Protocol and fixture examples Fixture input files are UTF-8 JSON supplied as a directory to LocalFixtureAdapter. The fixture namespace and version/schema IDs are validated; unknown schemas, duplicate IDs, malformed inputs and unsupported typed values fail visibly. No external services or optional dependencies are required. Runtime code does not depend on the repository's test directory or bundle test fixtures into the wheel. The adapter captures its source data at construction and returns independent owned arrays. Altering returned buffers cannot mutate the captured source or later reads. capabilities() reports versioned schema, immutable-version consistency, pinned historical reads, explicit lineage, supported scalar types and injected-failure support. This describes only this deterministic source, not enterprise guarantees. discover(scope=0, batch_size=1024) returns stable increasing artifact IDs as C1 artifact-table batches, exported label buffers, scope and COMPLETE discovery coverage. Root scope includes all five artifacts; dataset scope includes only that dataset. Unknown scope and invalid batch size raise ValueError. No partial results are reported as complete. For root scope, batch_size=2 produces IDs [0,1],[2,3],[4]. read(artifact_id, source_version, profile=STRICT, batch_size=1024) returns source identity, pinned version, full profile identity, status/reason, expected/returned record counts and owned value batches. Each batch has ordered record IDs plus a C1 values table; all fields for a record remain in the same batch. Values retain field IDs and typed payload offsets/lengths. Missing and null use separate C2 tags and zero validity bits. Value IDs are explicit fixture IDs, unique across artifacts within the source version; they are never row positions reassigned by batching. The profile identity is carried beside every read result, not inferred from words. Read statuses are COMPLETE and UNAVAILABLE. UNAVAILABLE returns no value batches with a specific reason: UNKNOWN_VERSION, ARTIFACT_NOT_FOUND, NOT_DATASET or INJECTED_READ_FAILURE. Unknown/inapplicable expected counts are None, never a fabricated successful zero. Injected failure for C reports expected=2, returned=0. An available empty dataset would be COMPLETE with expected=returned=0. Malformed input, unsupported normalization or invalid arguments raise their explicit errors; they are not recast as source unavailability. The scan coordinator must preserve both statuses and exceptions in scan coverage evidence. lineage(scope=0) returns C1 edge columns and explicit evidence/transform identities. Scope is the induced artifact subgraph: both endpoints must be in scope. Coverage is COMPLETE_WITHIN_SCOPE, not a claim about external relationships. Root scope returns all three edges; dataset-only scope has no internal edges. Metadata marks each relationship as a declared intent; content verification belongs to later tasks. Fixture files use typed scalar objects: {"type":"int","value":"250"} and {"type":"decimal","value":"2.50"}; integer strings avoid JSON rounding. Baseline carries all artifact/field/record identities. mutation.json names exactly one replacement by artifact/record/field ID. transform.json declares the invariant; lineage.json declares edges and evidence; missing.json declares unreadable IDs. Expected results are authored independently, not copied from normalizer output. Implementation notes (PW-06.08-.13) Import LocalFixtureAdapter from pipewhy.adapters.local and supply the directory. Construction captures all five JSON inputs and validates the baseline, mutation, failure evidence, lineage references and transform declaration before serving data. Computation uses flat C1 arrays; transient JSON objects are not the retained value or graph model. Captured values are normalized separately for both supported C2 profiles. All C4 result IDs carry the source namespace in their enclosing result. Discovery.batches is a tuple of owned artifact column mappings. Discovery.labels is an independent read-only UTF-8/offset/length snapshot loadable by LabelDictionary. ReadResult.batches contains ValueBatch(record_ids, columns); C1 value rows repeat the record's two fields, while record_ids contains one entry per complete record. Value IDs and per-record ordering survive batch-size changes; payload offsets and validity storage are repacked for each output batch. No borrowed source buffers are exposed. The fixture is small and captured in memory; this is not a bounded streaming implementation for arbitrary enterprise sources. LineageResult.evidence and .transforms are immutable UTF-8 JSON bytes preserving the explicit declarations, loadable with json.loads. Edges remain flat integer columns. A copy/transform declaration establishes intent, not verified equality. Available empty datasets return COMPLETE with expected=returned=0 and no batches. File, JSON, schema, type and normalization errors propagate, retaining failure context for the coordinator rather than being swallowed as successful coverage.