PipeWhy Lineage/Incident Contract L1 version 1.0
October 30, 2026 | PW-09.01-.07 | Refines C1, C6 and C7

Scope and D6 implementation choice
The initial enforceable transform is C4's declared cents_to_dollars_v1: stable row
keys/count unchanged and target_amount * 100 == source_amount. This is a bounded
fixture-supported implementation choice under continued-development authorization,
not a claim that arbitrary enterprise transformations or causal tracing are solved.
Only explicit declared/observed edges are accepted; equal fingerprints create no
edges. Unknown provenance, timing, unsupported rules and missing detail stay visible.
The implementation remains for accidental drift detection under C3 limitations.

PW-09.01 Relation semantics and flat representation
Containment is the acyclic parent forest from C1/C6. It is excluded from lineage
traversal. C1 edge relation codes remain COPY=0, TRANSFORM=1, DERIVED=2; no codes
are reassigned. COPY requires a sentinel transform ID. TRANSFORM requires a declared
transform ID. DERIVED is a declared dependence, optionally naming a transform;
it supports reachability, with no generic equality/transform inference rule.
Lineage cycles, self-edges and parallel edges are valid. Dangling endpoints, duplicate
edge IDs, unsupported relation codes and broken evidence/transform references fail.
Namespace/scope accompany stable artifact IDs. Names and physical row indexes never
establish cross-snapshot identity. Graph inputs borrow stable C1 integer buffers.

Build forward/reverse CSR indexes: a sorted stable artifact ID vector; uint64
offsets of length vertex_count+1; uint64 original edge-row indexes of length edge_count.
An edge row is a position, not its edge_id. Retain original edges and evidence links.
No retained nested adjacency lists or per-node Python graph replaces flat storage.
Traversal terminates on cycles and emits each selected stable ID at most once, in
sorted ID order. Default reachability excludes the selected start even if a cycle
reaches it again; an explicit include_self option includes it. An unknown start ID
is an error, not an alleged known empty graph. Relation filters are explicit and
never introduce containment. Scope coverage accompanies otherwise valid results.

PW-09.02 Evidence and completeness
Each edge references a non-sentinel evidence ID. Evidence records carry provenance
kind (DECLARED=0, OBSERVED=1, UNKNOWN=2), producer/version identity, source version,
capture time/window and clock domain when known, completeness and diagnostic detail.
Text lives in immutable UTF-8/offset/length dictionaries or compact control metadata;
bulk computational/reference fields remain flat integer arrays with stable IDs.
The implementation publishes exact evidence columns before accepting a consumer.
Unknown metadata uses explicit validity/sentinel fields rather than guessed values.
Timing-known windows require start<=finish and an identified clock domain. A timestamp
of zero is a possible epoch value when timing is valid; it is not a universal null.

Evidence describes what was supplied, with independent confidence/completeness:
DECLARED records intended dependence; OBSERVED records a producer-reported operation
or observation; UNKNOWN lacks usable provenance. A complete declaration is still a
declaration. Producer source versions identify the evidence capture; endpoint data
versions and snapshot/observation references identify the values being checked.
Never equate a declaration-document version with both endpoint data versions.
Operational claims additionally need explicit endpoint version/observation links.
Retain complete/partial/unavailable evidence coverage and failure diagnostics.

C4 lineage is declared fixture intent, captured without operation execution times
or endpoint version bindings. Preserve this limitation even though its induced
scope graph is COMPLETE_WITHIN_SCOPE. A snapshot's read window does not invent the
execution time of a copy. Missing evidence does not remove an observed edge or make
it verified. Incomplete graph scope limits impact claims beyond the captured edges.

PW-09.03 Allowed transformation and exact verification
Rule name cents_to_dollars_v1, integer factor 100, key_field_id=0, amount_field_id=1,
source unit cents, target unit dollars. Other rules are explicitly unsupported.
Source keys/amounts are C2 integers; target keys are C2 integers and amounts finite
C2 Decimals. Row keys are unique, matched by identity independently of physical
ordering, and expected/returned counts must be complete on both sides. Extra,
missing or duplicate keys cannot be silently ignored. Required fields, profile IDs,
scope/source identity and retained record/value evidence must be validated.

The invariant compares exact signed integer cents with the finite decimal's
coefficient times 10^(exponent+2). It uses bounded integer arithmetic, not float
conversion or ambient Decimal precision. Reject malformed C2 payloads. Unsupported
valid types/policies/rules return INCOMPARABLE with a reason. Null/missing/nonfinite
amounts or unavailable required historical detail prevent a proven invariant and
return UNKNOWN with their reason. Source/target count/key differences in complete
captures yield FAIL. Resource limits are explicit; huge exponents never allocate
an unbounded power of ten. Zero, sign, coefficient and exponent boundaries are tested.

