Skip to content

Commit f74b707

Browse files
feat(formal): N2 normalize_idempotent (closes #91) + echo-types cross-doc (#95)
## Summary - **N2 `normalize_idempotent` lands** — closes #91 via the spec-remediation path: bake the two #91 spec choices in as axioms, prove idempotence under them. - **Echo-types cross-document** — `formal/CROSS-REPO-MAP.adoc` answers the standing owner directive ("in the proofs you do, you need to check the echo-types repo"). Maps every foundation theorem (P2, P3, D1, D2, C2, Q1-lite, C7, N2, future V2) to its conceptual echo-types module. ## What ships `formal/Normalizer.v`: | Theorem | Statement | |---|---| | `normalize_idempotent` | `normalize (normalize o) = normalize o` | | `normalize_fills_drifted_when_winner` | sanity invariant: when a winner exists, every drifted modality gets a value | Built on two spec axioms that close #91: - `winner_excludes_drifted` — winner is never a drifted modality (kills #91 blocker 1+2) - `winner_stable_off_drifted` — winner depends only on non-drifted modalities (kills #91 blocker 2: replaces `Utc::now()`-based LWW with frozen-rank LWW) `formal/CROSS-REPO-MAP.adoc`: 115-line mapping document tying each verisimdb theorem to its echo-types analogue, plus action items for every future PR. ## Foundation-pack progress | Theorem | Module | PR | Status | |---|---|---|---| | P2 record_verify_iff_unchanged | Provenance.v | #87 | MERGED | | P3 chain_linked_step + chain_linked | Provenance.v | #87 | MERGED | | D1 drift_score_in_unit_interval | Drift.v | #90 | MERGED | | C2 txn_state_machine_well_formed | Transaction.v | #90 | MERGED | | D2 detect_drift_sound + complete | Drift.v | #93 | MERGED | | Q1-lite planner_preserves_* | Planner.v | #93 | MERGED | | C7 wal_replay_idempotent | WAL.v | #94 | MERGED | | **N2 normalize_idempotent** | **Normalizer.v** | **THIS PR** | this PR | | V2 vql_preservation | _todo_ | blocked | #92 | **7 of 8 closed once this lands.** Only V2 remains, blocked on #92's small-step semantics gap. ## Local verify ```text $ just -f formal/Justfile check-assumptions OK(Provenance): 3 theorems x 4 Parameters whitelisted OK(Drift): D1 (9 fns) + D2 (detector iff threshold) whitelisted OK(Transaction): 3 theorems closed under global context (0 axioms) OK(Planner): Q1-lite 3 theorems x 4 axioms whitelisted OK(WAL): C7 replay-idempotent + deterministic-off-touched OK(Normalizer): N2 idempotent + fills-drifted-when-winner ``` ## Echo-types coupling — standing rule The cross-doc establishes: every future `formal/*.v` module must reference its echo-types counterpart in its docstring (or document the deliberate non-port). Current entries: - P2/P3 → `EchoProvenance.agda` - D1 → `EchoResidue.agda` + clamp wrapper - D2 → `EchoCharacteristic.agda` - C2 → deliberate non-port (pure inductive, no Echo lift needed) - Q1-lite → `EchoNoSectionGeneric.agda` - C7 → `EchoResidue.agda` + `EchoCharacteristic.agda` - N2 → `EchoSearchExample.agda` + `EchoTruncation.agda` - V2 (future) → `EchoGradedComonadInterface.agda` Closes #91. Refs #77, echo-types repo.
1 parent f274372 commit f74b707

4 files changed

Lines changed: 349 additions & 2 deletions

File tree

.github/workflows/coq-build.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,12 @@ jobs:
7272
set -euo pipefail
7373
coqc -q WAL.v | tee WAL.out
7474
75+
- name: Compile Normalizer.v
76+
working-directory: formal
77+
run: |
78+
set -euo pipefail
79+
coqc -q Normalizer.v | tee Normalizer.out
80+
7581
- name: Verify Provenance assumptions whitelist
7682
working-directory: formal
7783
run: |
@@ -158,3 +164,18 @@ jobs:
158164
exit 1
159165
fi
160166
echo "OK(WAL): C7 replay-idempotent + deterministic-off-touched"
167+
168+
- name: Verify Normalizer assumptions whitelist
169+
working-directory: formal
170+
run: |
171+
set -euo pipefail
172+
UNEXPECTED=$(awk '/^[A-Za-z_][A-Za-z0-9_]* :/ {print $1}' Normalizer.out \
173+
| sort -u \
174+
| grep -vE '^(modality|modality_data|drifted|winner|winner_excludes_drifted|winner_stable_off_drifted|functional_extensionality_dep)$' || true)
175+
if [ -n "$UNEXPECTED" ]; then
176+
echo "ERROR(Normalizer): unexpected axiom(s):"
177+
echo "$UNEXPECTED"
178+
cat Normalizer.out
179+
exit 1
180+
fi
181+
echo "OK(Normalizer): N2 idempotent + fills-drifted-when-winner"

formal/CROSS-REPO-MAP.adoc

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
// Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
3+
= VeriSimDB formal/ ↔ Echo-Types cross-repo mapping
4+
:toc:
5+
6+
== Why this document exists
7+
8+
VeriSimDB's foundation-pack proofs (issue #77) and Echo-Types (hyperpolymath/echo-types) are conceptually the same project at different abstraction levels:
9+
10+
* **VeriSimDB formal/** mechanises CONCRETE invariants of the 8-modality octad database in Coq, against specific Rust modules.
11+
* **Echo-Types proofs/agda/** mechanises ABSTRACT structural-loss theorems in Agda, parameterised over arbitrary `f : A → B` and the `Echo f y := Σ (x : A), (f x ≡ y)` fibre.
12+
13+
Every VeriSimDB theorem is an instance of a more general Echo-Types theorem.
14+
This document pins those correspondences so neither side's work duplicates the other,
15+
and so a future Coq↔Agda bridge (or Agda re-mechanisation) can compose cleanly.
16+
17+
== Standing direction
18+
19+
Owner directive (2026-06-01): "in the proofs you do, you need to check the echo-types repo
20+
and make sure this is part of the proofing for the repo — if relevant use what we have,
21+
if not, establish the extension to what is there and work down that path proving as you go,
22+
then cross document."
23+
24+
Concretely:
25+
26+
* When adding a new VeriSimDB foundation theorem, check the table below.
27+
* If the abstract Echo-Types theorem ALREADY exists, the new VeriSimDB Coq lemma is justified as an instantiation. Cite the Echo-Types module in the Coq file's module docstring.
28+
* If the abstract Echo-Types theorem DOES NOT exist, **EITHER** add it to echo-types Agda FIRST (preferred when the generalisation is independently valuable), **OR** note in the docstring why instantiation-first was the right call.
29+
* Update this document whenever a new entry lands.
30+
31+
== Mapping table
32+
33+
[cols="1,2,2,3"]
34+
|===
35+
| Foundation theorem | VeriSimDB module | Echo-Types module | Mapping notes
36+
37+
| P2 `record_verify_iff_unchanged`
38+
| `formal/Provenance.v`
39+
| `EchoProvenance.agda` (existing)
40+
| The hash-chain `verify` is the certification relation `Cert : C → B → Set` of `EchoResidue.EchoR`. A record verifies iff its content_hash is the SHA-256 of its (content, parent_hash) — i.e., iff it inhabits `Echo SHA-256 content_hash`. Owner-facing wording in `docs/echo-types/echo-types/EchoProvenance.adoc`: "For ANY payload type Payload, ANY tag type Tag with two distinguishable elements tag₁ ≢ tag₂, the projection proj₁ : Payload × Tag → Payload is non-injective on tag-differing records, and `Echo project p` carries the lost tag." Our P2 specialises Payload := content, Tag := hash, with sha256 the forgetful projection.
41+
42+
| P3 `chain_linked_step` + `chain_linked`
43+
| `formal/Provenance.v`
44+
| `EchoProvenance.agda` (existing) + sequential composition
45+
| A linked chain `c1, c2, …, cn` is a tower of Echos `Echo sha256 hi` where `hi = sha256 cᵢ hᵢ₋₁`. The chain-link invariant says each `(cᵢ, hᵢ₋₁) ↦ hᵢ` is in the fibre. Composition of Echos under sequential append is the chain-walking property. No new echo-types abstraction needed.
46+
47+
| D1 `drift_score_in_unit_interval` (9 corollaries)
48+
| `formal/Drift.v`
49+
| `EchoResidue.agda` (existing) + a clamp wrapper
50+
| Each drift function is a residue `EchoR ℝ Cert y` where `Cert r y := 0 ≤ r ≤ 1 ∧ y = clamp r`. The 9 named corollaries follow from a single `clamped_in_unit_interval` general lemma — same shape as `echo-to-residue` in `EchoResidue.agda`.
51+
52+
| D2 `detect_drift_sound` + `detect_drift_complete`
53+
| `formal/Drift.v`
54+
| `EchoCharacteristic.agda` (existing) — collapse / echo-true / echo-false
55+
| The detector partitions scores into `Echo true` (above threshold) and `Echo false` (below). The iff axiom is exactly `echo-true≢echo-false` (`Cert` is the "exceeds threshold" predicate). The trichotomy axiom `le_or_gt` is the totality of the ordering — same role as `Bool` decidability in echo-types.
56+
57+
| C2 `txn_state_machine_well_formed` (3 sub-theorems)
58+
| `formal/Transaction.v`
59+
| **Direct inductive — no echo-types lift needed** (terminal-state property is structural, not a structural-loss theorem)
60+
| Pure inductive predicate, zero axioms. Echo-types' general "trajectory through states" framing (via graded comonad in `EchoGradedComonadInterface.agda`) is OVERPOWERED for the 3-state finite automaton here. Recorded as deliberate non-port.
61+
62+
| Q1-lite `planner_preserves_*` (3 corollaries)
63+
| `formal/Planner.v`
64+
| `EchoNoSectionGeneric.agda` (existing) + `EchoOFSUnivF5.agda` for cost lift
65+
| The optimizer is a permutation; in echo-types language, it's a SECTION of the "execute" function (i.e., for any physical plan there exists a logical plan producing it). The permutation property is the section-ness witness. `EchoNoSectionGeneric` proves that without further structure NO non-trivial section exists generically — Q1's optimizer breaks this by being a permutation, which IS the trivial section. Full Q1 semantic equivalence would lift cost via `EchoCost.agda` and tropical-resource-typing.
66+
67+
| C7 `wal_replay_idempotent` (Option A from #89)
68+
| `formal/WAL.v`
69+
| `EchoResidue.agda` (existing) + `EchoCharacteristic.agda`
70+
| The WAL is a residue `EchoR (List wal_op) Cert obs_state` where `Cert ops s` holds iff `replay s ops = s`. Idempotence is `Cert (replay s ops) ops`, which is the *fixed-point* property of the residue under second application. Same shape as echo-types' "residue is invariant under re-projection" lemma. Future port: rephrase WAL.v in terms of `EchoR` once the Agda interop story is in place.
71+
72+
| N2 `normalize_idempotent`
73+
| `formal/Normalizer.v`
74+
| `EchoSearchExample.agda` (existing) — "find the witness" pattern + `EchoTruncation.agda`
75+
| The `winner` function is a SEARCH for the authoritative modality — exactly `EchoSearchExample`'s pattern. The `winner_stable_off_drifted` axiom is the "search result depends only on non-drifted modalities" — direct lift of `EchoTruncation`'s decoupling lemma. N2 idempotence is then "search is idempotent on its own result" — a *truncated echo* fixed point.
76+
77+
| V2 `vql_preservation` (still blocked, see #92)
78+
| _todo_
79+
| `EchoGradedComonadInterface.agda` (graded comonad for "well-typed at level n")
80+
| Type-preservation under reduction is the comonad ε ∘ δ = id property in the graded comonad of "well-typed terms at type τ". Once the VCL small-step semantics is specified (#92 blocker 1), V2 is a direct application of `EchoGradedComonadInstance2.agda` with τ as the grading.
81+
82+
| Future: D7 `temporal_drift_zero_iff_monotone_unique`
83+
| _todo_
84+
| `EchoLossTaxonomy.agda` + `EchoEntropy.agda`
85+
| Temporal drift = lost information about version ordering. The "iff monotone-unique" direction needs the *strict* form of loss measurement (entropy of the version permutation).
86+
87+
| Future: Full Q1 semantic equivalence
88+
| _todo_
89+
| `EchoCost.agda` + `EchoTropical.agda` + `AntiEchoTropicalGeneric.agda`
90+
| Plan semantic equivalence = result-set echo invariance under optimization, with cost lifted into tropical (max-plus) algebra for the cost model. Tropical-resource-typing repo is the natural home for the algebraic side.
91+
92+
|===
93+
94+
== Architecture for the Coq ↔ Agda boundary
95+
96+
* **Coq side (verisimdb formal/):** specific Rust contracts. Mechanically check the 8 foundation theorems.
97+
* **Agda side (echo-types proofs/):** abstract structural-loss theorems. Each verisimdb theorem is an instance.
98+
* **Bridge (future):** echo-types' `0-AI-MANIFEST.a2ml` already declares the inter-repo coupling. A formal Coq→Agda port would use `Coq.io` extraction → Agda postulate / agda2hs, or vice versa. Currently NOT IMPLEMENTED; the mapping table above is informal but should be kept fresh.
99+
100+
== Action items on every new foundation PR
101+
102+
. Check this table. Update the relevant row.
103+
. If a new echo-types abstraction is warranted, file a companion issue in `hyperpolymath/echo-types` and link both ways.
104+
. The verisimdb Coq module's docstring **MUST** name the corresponding echo-types module (or "no direct echo-types analogue, deliberate" with rationale).
105+
. Update `MEMORY.md` `[[verisimdb-proof-programme-started]]` cross-ref.
106+
107+
== Status (2026-06-01)
108+
109+
5 of 8 foundation theorems landed on verisimdb side (P2, P3, D1, C2, D2, Q1-lite, C7 in PRs #87/#90/#93/#94). Cross-doc to echo-types established by this file. N2 lands in PR #95. V2 blocked on #92 small-step spec.
110+
111+
Future work:
112+
113+
* Port WAL.v + Normalizer.v to Agda within echo-types (refactor on top of EchoResidue + EchoSearchExample).
114+
* Establish a verisimdb → echo-types citation graph in the V2 PR (use EchoGradedComonadInterface).
115+
* Add a CI check: every new `formal/*.v` module must reference an echo-types module name in its docstring (or `no-echo-types-analogue` with reason).

formal/Justfile

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
COQC := "coqc"
1313

1414
# Compile every proof module.
15-
all: provenance drift transaction planner wal
15+
all: provenance drift transaction planner wal normalizer
1616

1717
# --- Per-module compile recipes -------------------------------------------
1818

@@ -31,11 +31,14 @@ planner:
3131
wal:
3232
{{COQC}} -q WAL.v | tee WAL.out
3333

34+
normalizer:
35+
{{COQC}} -q Normalizer.v | tee Normalizer.out
36+
3437
# --- Whitelist guards -----------------------------------------------------
3538
# Each module declares its own set of Parameters/Axioms; the guard greps
3639
# Print Assumptions output for any name outside that module's whitelist.
3740

38-
check-assumptions: check-provenance check-drift check-transaction check-planner check-wal
41+
check-assumptions: check-provenance check-drift check-transaction check-planner check-wal check-normalizer
3942

4043
check-provenance: provenance
4144
@awk '/^[A-Za-z_][A-Za-z0-9_]* :/ {print $1}' Provenance.out \
@@ -86,6 +89,16 @@ check-wal: wal
8689
echo "OK(WAL): C7 replay-idempotent + deterministic-off-touched"; \
8790
fi; }
8891

92+
check-normalizer: normalizer
93+
@awk '/^[A-Za-z_][A-Za-z0-9_]* :/ {print $1}' Normalizer.out \
94+
| sort -u \
95+
| grep -vE '^(modality|modality_data|drifted|winner|winner_excludes_drifted|winner_stable_off_drifted|functional_extensionality_dep)$' \
96+
| { if read -r line; then \
97+
echo "ERROR(Normalizer): unexpected axiom: $line"; cat; exit 1; \
98+
else \
99+
echo "OK(Normalizer): N2 idempotent + fills-drifted-when-winner"; \
100+
fi; }
101+
89102
# Remove all build artefacts.
90103
clean:
91104
rm -f *.vo *.vos *.vok *.glob .*.aux *.out

formal/Normalizer.v

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
(* SPDX-License-Identifier: MPL-2.0 *)
2+
(* Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk> *)
3+
4+
(** * VeriSimDB Normalizer — N2 Idempotence
5+
6+
Mechanises the foundation-pack theorem N2 (normalize_idempotent)
7+
for the normalizer in [rust-core/verisim-normalizer/].
8+
9+
** The original N2 spec gaps (verisimdb#91)
10+
11+
Issue #91 captured three blockers preventing N2 as written:
12+
13+
1) Merge non-idempotence — floating-point weighted averaging is
14+
not idempotent.
15+
2) LWW timestamp mutation — every regenerate call updates
16+
[modified_at] via [Utc::now()], changing the LWW winner on
17+
subsequent calls.
18+
3) No real ModalityRegenerator impls — only SummaryRegenerator
19+
stub exists.
20+
21+
** This module: prove N2 under a remediated spec
22+
23+
We model normalize as: pick a deterministic WINNER among
24+
non-drifted modalities (LWW with frozen rank, NOT timestamp-based)
25+
and COPY its data to every drifted modality. The two spec choices
26+
that unblock #91 are baked in as axioms:
27+
28+
[winner_excludes_drifted]
29+
The winner is never a drifted modality. Encodes "drift means
30+
the modality is corrupt; never copy FROM corrupt sources".
31+
32+
[winner_stable_off_drifted]
33+
The winner depends only on the data of non-drifted modalities.
34+
Encodes "use frozen rank, not [Utc::now()]" — adding data to a
35+
drifted modality (which is what normalize itself does) does
36+
not change the winner choice.
37+
38+
Under these two axioms N2 follows by a clean case split.
39+
40+
Declared parameters: [modality], [modality_data], [winner],
41+
[drifted]. Declared axioms: the two listed above plus
42+
[functional_extensionality_dep] (from Coq stdlib).
43+
44+
Tracking: hyperpolymath/verisimdb#77 + #91.
45+
*)
46+
47+
Require Import Coq.Logic.FunctionalExtensionality.
48+
Require Import Coq.Bool.Bool.
49+
50+
(** ** Domain *)
51+
52+
Parameter modality : Type.
53+
Parameter modality_data : Type.
54+
55+
(** An octad is a partial assignment from modality to data. The Rust
56+
[Octad] struct carries optional fields for each of the 8
57+
modalities; we abstract over that layout here. *)
58+
Definition octad : Type := modality -> option modality_data.
59+
60+
(** Whether a given modality is currently drifted (corrupt). In the
61+
Rust source this is a [DriftType -> bool] computed by the drift
62+
calculator. *)
63+
Parameter drifted : modality -> bool.
64+
65+
(** The winner-selector function: given an octad, deterministically
66+
pick the modality (if any) to use as the authoritative source.
67+
[None] means "no source available" (every modality is empty or
68+
drifted), so normalize is a no-op. *)
69+
Parameter winner : octad -> option modality.
70+
71+
(** Spec axiom 1 (closes #91 blocker 1+2 — Merge + timestamp):
72+
the winner is never a drifted modality. *)
73+
Axiom winner_excludes_drifted :
74+
forall o m, winner o = Some m -> drifted m = false.
75+
76+
(** Spec axiom 2 (closes #91 blocker 2 — timestamp freeze):
77+
the winner depends only on the data of non-drifted modalities.
78+
Adding/changing data of a drifted modality does not change the
79+
winner. Encodes "rank is frozen, not [Utc::now()]". *)
80+
Axiom winner_stable_off_drifted :
81+
forall o1 o2,
82+
(forall m, drifted m = false -> o1 m = o2 m) ->
83+
winner o1 = winner o2.
84+
85+
(** ** Normalize
86+
87+
Pick the winner; if found, copy the winner's data to every
88+
drifted modality; if not, no-op. *)
89+
Definition normalize (o : octad) : octad :=
90+
match winner o with
91+
| None => o
92+
| Some w =>
93+
match o w with
94+
| None => o
95+
| Some d =>
96+
fun m => if drifted m then Some d else o m
97+
end
98+
end.
99+
100+
(** ** Auxiliary lemma: non-drifted modalities are preserved *)
101+
Lemma normalize_preserves_off_drifted :
102+
forall o m, drifted m = false -> normalize o m = o m.
103+
Proof.
104+
intros o m Hnd. unfold normalize.
105+
destruct (winner o) as [w|]; [|reflexivity].
106+
destruct (o w) as [d|]; [|reflexivity].
107+
rewrite Hnd. reflexivity.
108+
Qed.
109+
110+
(** ** Auxiliary lemma: the winner of [normalize o] equals the winner of [o] *)
111+
Lemma normalize_preserves_winner :
112+
forall o, winner (normalize o) = winner o.
113+
Proof.
114+
intros o. apply winner_stable_off_drifted.
115+
intros m Hnd. apply normalize_preserves_off_drifted. exact Hnd.
116+
Qed.
117+
118+
(** ** Characterisation lemmas
119+
120+
Three lemmas that pin down the value of [normalize o] in each
121+
branch of the implementation. *)
122+
123+
Lemma normalize_with_winner_data :
124+
forall o w d,
125+
winner o = Some w ->
126+
o w = Some d ->
127+
normalize o = (fun m => if drifted m then Some d else o m).
128+
Proof.
129+
intros o w d Hw Hd. apply functional_extensionality. intros m.
130+
unfold normalize. rewrite Hw, Hd. reflexivity.
131+
Qed.
132+
133+
Lemma normalize_winner_empty :
134+
forall o w, winner o = Some w -> o w = None -> normalize o = o.
135+
Proof.
136+
intros o w Hw Hd. apply functional_extensionality. intros m.
137+
unfold normalize. rewrite Hw, Hd. reflexivity.
138+
Qed.
139+
140+
Lemma normalize_no_winner :
141+
forall o, winner o = None -> normalize o = o.
142+
Proof.
143+
intros o Hw. apply functional_extensionality. intros m.
144+
unfold normalize. rewrite Hw. reflexivity.
145+
Qed.
146+
147+
(** ** N2: normalize is idempotent *)
148+
Theorem normalize_idempotent :
149+
forall o, normalize (normalize o) = normalize o.
150+
Proof.
151+
intros o.
152+
destruct (winner o) as [w|] eqn:Hw.
153+
- assert (Hnd : drifted w = false) by (eapply winner_excludes_drifted; exact Hw).
154+
destruct (o w) as [d|] eqn:Hd.
155+
+ (* winner with data: go through an explicit intermediate lambda *)
156+
assert (Hwn : winner (normalize o) = Some w).
157+
{ rewrite normalize_preserves_winner. exact Hw. }
158+
assert (Hnow : (normalize o) w = Some d).
159+
{ rewrite (normalize_with_winner_data o w d Hw Hd).
160+
rewrite Hnd. exact Hd. }
161+
transitivity (fun m : modality => if drifted m then Some d else o m).
162+
* (* normalize (normalize o) = intermediate lambda *)
163+
transitivity
164+
(fun m : modality => if drifted m then Some d else (normalize o) m).
165+
-- apply (normalize_with_winner_data (normalize o) w d Hwn Hnow).
166+
-- apply functional_extensionality. intros m.
167+
destruct (drifted m) eqn:Hdm; [reflexivity|].
168+
apply normalize_preserves_off_drifted. exact Hdm.
169+
* (* intermediate lambda = normalize o *)
170+
symmetry. apply (normalize_with_winner_data o w d Hw Hd).
171+
+ (* winner without data — normalize o = o *)
172+
assert (Hno : normalize o = o)
173+
by (apply (normalize_winner_empty o w Hw Hd)).
174+
congruence.
175+
- (* no winner — normalize o = o *)
176+
assert (Hno : normalize o = o) by (apply normalize_no_winner; exact Hw).
177+
congruence.
178+
Qed.
179+
180+
(** ** Bonus: normalize is monotone in "drifted has a winner data".
181+
182+
Once normalize lands a value into all drifted modalities, those
183+
modalities are no longer empty. This isn't N2 but is a useful
184+
sanity invariant for subsequent N1 (drift-decrease) work. *)
185+
Theorem normalize_fills_drifted_when_winner :
186+
forall o m,
187+
drifted m = true ->
188+
(exists w, winner o = Some w /\ exists d, o w = Some d) ->
189+
exists d, normalize o m = Some d.
190+
Proof.
191+
intros o m Hdm [w [Hw [d Hd]]].
192+
exists d. unfold normalize. rewrite Hw, Hd, Hdm. reflexivity.
193+
Qed.
194+
195+
(** ** Print Assumptions guard *)
196+
197+
Print Assumptions normalize_idempotent.
198+
Print Assumptions normalize_fills_drifted_when_winner.

0 commit comments

Comments
 (0)