GovernanceRunProjector
The GOV-1 wall: overlays the evidence (a `GovernanceRun`) onto the law (a `GovernancePlan`) and decides whether that evidence is honest. "Evidence may repeat the law to show what it observed; it may never redefine it." Pure, no I/O: `observe()` never throws — every failure path is expressed as `ObservationState` + `reason`, so a report (T6) can show stale/invalid in a learnable way without blowing up. Zero mutation of the input `GovernancePlan`: the VOs are readonly, so a stamped `GovernanceGate` is always a NEW instance.
GovernanceRunProjector::observe()
public function observe(Milpa\Governance\GovernancePlan $plan, Milpa\Governance\GovernanceRunLookup $lookup): Milpa\Governance\GovernanceObservationCrosses `$plan` (the law) against `$lookup` (the most recent evidence lookup) and produces the resulting observation. Wall order: 1. `lookup` NONE → NEVER_OBSERVED, the plan comes back with all its gates as null. 2. `lookup` INVALID → INVALID, propagates `lookup->reason` as-is. 3. `lookup` FOUND, but the run observed a different plan (two locks: `profileHash` and `planHash`) → STALE, gates as null (nothing is projected from a misaligned run). 4. `lookup` FOUND, but the run is internally inconsistent (empty runId, inverted dates, duplicate/unknown gate, PASSED without producerRef, WAIVED without justification, boundTo that redefines the law) → INVALID with the reason naming the offender. 5. Everything else → CURRENT: stamps each outcome onto its gate by id; a plan gate with no corresponding record stays PENDING — never a fabricated passed.
Parameters
| Name | Type | Description |
|---|---|---|
| $plan | Milpa\Governance\GovernancePlan | |
| $lookup | Milpa\Governance\GovernanceRunLookup |