Checks return PASS=0, FAIL=1, UNKNOWN=2, INCOMPARABLE=3, plus reason/evidence and
input snapshot/version/observation references. PASS means this declared invariant
holds for captured rows; FAIL means observed values violate it. Neither diagnoses
physical corruption or an execution origin. TRANSFORM fingerprint differences alone
are expected, so they are not a generic FAIL. Copy checks can compare fully captured
eligible C3 states; equality is probabilistic under C3, not proof of a copy operation.

Strict representation remains the default for hashing/comparison. This explicitly
named business invariant may relate integer cents to Decimal dollars without changing
either snapshot/profile, converting raw C6 comparison results or enabling logical
normalization globally. C4 baseline C is a valid transform of B; the mutated B amount
251 versus retained C amount 2.50 fails the numeric invariant under this fixture rule.

PW-09.04 Divergence eligibility and certainty
Use C6 compatible captured observations and direct content-change evidence. Structural
parent moves, added/removed presence and child-derived parent changes retain their own
meanings; they do not automatically become direct content divergence candidates.
Missing/incomplete reads, incompatible contexts or missing retained rows remain
UNKNOWN/INCOMPARABLE, not silent negative results. Known eligible facts may coexist
with unknown evidence elsewhere; preserve scope uncertainty independently.

A direct changed node and an incoming declared COPY/TRANSFORM check failure support
an observed divergence candidate with edge/check/observation evidence. They do not
prove that the edge executed, that the target caused the difference or that this was
the first physical corruption point. Source changes can make unchanged targets differ
from a newer source; retain which endpoint actually changed. An upstream changed node
with absent/ineligible incoming evidence remains a candidate with explicit insufficient
provenance, rather than being dropped or declared a verified root cause. Multiple
possible upstream causes, cycles and missing branches remain ambiguity.

PW-09.05 Earliest observed ranking and temporal ambiguity
Rank first observation of supported divergence, not unobserved execution/corruption
time. Input times must have declared validity and the same explicit clock domain.
Do not infer a common clock from integer units or use source-version strings as a
timestamp order. Keep capture interval [start,finish] when available; point observations
are equal-endpoint intervals. Before-snapshot observations remain separately linked.

Candidate A is definitely earlier than B only when A.finish < B.start in a comparable
clock domain. Equal times, touching/overlapping intervals and unknown/incompatible
clocks preserve ties/uncertainty. The earliest set contains candidates with no other
candidate definitely before them; unknown-time candidates remain unresolved alongside
known-time earliest candidates. No ID/label/traversal order breaks a temporal tie.
Zero candidates is distinct from insufficient evidence. Ranking never produces a
single certain origin merely because deterministic sorting has a first row.

PW-09.06 Potential impact and observed downstream changes
Potential impact is the unique forward lineage-reachable set from the selected
candidate(s), excluding starts by default. Cycles/shared descendants are deduplicated.
COPY, TRANSFORM and DERIVED indicate declared dependence, so this is potential
exposure within captured scope. A reachable node with no observation is still a
potentially affected node; it is not an observed changed node.

Separate flat labels record POTENTIAL and OBSERVED_DOWNSTREAM_CHANGE. The latter
requires eligible C6 direct content-change evidence at a reachable node, with its
own observation links. Reachability plus change is evidence consistent with propagation;
it does not prove transmission/causation or replace missing operation/version/time
provenance. Unchanged or unknown downstream nodes keep their respective evidence.
Report potential unique count separately from observed changed unique count; never
sum branch paths or label every reachable target as corrupted. Scope incompleteness
and uncertainty flags remain explicit even when some impact facts are known.

PW-09.07 Incident identity, recurrence and repeat-scan deduplication
Policy anchored-window-v1 requires an explicit positive recurrence_window_ns and
clock-domain configuration. No undocumented default business/SLA window is inferred.
Group compatibility includes source namespace/scope, stable artifact ID, reason
category, full algorithm/profile/schema/mode identities and grouping-policy/config
identity. Different incompatible contexts remain separate incidents.

Known-time episodes anchor on first observed supported divergence. Observations in
[anchor,anchor+window) can recur in that episode; exactly at the end starts a new
episode. Use exact nonoverflowing integer comparisons. Late/out-of-order inputs must
not retroactively rewrite existing stable episode identities; report or handle them
under a documented deterministic policy rather than silently shifting anchors.
Unknown/incomparable times permit exact observation deduplication but no guessed
temporal merge. A new distinct unknown-time observation stays a separate uncertain
episode unless explicit stronger provenance identifies it as the same observation.

An exact observation identity includes before/after snapshot IDs, stable artifact ID,
reason/category and supporting observation/evidence references. Rerunning the same
comparison/evidence contributes once. Recurrence counts count distinct observations,
not repeated executions of a scan/report. Reachable impact counts are independently
deduplicated and are not incident recurrence counts.

Allocate stable unique uint64 incident IDs within a versioned registry; preserve full
canonical grouping/observation identities and registry identity beside them. Do not
truncate a cryptographic digest into an alleged collision-free uint64 ID. Registry
state can be exported as flat arrays/dictionaries and persisted with C5; repeated
calls reuse identities. Query/CLI consumers preserve registry/policy identity, time
ambiguity and supporting evidence. Final exact result columns/codes are published
with the implementation and reference consumer acceptance in PW-09.23.

Qualification required by later PW-09 subtasks
An independent set/graph oracle verifies branches, cycles, disconnected nodes,
forward/reverse relation filtering and deduplication. Authored truth fixtures verify
copy, valid cents/dollars transforms, mutation, missing reads/detail, ambiguous times,
multiple candidates and duplicate observations. C4/C5/C6 integration must retain
provenance limitations. Formatting, typing, unit-only coverage, package integrity
and verified CI delivery follow Q1. This specification itself implements no
graph, invariant checker or incident registry and claims no Python coverage pass.

Implemented graph boundary (PW-09.08)
pipewhy.lineage exposes LineageContext, LineageGraph and Relation. The context binds
source namespace, declaration source version, scope, inventory coverage/failures and
an optional C5 snapshot ID. COMPLETE_WITHIN_SCOPE requires no inventory failures;
PARTIAL/UNKNOWN require explicit failure diagnostics. This is edge-inventory coverage,
not operation verification or evidence-payload completeness.
LineageGraph accepts context, artifact_ids and the exact C1 edge columns, plus
explicit evidence_ids and transform_ids reference buffers. It reuses C1 relationships
and persistent-ID validation. It preserves original artifact/edge order, exposes a
separate owned immutable sorted ID vector, and borrows read-only exports of inputs.
All source aliases must remain stable for the graph's lifetime. Reference IDs alone
do not supply or verify provenance/time payloads. Evidence checking/traversal are
separate later operations; constructing a graph claims no verified propagation.

Implemented adjacency boundary (PW-09.09)
LineageIndex(graph) builds forward/reverse Adjacency values. offsets and edge_indices
are owned immutable contiguous uint64 views; graph.sorted_ids defines vertex positions.
Forward buckets use source_id; reverse buckets use target_id. Edges within a bucket
retain original input row order, including parallel edges. Empty edge inventories
preserve isolated vertices; a zero-vertex graph has offsets=[0]. The index retains
its graph owner and requires the graph's borrowed source aliases to stay stable.

Implemented reachability boundary (PW-09.10/.11)
reachable(index, artifact_id, direction="forward", relations=(0,1,2),
include_self=False) returns Reachability(context, start_id, direction, relations,
include_self, artifact_ids). Direction is FORWARD="forward" or REVERSE="reverse".
Relation filters are canonicalized/deduplicated; an empty filter visits no edges.
The source is excluded by default even on cycles, or included as an explicit zero-hop
result when include_self=True. An unseen source raises KeyError; malformed IDs,
directions, filters and flags raise explicit errors. No source is read to fill gaps.
The traversal uses a flat visited bitmap/vertex stack and the selected CSR, avoiding
recursion and duplicate IDs. Output IDs are sorted, owned immutable uint64 storage.
The original inventory coverage/failures and declaration context are preserved;
reachability alone is not a content/operation check or a propagation verdict.

Implemented selected copy/evidence boundary (PW-09.12)
Evidence is immutable metadata for one selected evidence ID, not a replacement for
the bulk flat graph. Fields: evidence_id, kind (DECLARED=0, OBSERVED=1, UNKNOWN=2),
producer/source_version (text or explicit None), complete boolean, diagnostic tuple,
optional started_at_ns/finished_at_ns and optional clock_domain. Both window endpoints
are supplied together or neither. uint64 zero/max are valid timestamps. A window
without a clock is retained but timing_known=False; a clock without a window is invalid.
Complete evidence requires known producer/version/kind and no diagnostics; incomplete
evidence requires diagnostics. OBSERVED remains a producer-reported classification,
not an independently proven operation. This selected metadata contains no asserted
endpoint execution/version bindings and creates no causal claim.

check_copy(graph, observations, edge_id, evidence=...) returns EdgeCheck with both
graph and data contexts, stable edge/endpoints/evidence/transform IDs, both observed
IDs/timestamps, overall CheckStatus/CheckReason, separate content_status/content_reason
and the selected Evidence or None. Data contexts are C6 ArtifactObservations; the
shared C6 captured-content comparator preserves read/error/partial/ordered-offset
semantics. Namespace/scope mismatches do not claim matching observation IDs. Different
endpoint levels use C6 LEVEL_CHANGED to mean incompatible endpoint level semantics
in this edge check. Containment parent IDs do not determine copy content equality.

Overall CheckStatus remains PASS=0, FAIL=1, UNKNOWN=2, INCOMPARABLE=3. CheckReason is
a control-metadata string enum: MATCH, DIFFERENT, DATA_UNKNOWN, DATA_INCOMPARABLE,
EVIDENCE_UNAVAILABLE, EVIDENCE_INCOMPLETE, EVIDENCE_MISMATCH,
EVIDENCE_VERSION_MISMATCH, SNAPSHOT_MISMATCH, RELATION_UNSUPPORTED.
Mismatched evidence ID/declaration version, an explicit wrong graph snapshot binding
or a non-COPY relation is INCOMPARABLE. Incomparable captured data remains so even
when provenance is absent. Absent/incomplete evidence is UNKNOWN, preserving any
independently observed content difference. Missing/partial reads remain UNKNOWN.
Only complete matching selected evidence plus comparable captured content yields
PASS for SAME or FAIL for CHANGED. The producer/declaration version is compared to
the graph context; it is not incorrectly required to equal the endpoint data version.
Unknown edge selections/invalid API inputs raise explicit errors.

PASS/FAIL refer to this local captured copy condition under C3, not proof of edge
execution, source authenticity or physical corruption. Timing/clock uncertainty,
graph inventory failures and unrelated capture failures stay in the returned metadata
even when a fully captured local condition is PASS. No scope-wide completeness is
inferred. Bulk incident/query results will use flat arrays/dictionaries under PW-09.23.
This API selects both endpoints from one C6 context; heterogeneous per-endpoint policy
contexts cannot be coerced into that view to force eligibility. Producer timing in
Evidence does not establish the clock domain of separate data observation timestamps.
C4 integration content-addresses captured declaration payloads because that provider
has no server-issued declaration version or execution clock; it does not invent them.

Retained-record binding component (PW-09.13 in progress)
RetainedRecords(metadata, artifact_id, observation_id, records) pairs actual C5
SnapshotMetadata with a C2 RecordBatch. It is a selected payload interface, not an
automatic source reader. The batch profile must match its snapshot. Buffers remain
borrowed/stable during validation and subsequent use; LocalSnapshotStore.open should
validate persisted bytes before this interface is used.
check_retained_records(details, observations, artifact_id, record_limit=65536,
word_limit=1048576) checks exact snapshot/namespace/source-version/scope/fingerprint
context and artifact/observation links. Unsupported policies stay INCOMPARABLE.
Absent/uncaptured/error/partial observations cannot prove a complete retained input.
C5 value-level retention is required; newly supplied rows cannot turn a dataset/record
capture into retained value evidence. Dataset-only C6 node views may remain conservative
while a separately supplied C5 value payload is validated for a specialized rule;
this does not create finer generic drill-down nodes.

All frames and limits are checked, record count is compared before hashing, and the
supplied rows' C3 state must match the actual captured state, including original ordered
positions. No full-source object materialization occurs; reference hashing uses one
bounded temporary record frame at a time. This correspondence has C3's probabilistic
limitations and is not adversarial authentication. Wrong metadata/state is INCOMPARABLE;
missing retention/read evidence is UNKNOWN. Malformed wire/limits are explicit errors.
PASS yields borrowed read-only pool/offset/length views; other results have records=None.
RetainedCheck keeps both metadata/context, observation links and a RetainedReason:
MATCH, UNAVAILABLE, CONTEXT_MISMATCH, UNSUPPORTED_POLICY, OBSERVATION_UNAVAILABLE,
READ_ERROR, PARTIAL_READ or FINGERPRINT_MISMATCH. No live read or normalization/profile
conversion is attempted. The dataset evaluator below consumes this retained evidence.

Selected numeric invariant component (PW-09.13)
check_cents_to_dollars(source, target, word_limit=256) accepts current C2 EncodedValue
amounts. It compares signed int64/nonnegative uint64 cents against finite Decimal
dollars using exact coefficient/exponent integer arithmetic. It performs no float
conversion or Decimal arithmetic/context rounding. The default bound is 256 uint64
payload words per amount; malformed supported numeric payloads or exceeded/invalid
limits raise explicit errors. Digit/exponent bounds prevent unbounded powers of ten,
including the full uint64 exponent range. Finite signed zero compares numerically.

NumericCheck returns CheckStatus with NumericReason MATCH, DIFFERENT,
AMOUNT_UNAVAILABLE, NONFINITE or UNSUPPORTED_TYPE. Null/missing amounts are UNKNOWN;
nonfinite Decimal targets are UNKNOWN; other valid types, including float/bool cents,
are INCOMPARABLE. Supported numeric shapes/flags/coefficient widths are validated;
this is not a generic decoder for unsupported scalar types. This selected-value API
makes no snapshot, key-alignment, completeness or provenance assertion. The retained
dataset/edge evaluator below binds those contexts separately.

Keyed retained transform evaluation (PW-09.13 / PW-09.20)
check_transform(graph, observations, edge_id, rule=..., source=..., target=...,
evidence=...) checks only explicit TRANSFORM edges. TransformRule binds the edge's
transform ID and declaration source version. The supported definition is exactly
cents_to_dollars_v1, key field 0, amount field 1, factor 100, cents to dollars.
Other valid definitions are INCOMPARABLE; malformed declarations raise errors.
Missing rules are UNKNOWN. RetainedRecords on both endpoints must pass the C5/C3
binding checks above before any row condition is accepted. No live reads occur.

Keys are exact typed signed-int64/nonnegative-uint64 C2 values. Record order is
irrelevant to the rule, including when each endpoint uses its own valid ordered
fingerprint offset. Keys sort through flat index vectors; physical row positions
are preserved. Every member of a duplicate key group fails without arbitrary pairing.
Missing/null keys are UNKNOWN; other key types are INCOMPARABLE. A one-sided known
key fails only when the opposite side has no unusable keys that could conceal its
counterpart. Extra fields outside the declared two-field rule are INCOMPARABLE.
Amounts use the bounded exact numeric condition above. Complete empty inputs pass
the declared empty identity. A known failing row dominates other uncertain rows,
which remain present in the row evidence. No generic transformation language exists.

TransformCheck retains the rule, both RetainedCheck results, a qualified EdgeCheck
and immutable row columns: source_index Q, target_index Q, status B, reason B.
Indices address logical offset-vector positions; absent sides use NULL_ID. Status
uses CheckStatus (PASS 0, FAIL 1, UNKNOWN 2, INCOMPARABLE 3), not C6 Status codes.
RowReason codes are MATCH 0, DIFFERENT 1, KEY_UNAVAILABLE 2, KEY_UNSUPPORTED 3,
DUPLICATE_KEY 4, MISSING_SOURCE 5, MISSING_TARGET 6, COUNTERPART_KEY_UNAVAILABLE 7,
AMOUNT_UNAVAILABLE 8, NONFINITE 9, AMOUNT_UNSUPPORTED 10, EXTRA_FIELDS 11.

EdgeCheck.content_status/content_reason preserve the original raw fingerprint
comparison, which may differ even for a valid transform. Qualified status checks
the rule, retained evidence, snapshot/declaration bindings and provenance. Missing
or incomplete provenance cannot become PASS/FAIL merely from matching row values.
Declared lineage describes intent; this result proves neither execution timing,
physical cause nor adversarial integrity. Accidental drift/corruption detection
inherits the experimental C3 fingerprint limitations.

Direct divergence candidates (PW-09.14)
divergence_candidates(index, before, after, checks=()) retains a direct C6
classify result, without hierarchy propagation, and selects only CHANGED rows with
VALUE_CHANGED reasons. Presence/containment/level changes, derived container changes,
read failures and incompatible policies are retained in comparison but not promoted
to direct content candidates. Zero candidates is not a claim of complete equality;
consumers must inspect the comparison's status, contexts, flags and diagnostics.

The graph namespace/scope and optional snapshot binding must match the after view;
invalid API bindings raise errors. Before/after comparison incompatibility remains a
normal C6 result. Selected EdgeCheck results must bind this graph, the after data
context, actual edge references and endpoint observation IDs/timestamps. Duplicate
checks for an edge are rejected rather than double counted. Check results are
trusted selected API outputs, not authenticated externally supplied certificates.

DivergenceResult contains graph context, full direct comparison, the selected check
tuple, candidate columns and support columns. All result arrays are owned immutable
uint64 views. Candidate columns: artifact_id, comparison_index, incoming_edge_count,
incoming_check_count, incoming_failed_count, outgoing_failed_count, flags. Candidate
IDs follow stable ascending comparison ID order. Missing graph inventory does not
erase a directly observed candidate. Support columns candidate_index, check_index,
endpoint link selected check positions and candidate rows. Endpoint SOURCE=0 and
TARGET=1 distinguish which endpoint actually changed; a self-edge may link both.
Parallel edges remain distinct evidence without duplicating candidate rows.

CandidateFlags bits: INCOMING_FAILED=1, INCOMING_PASSED=2, INCOMING_UNKNOWN=4,
INCOMING_INCOMPARABLE=8, OUTGOING_FAILED=16, NO_INCOMING_CHECK=32,
OUTSIDE_GRAPH=64, GRAPH_INCOMPLETE=128, UNCHECKED_INCOMING=256. A passed copy or
transform condition cannot erase a supported historical change. Unknown/unsupported
checks preserve their own status and raw content evidence; unchecked incoming edges
remain visible. Inventory coverage and unrelated read failures stay independent.
These are observed divergence candidates, with no inferred execution order, proven
physical origin or causal attribution. Earliest ranking is a separate explicit-time
operation; evidence capture windows do not supply data observation timing.

Observed-time ranking (PW-09.15)
ObservationWindows(snapshot_id, columns, clock_domains=()) validates flat uint64
observation_id, started_at_ns, finished_at_ns, clock_id and time_valid columns.
Observation IDs are unique non-sentinel IDs. The snapshot binds the after data;
clock_domains is an immutable tuple of unique nonempty clock identity strings.
clock_id indexes that dictionary, or NULL_ID for an unidentified clock. time_valid
is 0/1. Unknown windows require zero endpoints and NULL_ID clock; known windows
require start<=finish and may still have an unknown clock. Zero and uint64 maximum
are valid known endpoints. Views borrow stable source aliases like C1/C6.

rank_earliest(divergence, windows=None) selects timing by the candidate's after
observation ID and verifies its captured observed_at_ns falls within the supplied
data-observation window. Inconsistent snapshot/time bindings raise explicit errors.
Missing timing and known windows with no clock stay unresolved. No timing is
inferred from edge Evidence, integer units, snapshot IDs or source-version strings.
Other supplied observation windows do not participate in candidate ranking.

Within each identified clock, a candidate is excluded from the earliest set only
if another candidate's finish is strictly less than its start. Equal, overlapping
or touching windows remain tied. Unknown time/clock candidates remain in the set
alongside every clock's earliest candidates. The implementation computes per-clock
minimum finishes in linear time after ID lookup; it does not compare every pair.
No order is inferred across different clock identities. This ranks selected observed
changes in this comparison; it is not a global history search or corruption time.

EarliestResult retains divergence and windows, immutable columns and an immutable
uint64 earliest_candidate_indices vector. Columns: candidate_index, window_index,
started_at_ns, finished_at_ns, clock_id, time_valid, earliest (uint64); reason
(uint8). Missing window indexes use NULL_ID. TimingReason codes UNKNOWN_TIME=0,
UNKNOWN_CLOCK=1, EARLIEST_WITHIN_CLOCK=2, DEFINITELY_LATER=3. RankingStatus is
EMPTY=0, UNIQUE_EARLIEST=1, AMBIGUOUS=2, UNKNOWN=3. EMPTY describes no candidates,
with original comparison uncertainty still retained. UNKNOWN denotes absent timing,
unidentified clocks or multiple clock domains. UNIQUE_EARLIEST requires one earliest
candidate and complete timing in one clock; it asserts no physical/causal origin.
Underlying graph and data coverage limitations remain in the retained divergence.

Potential exposure and observed downstream content (PW-09.16 / PW-09.17)
impact(index, start_ids, divergence=None, relations=(COPY,TRANSFORM,DERIVED),
include_starts=False) performs one shared multi-source forward CSR traversal.
Repeated starts, cycles, parallel paths and shared descendants contribute each
artifact once. Every selected start is excluded by default, even if another start
or a cycle reaches it. include_starts=True includes the zero-hop selection.
Unknown/out-of-inventory starts raise errors; they are not silently dropped.
Empty starts and known disconnected vertices have known empty reachable sets.
Relation filters use the same canonical semantics as reachable; no containment
edges or inferred equal-content edges participate. Work is linear in visited graph
structure apart from existing sorted-ID lookups, rather than repeated per-root walks.

ImpactResult retains graph context, immutable sorted unique start_ids, relation
selection, include_starts, optional DivergenceResult and immutable output columns.
Columns artifact_id, comparison_index and flags are uint64; status and reason are
the original C6 uint8 codes. Missing comparison rows use NULL_ID and UNKNOWN /
SELECTION_UNAVAILABLE, unless comparison policies are themselves incompatible.
Incompatible policy/scope remains INCOMPARABLE even without alignable rows.
Divergence must carry the same lineage context, including declaration version,
scope, coverage/failures and optional snapshot binding. Original comparison
contexts, observations, read failures and uncertainty remain available through it.

ImpactFlags are POTENTIAL=1, OBSERVED_DOWNSTREAM_CHANGE=2,
WITHOUT_OBSERVATION_ROW=4, GRAPH_INCOMPLETE=8. Every output node is potentially
exposed within captured declared lineage. The observed label additionally requires
a direct C6 CHANGED / VALUE_CHANGED fact at that reachable node. Added/removed
presence, parent moves, unknown reads and unsupported policies do not satisfy it.
Without supplied divergence, observed evidence is unavailable, not a complete
negative finding. potential_count and observed_changed_count count unique artifacts
independently, never branch paths, repeat scans or incident occurrences. A known
local change can coexist with unrelated read failures or incomplete graph scope.
Reachability plus change is consistent with propagation but does not establish
transmission, execution timing, physical corruption or cause.

Incident identity component (PW-09.18)
IncidentPolicy(recurrence_window_ns, clock_domain) requires an explicit positive
uint64 window and nonempty clock identity; it has no default business window.
incident_observations(ranking, policy) creates canonical full keys and flat columns
for every direct candidate, including candidates outside the earliest set. This key
preparation API does not allocate incident IDs or merge recurrence. Full keys use compact UTF-8 JSON
with non-ASCII text preserved and uint64 references represented as decimal strings.
Keys are interned through the existing exact-byte LabelDictionary, never truncated
to a purportedly collision-free numeric digest. Key IDs are local dictionary indexes,
not durable incident IDs. Results preserve the ranking and policy.

Grouping key is the JSON array [anchored-window-v1, window_ns, configured_clock,
namespace, scope, artifact_id, DIRECT_CONTENT_CHANGE, algorithm_id, profile_id,
schema_id, mode]. All identities are full values. Observation key is
[observed-divergence-v1, full_group_key, before_snapshot_id, after_snapshot_id,
before_observation_id, after_observation_id, declaration_source_version, references].
References contain edge_id/source_id/target_id/evidence_id/transform_id as decimal
strings and endpoint code as an integer, sorted by stable edge ID then endpoint.
Reordering selected checks does not change identity. Different supporting reference
sets remain distinct evidence observations; repeating the same set does not invent
a new observation identity. Referenced payload authenticity is not established here.

Timing has a separate key [observation-window-v1, time_valid, actual_clock_or_null,
start_ns, finish_ns]. Clock dictionary row order is not identity. Timing refinement
does not rewrite the exact data/evidence observation key; the registry must handle
conflicting timing explicitly. Missing data timing does not prevent repeat detection.
Columns candidate_index, group_key_id, observation_key_id, timing_key_id,
started_at_ns, finished_at_ns, time_valid, clock_compatible, mergeable are immutable
uint64 views. Keys are immutable UTF-8 payload/uint64 offset/length buffers;
IncidentObservations.resolve_key selects full key text. No source reads occur.

clock_compatible requires known timing in the explicitly configured clock. mergeable
also requires finish-start < recurrence_window_ns: a wide or boundary-spanning
interval cannot be reduced to a guessed point for temporal recurrence. Its complete
timing key is retained. Anchored episode membership requires the whole interval
inside the episode's half-open window, as implemented by the registry below.

Incident registry allocation (PW-09.18)
IncidentRegistry(registry_id, policy) requires a canonical 32-character lowercase
hex registry identity and an explicit IncidentPolicy. register(IncidentObservations)
returns IncidentUpdate with a new registry, the original input and immutable uint64
assignment columns candidate_index, incident_id, observation_index, flags. Previous
registry instances remain unchanged, including on validation/timing conflicts.
IDs are append positions scoped by registry_id; never compare them across registries.
Flat episode and distinct observation tables retain full key IDs, anchors and counts.
resolve_key retrieves original full identities. This reference allocator scans flat
episode rows and makes no unmeasured performance claim.

Exact observation keys are checked before timing/lateness grouping. An exact repeat
reuses both incident and observation positions without incrementing occurrence_count.
Changed timing for the same exact observation is an explicit error; this operation
does not silently refine metadata or move an existing assignment. Different compatible
observations can recur only if their entire known interval is inside the episode's
[anchor,anchor+window) boundary. Difference arithmetic avoids uint64 wraparound.
Wide/unknown/incompatible-clock intervals remain separate episodes. A new episode
anchors at the first interval's start in the configured clock, when compatible.

Late policy separate-late-v1: a distinct observation whose known start is below the
group's latest accepted known start gets a separate nonmergeable episode. It cannot
reanchor previous episodes, and subsequent late observations remain separate. Exact
repeats of earlier evidence still reuse their original assignment. Stable streaming
assignments therefore depend on documented arrival order; they do not claim to
reconstruct unobserved event order. occurrence_count counts distinct captured
data/evidence identities, not proven corruption events or repeated report execution.

IncidentFlags NEW_EPISODE=1, RECURRENCE=2, REPEATED=4, TIME_UNKNOWN=8, LATE=16,
WINDOW_AMBIGUOUS=32 distinguish outcomes. Repeat outputs preserve timing/late/wide
qualifiers but replace NEW_EPISODE/RECURRENCE with REPEATED. Registry episodes expose
uint64 group_key_id, anchor_at_ns, anchor_valid, last_started_at_ns, mergeable,
occurrence_count. Observation rows expose observation_key_id, timing_key_id,
incident_id, flags. All public tables are owned immutable snapshots. Episode/count
capacity is bounded by the checked distinct observation count.

Durable registry state and replay validation (PW-09.18 / .21 / .22)
export_arrays() produces owned immutable metadata/key bytes and uint64 tables.
metadata is canonical compact UTF-8 JSON with schema_version 1.0, registry_id,
policy anchored-window-v1, decimal-string recurrence_window_ns, clock_domain and
late_policy separate-late-v1. The remaining inventory is keys_payload (uint8),
keys_offsets / keys_lengths (uint64), all episode columns prefixed episodes_, and
all observation columns prefixed events_. Episode row positions are the registry's
stable incident IDs. This exact versioned inventory rejects unknown fields/versions.

IncidentRegistry.from_arrays validates types/alignment, canonical JSON identities,
full supported fingerprint context, snapshot/observation and endpoint references,
clock/window validity and sorted unique support links. It replays every distinct
observation in original arrival order. Replayed assignments, qualification flags,
episode anchors/latest starts/counts, dictionary packing/IDs and metadata must all
match the saved arrays exactly. Duplicate receipts, unused keys, inconsistent
summaries and unsupported policy/version state fail explicitly. Loaded registries
own their state; subsequent caller buffer changes cannot alter them. Appends and
exact repeats after reload retain existing registry-scoped IDs.

These arrays can be stored in C5 without another serialization/dependency stack.
The reference C4/native/C5 integration saves and reopens a derived registry, then
repeats the same evidence while preserving a count of one. Unknown declaration
timing remains TIME_UNKNOWN across this round trip. Replay establishes internal
registry consistency; it does not authenticate referenced data/evidence or recreate
complete C6 analyses from identity keys. Original snapshot/control evidence and
uncertainty must be retained for a later why/impact explanation. C5 checksums and
C3 probabilistic/accidental-drift limitations remain separate guarantees.

Analysis schema handoff (PW-09.23)
pipewhy.lineage.ANALYSIS_SCHEMA_VERSION is 1.0. The portable contract manifest is
tests/fixtures/lineage_schema_v1.json, with exact enum values, result control fields,
column dtypes and required evidence/limitations. It references the existing C6
comparison_schema_v1.json instead of assigning competing C6 status/reason codes.
tests/fixtures/lineage_consumer_v1.json supplies an independent selected-result truth
case. The reference consumer in tests/unit/test_lineage_schema.py joins actual
comparison/check/candidate/timing/impact/registry results and retains uncertainty,
endpoint roles, original metadata links and independently counted outcomes.

Results use one explicitly selected L1 schema version. Consumers must retain the
version and full context identities; unsupported versions require explicit rejection
rather than best-effort status reinterpretation. Q means uint64 and B means uint8 on
the supported little-endian C1 host protocol. Portable JSON renders uint64 values
(including indexes, counts, flags and null sentinels) as exact decimal strings.
Evidence/control booleans remain booleans. Do not interpret CheckStatus's UNKNOWN=2
as C6 Status's ADDED=2, or CheckStatus's PASS=0 as provenance verification.

Required output context includes graph namespace/declaration version/scope/coverage,
failure diagnostics and optional snapshot binding; both data contexts with complete
algorithm/profile/schema/mode and capture failures; original comparison/observation
links; qualified checks separate from raw content; selected evidence kind, producer,
version, completeness, diagnostics and time/clock; selected transform and retained
input outcomes; support endpoint roles; data timing validity/clocks/intervals and the
unresolved earliest set; unique potential/observed counts with C6 reasons; and registry
identity, full canonical keys, policy/config and recurrence qualifications when used.
Input/result borrowing and owned immutable table rules above continue to apply.

The reference projection is an acceptance consumer, not a released CLI/export API.
PW-11 and PW-12 implement selected, bounded public views/exports on this boundary.
Metadata and limitations must survive those selections; trimming rows cannot turn
partial scope, unknown provenance or absent timing into a complete/causal answer.
PW-09 implements observed candidate/impact/recurrence analysis under experimental C3
limits. It does not identify a proven physical corruption point or authenticate data.
