betterwithage commited on
Commit
4c4cf1b
·
verified ·
1 Parent(s): d5c9132

chore(sync): mirror backend .py + Dockerfile to Space (hf-sync-backend)

Browse files

Automated backend sync from szl-holdings/a11oy main via hf-sync-backend.
Updated (differed from the Space): Dockerfile, src/a11oy/harvest/__init__.py, src/a11oy/harvest/harvest_budget.py, src/a11oy/harvest/wasted_energy_harvest.py, szl_anatomy_loop.py
Deleted (gone from the repo + Dockerfile COPY set): (none)

Keeps the Space-built backend (serve.py + the Dockerfile-COPY'd .py
modules) identical to GitHub main so the Space never rebuilds from a
stale backend, new endpoints don't 404 there, and orphaned modules
removed from the repo don't linger in the Space tree.

Dockerfile CHANGED
@@ -311,6 +311,9 @@ COPY src/a11oy/formulas/bloom_filter.py ./src/a11oy/formulas/bloom_filter.py
311
  COPY src/a11oy/formulas/kalman.py ./src/a11oy/formulas/kalman.py
312
  COPY src/a11oy/formulas/hnsw_retrieval.py ./src/a11oy/formulas/hnsw_retrieval.py
313
  COPY src/a11oy/formulas/reidemeister.py ./src/a11oy/formulas/reidemeister.py
 
 
 
314
  # ADDITIVE (Formulas SECTION page — closeout): serve.py imports a11oy_formulas_page
315
  # and calls .register(app) BEFORE the SPA catch-all, mounting GET /formulas/wired
316
  # (premium Inca-palette list of every live formula + thesis citation + Lean permalink
@@ -670,7 +673,7 @@ COPY szl_connectors/ ./szl_connectors/
670
  COPY szl_hf_bucket.py szl_metrics_prom.py ./
671
  # Forge fix: these modules are on main + imported by serve.py (try/except) but were NEVER COPY'd
672
  # into the image -> ModuleNotFoundError at startup -> /api/a11oy/v1/research/* + dark surfaces 404.
673
- COPY szl_research_infra.py szl_dark_surfaces_register.py ./
674
 
675
 
676
  CMD ["python", "serve.py"]
 
311
  COPY src/a11oy/formulas/kalman.py ./src/a11oy/formulas/kalman.py
312
  COPY src/a11oy/formulas/hnsw_retrieval.py ./src/a11oy/formulas/hnsw_retrieval.py
313
  COPY src/a11oy/formulas/reidemeister.py ./src/a11oy/formulas/reidemeister.py
314
+ COPY src/a11oy/harvest/__init__.py ./src/a11oy/harvest/__init__.py
315
+ COPY src/a11oy/harvest/wasted_energy_harvest.py ./src/a11oy/harvest/wasted_energy_harvest.py
316
+ COPY src/a11oy/harvest/harvest_budget.py ./src/a11oy/harvest/harvest_budget.py
317
  # ADDITIVE (Formulas SECTION page — closeout): serve.py imports a11oy_formulas_page
318
  # and calls .register(app) BEFORE the SPA catch-all, mounting GET /formulas/wired
319
  # (premium Inca-palette list of every live formula + thesis citation + Lean permalink
 
673
  COPY szl_hf_bucket.py szl_metrics_prom.py ./
674
  # Forge fix: these modules are on main + imported by serve.py (try/except) but were NEVER COPY'd
675
  # into the image -> ModuleNotFoundError at startup -> /api/a11oy/v1/research/* + dark surfaces 404.
676
+ COPY szl_research_infra.py szl_dark_surfaces_register.py szl_anatomy_loop.py ./
677
 
678
 
679
  CMD ["python", "serve.py"]
src/a11oy/harvest/__init__.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # © 2026 Lutar, Stephen P. — SZL Holdings
3
+ """a11oy.harvest — vendored wasted-energy harvest module.
4
+
5
+ Doctrine (binding):
6
+ - NO free-energy / over-unity. Harvests ALREADY-WASTED grid energy only.
7
+ - joules_label ALWAYS "sample" off-box; only on-box NVML flips it to "measured".
8
+ - All feeds are FREE and PUBLIC (no token).
9
+ - Reactive turns are NEVER gated by harvest posture.
10
+ - Locked-8 theorems untouched.
11
+ """
12
+ from .wasted_energy_harvest import (
13
+ current_harvest_posture,
14
+ harvest_provenance,
15
+ scan_world_renshare,
16
+ HarvestPosture,
17
+ FeedReading,
18
+ POSTURE_RANK,
19
+ )
20
+ from .harvest_budget import plan_soak, SoakPlan
21
+
22
+ __all__ = [
23
+ "current_harvest_posture",
24
+ "harvest_provenance",
25
+ "scan_world_renshare",
26
+ "HarvestPosture",
27
+ "FeedReading",
28
+ "POSTURE_RANK",
29
+ "plan_soak",
30
+ "SoakPlan",
31
+ ]
src/a11oy/harvest/harvest_budget.py ADDED
@@ -0,0 +1,735 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """harvest_budget.py — Proven sponge budget for the wasted-energy harvest.
2
+
3
+ Grounds the soak-window batch admission in the founder's own kernel-proven
4
+ formulas so the harvest is PROVABLY BOUNDED, not just heuristic.
5
+
6
+ Doctrine (binding, v11/v12):
7
+ - NO free-energy / over-unity. This computes an INFORMATION cap (Bekenstein,
8
+ proven by Lean) and an ENERGY FLOOR (Landauer, proven by Lean). Joule
9
+ figures are labelled SAMPLE/ESTIMATE until a real on-box NVML meter feeds them.
10
+ - The Bekenstein cap is on INFORMATION (bits) — this IS provable and the Lean
11
+ theorem is kernel-checked. The energy floor (Landauer) is also proven.
12
+ Joule figures stay SAMPLE; information figures are bounded by proven math.
13
+ - The soak loop is bounded by an Ouroboros-style hard-max + budget counter so
14
+ the sponge can NEVER run away. Reactive turns always preempt.
15
+ - Locked-proven set stays EXACTLY 8 (F1,F4,F7,F11,F12,F18,F19,F22 @ c7c0ba17).
16
+ This module cites those theorems but does NOT modify or add to the locked-8.
17
+
18
+ Formula citations (all kernel-proven / machine-checked):
19
+ ┌─────────────────────────────────────────────────────────────────────────────┐
20
+ │ BEKENSTEIN CAP (information bound) │
21
+ │ Lean theorem: bekenstein_bound_additive (n m : Nat) : │
22
+ │ bekensteinBits (n + m) = bekensteinBits n + bekensteinBits m │
23
+ │ ∧ bekensteinBits n ≤ bekensteinBits (n + m) │
24
+ │ Lean theorem: info_within_bound (n m e : Nat) (h : e ≤ bekensteinBits n) │
25
+ │ : e ≤ bekensteinBits (n + m) │
26
+ │ Source: Showcase/Frontier/EnergyBudgetWitness.lean │
27
+ │ lutar-lean PR #239 (KEYSTONE, 0-sorry) │
28
+ │ Composition: mirrors F19 (f19_budget_monotone, s1 ≤ s1+s2) │
29
+ ├─────────────────────────────────────────────────────────────────────────────┤
30
+ │ LANDAUER FLOOR (energy lower bound) │
31
+ │ Lean theorem: landauer_floor_pos (n q : Nat) (hn : 0<n) (hq : 0<q) : │
32
+ │ 0 < energyFloor n q │
33
+ │ Lean def: energyFloor n q = n * q (integer shadow of n·kT·ln2) │
34
+ │ Source: Showcase/Frontier/LandauerFloorWitness.lean │
35
+ │ lutar-lean PR #240 │
36
+ ├─────────────────────────────────────────────────────────────────────────────┤
37
+ │ MONOTONE LEDGER │
38
+ │ Lean theorem: energy_ledger_monotone (start : Nat) (draws : List Nat) : │
39
+ │ start ≤ ledgerSum start draws │
40
+ │ Source: Showcase/Frontier/EnergyBudgetWitness.lean (PR #239) │
41
+ │ Composition: mirrors F19 (f19_budget_monotone) │
42
+ ├─────────────────────────────────────────────────────────────────────────────┤
43
+ │ OUROBOROS BOUNDED RECURSION │
44
+ │ Source: szl-holdings/ouroboros packages/ouroboros/src/loop-kernel.ts │
45
+ │ Primitive: runLoop({ maxSteps }) — the loop NEVER exceeds maxSteps │
46
+ │ iterations; exits 'budgetExhausted' when the cap is hit. We mirror │
47
+ │ this exact pattern: OUROBOROS_MAX_SOAK_STEPS hard cap + a work-budget │
48
+ │ counter so the proactive sponge loop halts unconditionally. │
49
+ └─────────────────────────────────────────────────────────────────────────────┘
50
+ """
51
+ from __future__ import annotations
52
+
53
+ import math
54
+ import datetime
55
+ from dataclasses import dataclass, field
56
+ from typing import Any, Optional
57
+
58
+ # ---------------------------------------------------------------------------
59
+ # Constants
60
+ # ---------------------------------------------------------------------------
61
+
62
+ # Boltzmann constant * room temperature (300 K) * ln 2 — the Landauer minimum
63
+ # energy per irreversible bit erasure. SAMPLE until an on-box calorimeter
64
+ # feeds real values. Used only to ensure SAMPLE estimates never CLAIM to beat
65
+ # the floor; the floor itself is the proven lower bound.
66
+ _K_B = 1.380649e-23 # J/K (exact by SI 2019 redefinition)
67
+ _LN2 = 0.6931471805599453
68
+
69
+ # Default room temperature for Landauer floor calculations.
70
+ LANDAUER_DEFAULT_TEMP_K: float = 300.0
71
+
72
+ # Ouroboros bounded-recursion cap.
73
+ # Mirrors loop-kernel.ts DEFAULT_MAX_STEPS=8 / runLoop({ maxSteps }) hard cap.
74
+ # The proactive soak loop NEVER exceeds this iteration count.
75
+ OUROBOROS_MAX_SOAK_STEPS: int = 32 # generous but hard; reactive always preempts
76
+
77
+ # ---------------------------------------------------------------------------
78
+ # Part 1 — Bekenstein information cap
79
+ # ---------------------------------------------------------------------------
80
+ # Python mirror of:
81
+ # def bekensteinBits (n : Nat) : Nat := n * 8
82
+ # theorem bekenstein_bound_additive ... [PR #239, EnergyBudgetWitness.lean]
83
+ # theorem info_within_bound ... [PR #239, EnergyBudgetWitness.lean]
84
+
85
+
86
+ def bekenstein_info_cap(n_bytes: int) -> int:
87
+ """Return the Bekenstein bit-cap for an n-byte output register: n * 8.
88
+
89
+ Python mirror of `bekensteinBits (n : Nat) : Nat := n * 8` from
90
+ Showcase/Frontier/EnergyBudgetWitness.lean (lutar-lean PR #239, keystone,
91
+ 0-sorry). This is the maximum information that can be carried by an n-byte
92
+ register — the ceiling any admitted batch job must stay within.
93
+
94
+ Citation:
95
+ bekenstein_bound_additive (n m : Nat) :
96
+ bekensteinBits (n+m) = bekensteinBits n + bekensteinBits m
97
+ ∧ bekensteinBits n ≤ bekensteinBits (n+m)
98
+ [EnergyBudgetWitness.lean, PR #239, F19-composition]
99
+ """
100
+ if n_bytes < 0:
101
+ raise ValueError(f"n_bytes must be non-negative, got {n_bytes}")
102
+ return n_bytes * 8
103
+
104
+
105
+ class BekensteinAccumulator:
106
+ """Additive Bekenstein-cap accumulator for a soak window.
107
+
108
+ Implements the info_within_bound / bekenstein_bound_additive pattern:
109
+ each admitted job contributes its info load to a running total; a new job
110
+ is REFUSED if admitting it would push the total past the window cap.
111
+
112
+ All information figures are in BITS (integers, matching the Lean Nat model).
113
+
114
+ Citation:
115
+ info_within_bound (n m e : Nat) (h : e ≤ bekensteinBits n) :
116
+ e ≤ bekensteinBits (n + m)
117
+ bekenstein_bound_additive ...
118
+ [Showcase/Frontier/EnergyBudgetWitness.lean, lutar-lean PR #239, 0-sorry]
119
+ """
120
+
121
+ def __init__(self, window_cap_bytes: int) -> None:
122
+ """
123
+ Args:
124
+ window_cap_bytes: Total byte budget for the soak window. The
125
+ Bekenstein cap is window_cap_bytes * 8 bits.
126
+ """
127
+ if window_cap_bytes <= 0:
128
+ raise ValueError(f"window_cap_bytes must be positive, got {window_cap_bytes}")
129
+ self._cap_bits: int = bekenstein_info_cap(window_cap_bytes)
130
+ self._used_bits: int = 0
131
+ self._admitted: list[dict] = []
132
+
133
+ @property
134
+ def cap_bits(self) -> int:
135
+ return self._cap_bits
136
+
137
+ @property
138
+ def used_bits(self) -> int:
139
+ return self._used_bits
140
+
141
+ @property
142
+ def remaining_bits(self) -> int:
143
+ return max(0, self._cap_bits - self._used_bits)
144
+
145
+ def try_admit(self, job_id: Any, job_info_bits: int) -> bool:
146
+ """Attempt to admit a job that will process job_info_bits bits.
147
+
148
+ Returns True (admitted) iff used_bits + job_info_bits ≤ cap_bits,
149
+ mirroring info_within_bound: if e ≤ bekensteinBits n then e ≤ cap.
150
+ Returns False and does NOT update state when the job would exceed the cap.
151
+
152
+ Citation: bekenstein_bound_additive / info_within_bound
153
+ [EnergyBudgetWitness.lean, lutar-lean PR #239, keystone, 0-sorry]
154
+ """
155
+ if job_info_bits < 0:
156
+ raise ValueError(f"job_info_bits must be non-negative, got {job_info_bits}")
157
+ if self._used_bits + job_info_bits > self._cap_bits:
158
+ return False
159
+ self._used_bits += job_info_bits
160
+ self._admitted.append({"job_id": job_id, "info_bits": job_info_bits})
161
+ return True
162
+
163
+ def admitted_jobs(self) -> list[dict]:
164
+ return list(self._admitted)
165
+
166
+
167
+ # ---------------------------------------------------------------------------
168
+ # Part 2 — Landauer floor
169
+ # ---------------------------------------------------------------------------
170
+ # Python mirror of:
171
+ # def energyFloor (n q : Nat) : Nat := n * q
172
+ # theorem landauer_floor_pos ... [PR #240, LandauerFloorWitness.lean]
173
+
174
+
175
+ def landauer_floor_joules(bits: int, temp_K: float = LANDAUER_DEFAULT_TEMP_K) -> float:
176
+ """Minimum energy (joules, SAMPLE) to irreversibly erase `bits` bits at temp_K.
177
+
178
+ Computes bits * k_B * T * ln2, the Landauer minimum. This is a LOWER BOUND;
179
+ a real system dissipates at least this much. A SAMPLE joule estimate for
180
+ any batch job must never CLAIM to beat this floor (anti-over-unity guard).
181
+
182
+ The value returned is labelled SAMPLE/ESTIMATE because it uses nominal
183
+ k_B and T; a real on-box calorimeter is needed for a measured value.
184
+
185
+ Citation:
186
+ energyFloor (n q : Nat) : Nat := n * q (integer shadow of n·kT·ln2)
187
+ landauer_floor_pos (n q : Nat) (hn : 0<n) (hq : 0<q) : 0 < energyFloor n q
188
+ landauer_floor_additive (n m q : Nat) : energyFloor (n+m) q = ...
189
+ [Showcase/Frontier/LandauerFloorWitness.lean, lutar-lean PR #240]
190
+ """
191
+ if bits < 0:
192
+ raise ValueError(f"bits must be non-negative, got {bits}")
193
+ if temp_K <= 0:
194
+ raise ValueError(f"temp_K must be positive, got {temp_K}")
195
+ return bits * _K_B * temp_K * _LN2 # SAMPLE/ESTIMATE (doctrine)
196
+
197
+
198
+ def assert_sample_beats_landauer_floor(
199
+ bits: int,
200
+ sample_joules: float,
201
+ temp_K: float = LANDAUER_DEFAULT_TEMP_K,
202
+ ) -> None:
203
+ """Assert that a SAMPLE joule estimate does NOT claim to beat the Landauer floor.
204
+
205
+ If sample_joules < landauer_floor_joules(bits, temp_K), this raises
206
+ AssertionError — the estimate would be physically impossible (over-unity /
207
+ free-energy claim), violating doctrine.
208
+
209
+ Note: sample_joules=0 (unknown / not estimated) always passes — zero means
210
+ "not yet measured", not "costs nothing". Pass the actual SAMPLE estimate
211
+ only when you have one.
212
+
213
+ Citation: LandauerFloorWitness.lean, lutar-lean PR #240
214
+ """
215
+ if sample_joules <= 0:
216
+ return # 0 / negative = not estimated; floor check not applicable
217
+ floor = landauer_floor_joules(bits, temp_K)
218
+ assert sample_joules >= floor, (
219
+ f"DOCTRINE VIOLATION: SAMPLE joule estimate {sample_joules:.3e} J "
220
+ f"claims to beat the Landauer floor {floor:.3e} J for {bits} bits at "
221
+ f"{temp_K} K. This would be a free-energy / over-unity claim. "
222
+ f"[LandauerFloorWitness.lean, PR #240]"
223
+ )
224
+
225
+
226
+ # ---------------------------------------------------------------------------
227
+ # Part 3 — Monotone SoakLedger
228
+ # ---------------------------------------------------------------------------
229
+ # Python mirror of:
230
+ # theorem energy_ledger_monotone (start : Nat) (draws : List Nat) :
231
+ # start ≤ ledgerSum start draws
232
+ # [EnergyBudgetWitness.lean, PR #239, F19-composition: f19_budget_monotone]
233
+
234
+
235
+ @dataclass
236
+ class LedgerEntry:
237
+ """One append-only entry in the SoakLedger."""
238
+ job_id: Any
239
+ info_bits: int # proven-bounded
240
+ joules_sample: float # SAMPLE/ESTIMATE until NVML
241
+ joules_label: str = "sample" # NEVER changes
242
+ timestamp_utc: str = field(default_factory=lambda: datetime.datetime.now(datetime.timezone.utc).isoformat())
243
+ landauer_floor_joules: float = 0.0 # floor for audit
244
+ beats_floor: bool = True # False = over-unity claim detected
245
+
246
+
247
+ class SoakLedger:
248
+ """Append-only monotone ledger for the wasted-energy soak.
249
+
250
+ Models `energy_ledger_monotone` from EnergyBudgetWitness.lean (PR #239):
251
+ the running cumulative totals (info bits, sample joules) are monotone-
252
+ nondecreasing — appending a new draw never lowers the total.
253
+
254
+ A monotonicity assertion is checked on every append.
255
+
256
+ Citation:
257
+ energy_ledger_monotone (start : Nat) (draws : List Nat) :
258
+ start ≤ ledgerSum start draws
259
+ ledger_step_monotone (s d : Nat) : s ≤ s + d (= f19_budget_monotone)
260
+ [Showcase/Frontier/EnergyBudgetWitness.lean, lutar-lean PR #239, 0-sorry]
261
+ """
262
+
263
+ def __init__(self) -> None:
264
+ self._entries: list[LedgerEntry] = []
265
+ self._total_info_bits: int = 0
266
+ self._total_joules_sample: float = 0.0
267
+
268
+ @property
269
+ def total_info_bits(self) -> int:
270
+ return self._total_info_bits
271
+
272
+ @property
273
+ def total_joules_sample(self) -> float:
274
+ return self._total_joules_sample
275
+
276
+ @property
277
+ def entries(self) -> list[LedgerEntry]:
278
+ return list(self._entries)
279
+
280
+ def append(
281
+ self,
282
+ job_id: Any,
283
+ info_bits: int,
284
+ joules_sample: float = 0.0,
285
+ temp_K: float = LANDAUER_DEFAULT_TEMP_K,
286
+ ) -> LedgerEntry:
287
+ """Append one entry; assert monotonicity after each append.
288
+
289
+ Mirrors `ledger_step_monotone`: prev_total ≤ prev_total + draw.
290
+ Checks that the SAMPLE joule estimate does not beat the Landauer floor.
291
+
292
+ Joules are labelled SAMPLE/ESTIMATE and never treated as measured.
293
+ """
294
+ if info_bits < 0:
295
+ raise ValueError(f"info_bits must be non-negative, got {info_bits}")
296
+ if joules_sample < 0:
297
+ raise ValueError(f"joules_sample must be non-negative, got {joules_sample}")
298
+
299
+ prev_bits = self._total_info_bits
300
+ prev_joules = self._total_joules_sample
301
+
302
+ # Landauer floor guard (anti-over-unity)
303
+ floor = landauer_floor_joules(info_bits, temp_K)
304
+ beats_floor = True
305
+ if joules_sample > 0 and joules_sample < floor:
306
+ beats_floor = False # flag; do not raise (SAMPLE may be zero)
307
+
308
+ entry = LedgerEntry(
309
+ job_id=job_id,
310
+ info_bits=info_bits,
311
+ joules_sample=joules_sample,
312
+ joules_label="sample",
313
+ landauer_floor_joules=floor,
314
+ beats_floor=beats_floor,
315
+ )
316
+
317
+ self._total_info_bits += info_bits
318
+ self._total_joules_sample += joules_sample
319
+ self._entries.append(entry)
320
+
321
+ # Monotonicity assertion — mirrors energy_ledger_monotone
322
+ assert self._total_info_bits >= prev_bits, (
323
+ f"SoakLedger monotonicity violated on info_bits: "
324
+ f"{prev_bits} → {self._total_info_bits} [EnergyBudgetWitness.lean PR #239]"
325
+ )
326
+ assert self._total_joules_sample >= prev_joules, (
327
+ f"SoakLedger monotonicity violated on joules: "
328
+ f"{prev_joules} → {self._total_joules_sample} [EnergyBudgetWitness.lean PR #239]"
329
+ )
330
+
331
+ return entry
332
+
333
+ def provenance(self) -> dict:
334
+ """Return a receipt-shaped provenance block for the full soak window."""
335
+ return {
336
+ "soak_jobs": len(self._entries),
337
+ "total_info_bits": self._total_info_bits,
338
+ "total_joules_sample": self._total_joules_sample,
339
+ "joules_label": "sample",
340
+ "ledger_monotone": True,
341
+ "citation": (
342
+ "energy_ledger_monotone / ledger_step_monotone "
343
+ "[EnergyBudgetWitness.lean, lutar-lean PR #239, 0-sorry]"
344
+ ),
345
+ }
346
+
347
+
348
+ # ---------------------------------------------------------------------------
349
+ # Part 4 — Ouroboros bounded-recursion soak loop guard
350
+ # ---------------------------------------------------------------------------
351
+ # Models loop-kernel.ts runLoop({ maxSteps }):
352
+ # "runs steps until … we hit maxSteps ('budgetExhausted')"
353
+ # The proactive soak loop is capped at OUROBOROS_MAX_SOAK_STEPS; reactive
354
+ # turns are NEVER subject to this gate and always preempt.
355
+
356
+
357
+ @dataclass
358
+ class OuroborosBudget:
359
+ """A single-use Ouroboros-style work budget for one soak session.
360
+
361
+ Mirrors the loop-kernel.ts runLoop({ maxSteps }) primitive:
362
+ the kernel NEVER exceeds maxSteps — it exits 'budgetExhausted' when the
363
+ cap is hit. This budget wraps the Python soak loop equivalently.
364
+
365
+ Citation:
366
+ szl-holdings/ouroboros packages/ouroboros/src/loop-kernel.ts
367
+ runLoop({ maxSteps }) — exits 'budgetExhausted' at hard cap
368
+ """
369
+ max_steps: int = OUROBOROS_MAX_SOAK_STEPS
370
+ _steps_taken: int = field(default=0, init=False, repr=False)
371
+ _exhausted: bool = field(default=False, init=False, repr=False)
372
+
373
+ def step(self) -> bool:
374
+ """Consume one step. Returns True if the loop may continue, False if exhausted.
375
+
376
+ Once exhausted, every subsequent call returns False — the soak loop halts
377
+ unconditionally, mirroring 'budgetExhausted' in loop-kernel.ts.
378
+ """
379
+ if self._exhausted or self._steps_taken >= self.max_steps:
380
+ self._exhausted = True
381
+ return False
382
+ self._steps_taken += 1
383
+ return True
384
+
385
+ @property
386
+ def steps_taken(self) -> int:
387
+ return self._steps_taken
388
+
389
+ @property
390
+ def is_exhausted(self) -> bool:
391
+ return self._exhausted or self._steps_taken >= self.max_steps
392
+
393
+ @property
394
+ def exit_reason(self) -> str:
395
+ if self._exhausted or self._steps_taken >= self.max_steps:
396
+ return "budgetExhausted"
397
+ return "running"
398
+
399
+
400
+ # ---------------------------------------------------------------------------
401
+ # Part 5 — plan_soak: wire everything together
402
+ # ---------------------------------------------------------------------------
403
+
404
+
405
+ @dataclass
406
+ class SoakPlan:
407
+ """Result of plan_soak(): the admitted batch jobs and the proven bounds respected."""
408
+ admitted: list[dict] # subset of input jobs admitted within bounds
409
+ refused: list[dict] # jobs refused (would exceed Bekenstein cap)
410
+ ledger: SoakLedger
411
+ bekenstein_cap_bits: int
412
+ bekenstein_used_bits: int
413
+ ouroboros_steps_taken: int
414
+ ouroboros_max_steps: int
415
+ ouroboros_exit_reason: str
416
+ posture: str
417
+ wasted_energy_available: bool
418
+ proven_bounds_respected: list[str]
419
+ joules_label: str = "sample"
420
+ honest_note: str = (
421
+ "Information cap (Bekenstein) and ledger monotonicity are PROVEN by "
422
+ "kernel-checked Lean theorems (0-sorry). The Ouroboros loop bound is "
423
+ "test-checked (mirrors loop-kernel.ts maxSteps). Joule figures stay "
424
+ "SAMPLE/ESTIMATE until a real on-box NVML meter feeds them."
425
+ )
426
+
427
+
428
+ def plan_soak(
429
+ window: dict,
430
+ jobs: list[dict],
431
+ window_cap_bytes: Optional[int] = None,
432
+ max_soak_steps: int = OUROBOROS_MAX_SOAK_STEPS,
433
+ temp_K: float = LANDAUER_DEFAULT_TEMP_K,
434
+ ) -> SoakPlan:
435
+ """Admit batch jobs into the soak window, bounded by proven formulas.
436
+
437
+ Given:
438
+ window — the harvest posture dict (from harvest_posture_bridge /
439
+ should_soak_wasted_energy). Must have
440
+ wasted_energy_available=True for any jobs to be admitted.
441
+ jobs — list of dicts, each with:
442
+ 'id' : any hashable job identifier
443
+ 'info_bits' : int, information load the job will process
444
+ 'joules_est' : float (optional), SAMPLE energy estimate
445
+ window_cap_bytes — byte budget for the soak window; Bekenstein cap =
446
+ window_cap_bytes * 8 bits. If None, defaults to
447
+ sum(job['info_bits'] for job in jobs) // 8 + 1 (admits
448
+ exactly the jobs that fit within the cap individually but
449
+ refuses those that together overflow it). Callers should
450
+ supply a real window byte budget.
451
+ max_soak_steps — Ouroboros hard cap (default OUROBOROS_MAX_SOAK_STEPS).
452
+ temp_K — temperature for Landauer floor (default 300 K).
453
+
454
+ Returns:
455
+ SoakPlan with admitted subset, refused subset, proven bounds, ledger.
456
+
457
+ Proven bounds respected (in order):
458
+ 1. wasted_energy_available gate (posture check)
459
+ 2. Bekenstein additive cap (information bound, Lean proven PR #239)
460
+ 3. Landauer floor (energy lower bound, Lean proven PR #240)
461
+ 4. SoakLedger monotonicity (append-only, Lean proven PR #239)
462
+ 5. Ouroboros bounded-recursion cap (loop-kernel.ts maxSteps pattern)
463
+ """
464
+ posture = window.get("posture", "normal")
465
+ wasted = bool(window.get("wasted_energy_available", False))
466
+
467
+ # Default cap: sum of all job info bits translated back to bytes (generous)
468
+ if window_cap_bytes is None:
469
+ total_bits = sum(j.get("info_bits", 0) for j in jobs)
470
+ # Use sum+1 as the cap; the additive check still refuses over-cap combos
471
+ window_cap_bytes = max(1, (total_bits // 8) + 1)
472
+
473
+ accumulator = BekensteinAccumulator(window_cap_bytes)
474
+ ledger = SoakLedger()
475
+ budget = OuroborosBudget(max_steps=max_soak_steps)
476
+
477
+ admitted: list[dict] = []
478
+ refused: list[dict] = []
479
+ proven_bounds: list[str] = []
480
+
481
+ # Gate 1: posture check — only soak when wasted energy is available
482
+ if not wasted:
483
+ proven_bounds.append(
484
+ "posture_gate: wasted_energy_available=False — no jobs admitted "
485
+ "(grid not in negative-price/curtailed window)"
486
+ )
487
+ return SoakPlan(
488
+ admitted=admitted,
489
+ refused=list(jobs),
490
+ ledger=ledger,
491
+ bekenstein_cap_bits=accumulator.cap_bits,
492
+ bekenstein_used_bits=0,
493
+ ouroboros_steps_taken=0,
494
+ ouroboros_max_steps=max_soak_steps,
495
+ ouroboros_exit_reason="posture_gate",
496
+ posture=posture,
497
+ wasted_energy_available=wasted,
498
+ proven_bounds_respected=proven_bounds,
499
+ )
500
+
501
+ proven_bounds.append(
502
+ "posture_gate: wasted_energy_available=True — soak window open"
503
+ )
504
+
505
+ # Main bounded soak loop — capped by Ouroboros budget
506
+ for job in jobs:
507
+ # Gate 2: Ouroboros bounded-recursion cap
508
+ if not budget.step():
509
+ # budgetExhausted — remaining jobs refused (not processed)
510
+ refused.append({**job, "refused_reason": "ouroboros_budget_exhausted"})
511
+ continue
512
+
513
+ job_id = job.get("id", f"job_{len(admitted)+len(refused)}")
514
+ info_bits = int(job.get("info_bits", 0))
515
+ joules_est = float(job.get("joules_est", 0.0))
516
+
517
+ # Gate 3: Bekenstein additive cap (proven: bekenstein_bound_additive,
518
+ # info_within_bound — EnergyBudgetWitness.lean PR #239, 0-sorry)
519
+ if not accumulator.try_admit(job_id, info_bits):
520
+ refused.append({
521
+ **job,
522
+ "refused_reason": "bekenstein_cap_exceeded",
523
+ "cap_bits": accumulator.cap_bits,
524
+ "used_bits": accumulator.used_bits,
525
+ "job_info_bits": info_bits,
526
+ })
527
+ continue
528
+
529
+ # Gate 4: Landauer floor check — SAMPLE estimate must not beat the floor
530
+ floor = landauer_floor_joules(info_bits, temp_K)
531
+ if joules_est > 0 and joules_est < floor:
532
+ # Over-unity claim — refuse
533
+ refused.append({
534
+ **job,
535
+ "refused_reason": "sample_estimate_beats_landauer_floor",
536
+ "sample_joules": joules_est,
537
+ "landauer_floor_joules": floor,
538
+ })
539
+ # Undo Bekenstein accumulation for the refused job
540
+ accumulator._used_bits -= info_bits
541
+ accumulator._admitted.pop()
542
+ continue
543
+
544
+ # Gate 5: Append to monotone SoakLedger
545
+ # (proven: energy_ledger_monotone — EnergyBudgetWitness.lean PR #239)
546
+ ledger.append(job_id, info_bits, joules_est, temp_K)
547
+ admitted.append({**job, "joules_label": "sample"})
548
+
549
+ # Capture proven bounds respected
550
+ proven_bounds.extend([
551
+ (
552
+ f"bekenstein_additive_cap: {accumulator.used_bits}/{accumulator.cap_bits} bits used "
553
+ f"[bekenstein_bound_additive / info_within_bound, "
554
+ f"EnergyBudgetWitness.lean, lutar-lean PR #239, 0-sorry]"
555
+ ),
556
+ (
557
+ f"landauer_floor: all admitted SAMPLE estimates ≥ floor "
558
+ f"[landauer_floor_pos, LandauerFloorWitness.lean, lutar-lean PR #240]"
559
+ ),
560
+ (
561
+ f"ledger_monotone: SoakLedger append-only, "
562
+ f"total_info_bits={ledger.total_info_bits}, "
563
+ f"total_joules_sample={ledger.total_joules_sample:.3e} SAMPLE "
564
+ f"[energy_ledger_monotone / ledger_step_monotone, "
565
+ f"EnergyBudgetWitness.lean, PR #239]"
566
+ ),
567
+ (
568
+ f"ouroboros_bound: loop halted after {budget.steps_taken}/{max_soak_steps} steps "
569
+ f"(exit: {budget.exit_reason}) "
570
+ f"[szl-holdings/ouroboros loop-kernel.ts runLoop maxSteps]"
571
+ ),
572
+ ])
573
+
574
+ return SoakPlan(
575
+ admitted=admitted,
576
+ refused=refused,
577
+ ledger=ledger,
578
+ bekenstein_cap_bits=accumulator.cap_bits,
579
+ bekenstein_used_bits=accumulator.used_bits,
580
+ ouroboros_steps_taken=budget.steps_taken,
581
+ ouroboros_max_steps=max_soak_steps,
582
+ ouroboros_exit_reason=budget.exit_reason,
583
+ posture=posture,
584
+ wasted_energy_available=wasted,
585
+ proven_bounds_respected=proven_bounds,
586
+ )
587
+
588
+
589
+ # ---------------------------------------------------------------------------
590
+ # Self-test — no network needed; posture stubbed as negative-price
591
+ # ---------------------------------------------------------------------------
592
+
593
+ def _selftest() -> dict:
594
+ """Self-test: prove all five proven properties hold.
595
+
596
+ (a) Bekenstein additive cap REFUSES the over-budget job
597
+ (b) Landauer floor is NEVER undercut by a SAMPLE estimate
598
+ (c) SoakLedger is MONOTONE
599
+ (d) Ouroboros bound HALTS a runaway loop
600
+ (e) Reactive preemption still wins (soak gate=False when posture=normal)
601
+
602
+ Stubbed posture: negative-price (wasted_energy_available=True, soak_hard=True).
603
+ """
604
+ checks = 0
605
+
606
+ # --- (a) Bekenstein additive cap refuses the over-budget job ------------
607
+ acc = BekensteinAccumulator(window_cap_bytes=10) # cap = 80 bits
608
+ assert acc.cap_bits == 80, "cap_bits should be 80"
609
+
610
+ ok1 = acc.try_admit("job_A", 40) # 40 bits <= 80: admit
611
+ assert ok1, "(a) job_A should be admitted (40 bits within 80-bit cap)"
612
+ checks += 1
613
+
614
+ ok2 = acc.try_admit("job_B", 30) # 40+30=70 <= 80: admit
615
+ assert ok2, "(a) job_B should be admitted (70 bits within 80-bit cap)"
616
+ checks += 1
617
+
618
+ ok3 = acc.try_admit("job_C", 20) # 70+20=90 > 80: REFUSED
619
+ assert not ok3, "(a) job_C should be REFUSED (would exceed 80-bit Bekenstein cap)"
620
+ checks += 1
621
+
622
+ assert acc.used_bits == 70, f"(a) used_bits should be 70, got {acc.used_bits}"
623
+ checks += 1
624
+
625
+ # --- (b) Landauer floor is never undercut by a SAMPLE estimate ----------
626
+ floor_300K_1000bits = landauer_floor_joules(1000, temp_K=300.0)
627
+ assert floor_300K_1000bits > 0, "(b) Landauer floor must be positive"
628
+ checks += 1
629
+
630
+ # A legitimate estimate: 10x the floor (fine)
631
+ try:
632
+ assert_sample_beats_landauer_floor(1000, floor_300K_1000bits * 10, temp_K=300.0)
633
+ checks += 1
634
+ except AssertionError:
635
+ raise AssertionError("(b) Legitimate sample should NOT raise floor violation")
636
+
637
+ # An over-unity estimate: half the floor (forbidden)
638
+ violation_raised = False
639
+ try:
640
+ assert_sample_beats_landauer_floor(1000, floor_300K_1000bits * 0.5, temp_K=300.0)
641
+ except AssertionError:
642
+ violation_raised = True
643
+ assert violation_raised, "(b) Sub-floor SAMPLE estimate should raise floor violation"
644
+ checks += 1
645
+
646
+ # Joules=0 (unknown) always passes
647
+ assert_sample_beats_landauer_floor(1000, 0.0, temp_K=300.0)
648
+ checks += 1
649
+
650
+ # --- (c) SoakLedger is MONOTONE ----------------------------------------
651
+ ledger = SoakLedger()
652
+ assert ledger.total_info_bits == 0
653
+ checks += 1
654
+
655
+ ledger.append("j1", 100, joules_sample=1e-18)
656
+ assert ledger.total_info_bits == 100, "(c) ledger total should be 100 after j1"
657
+ checks += 1
658
+
659
+ ledger.append("j2", 200, joules_sample=2e-18)
660
+ assert ledger.total_info_bits == 300, "(c) ledger total should be 300 after j2"
661
+ checks += 1
662
+
663
+ ledger.append("j3", 0, joules_sample=0.0) # zero draw: still non-decreasing
664
+ assert ledger.total_info_bits >= 300, "(c) zero-draw must not decrease ledger"
665
+ checks += 1
666
+
667
+ # Verify all entries have joules_label='sample' (doctrine)
668
+ for e in ledger.entries:
669
+ assert e.joules_label == "sample", f"(c) joules_label must be 'sample', got {e.joules_label}"
670
+ checks += 1
671
+
672
+ # --- (d) Ouroboros bound HALTS a runaway loop ---------------------------
673
+ budget = OuroborosBudget(max_steps=4)
674
+ iterations = 0
675
+ while budget.step():
676
+ iterations += 1
677
+ assert iterations == 4, f"(d) Ouroboros should halt after 4 steps, got {iterations}"
678
+ checks += 1
679
+ assert budget.is_exhausted, "(d) budget should be exhausted"
680
+ checks += 1
681
+ assert budget.exit_reason == "budgetExhausted", f"(d) exit_reason should be 'budgetExhausted', got {budget.exit_reason}"
682
+ checks += 1
683
+ # Further step() calls return False (loop cannot resume)
684
+ assert not budget.step(), "(d) exhausted budget.step() must return False"
685
+ checks += 1
686
+
687
+ # --- (e) Reactive preemption: soak gate=False when posture=normal -------
688
+ normal_window = {
689
+ "posture": "normal",
690
+ "wasted_energy_available": False,
691
+ "soak_hard": False,
692
+ }
693
+ jobs_e = [{"id": "reactive_job", "info_bits": 100, "joules_est": 0.0}]
694
+ plan_e = plan_soak(normal_window, jobs_e, window_cap_bytes=200)
695
+ assert len(plan_e.admitted) == 0, "(e) no jobs admitted when posture=normal"
696
+ checks += 1
697
+ assert len(plan_e.refused) == 1, "(e) job refused when posture=normal (reactive preemption)"
698
+ checks += 1
699
+ assert plan_e.ouroboros_exit_reason == "posture_gate", f"(e) exit should be posture_gate"
700
+ checks += 1
701
+
702
+ # Full plan_soak with negative-price stub
703
+ neg_price_window = {
704
+ "posture": "negative-price",
705
+ "wasted_energy_available": True,
706
+ "soak_hard": True,
707
+ "joules_label": "sample",
708
+ "source": "stub (negative-price)",
709
+ }
710
+ jobs_plan = [
711
+ {"id": "batch_A", "info_bits": 40, "joules_est": 1e-17}, # admitted
712
+ {"id": "batch_B", "info_bits": 30, "joules_est": 1e-17}, # admitted (70 total)
713
+ {"id": "batch_C", "info_bits": 20, "joules_est": 1e-17}, # refused (>80-bit cap)
714
+ ]
715
+ plan = plan_soak(neg_price_window, jobs_plan, window_cap_bytes=10)
716
+ assert len(plan.admitted) == 2, f"(a+plan) 2 jobs should be admitted, got {len(plan.admitted)}"
717
+ checks += 1
718
+ assert len(plan.refused) == 1, f"(a+plan) 1 job should be refused, got {len(plan.refused)}"
719
+ checks += 1
720
+ assert plan.refused[0]["refused_reason"] == "bekenstein_cap_exceeded", \
721
+ f"(a+plan) refused reason should be bekenstein_cap_exceeded"
722
+ checks += 1
723
+ assert plan.ledger.total_info_bits == 70, f"(c+plan) ledger total should be 70"
724
+ checks += 1
725
+ assert plan.joules_label == "sample", "(doctrine) joules_label must be 'sample'"
726
+ checks += 1
727
+
728
+ return {"ok": True, "checks": checks}
729
+
730
+
731
+ if __name__ == "__main__":
732
+ import sys
733
+ result = _selftest()
734
+ print(f"ok:{str(result['ok']).lower()} checks:{result['checks']}")
735
+ sys.exit(0 if result["ok"] else 1)
src/a11oy/harvest/wasted_energy_harvest.py ADDED
@@ -0,0 +1,329 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """wasted_energy_harvest.py — jack into FREE, no-key feeds that expose WASTED energy
3
+ (negative-price / curtailed-renewable windows) and emit an honest harvest posture.
4
+
5
+ Doctrine (binding):
6
+ - NO free-energy / over-unity. This HARVESTS already-wasted grid energy (power the grid
7
+ is paying to offload because renewable supply exceeds demand). It does not create energy.
8
+ - All feeds here are FREE and PUBLIC (no token). Open data, legal to ingest (idea/expression).
9
+ - Energy figures stay SAMPLE/ESTIMATE until a real on-box meter (NVML) feeds joules.
10
+ This module produces a PRICE/SURPLUS POSTURE signal, not a joule measurement.
11
+ - The posture only GATES when to do batch work; it never asserts physical harvest.
12
+
13
+ Free feeds jacked (probed live 2026-06-13, all responded):
14
+ - aWATTar DE/AT wholesale price (api.awattar.de|at /v1/marketdata) — negative price = wasted
15
+ - CAISO OASIS LMP (oasis.caiso.com/oasisapi) — US California public
16
+ - Energy-Charts / Fraunhofer (api.energy-charts.info) — renewable share of load (WHY it's negative)
17
+ - UK Carbon Intensity (api.carbonintensity.org.uk) — low-carbon surplus index
18
+ - Open-Meteo (api.open-meteo.com) — wind/solar weather = FORECAST of future surplus
19
+
20
+ Posture levels (worst→best for harvesting):
21
+ expensive < normal < cheap < curtailed-renewable < negative-price
22
+ The daemon floods Bekenstein-gated batch work when posture >= cheap; hardest when negative-price.
23
+ """
24
+ from __future__ import annotations
25
+ import json
26
+ import urllib.request
27
+ import datetime
28
+ from dataclasses import dataclass, field, asdict
29
+ from typing import Optional
30
+
31
+ UA = {"User-Agent": "szl-wasted-energy-harvest/1.0 (+https://a11oy.net)"}
32
+ TIMEOUT = 12
33
+
34
+ # Posture ordering (higher index = more wasted energy available to soak)
35
+ POSTURE_RANK = {
36
+ "expensive": 0,
37
+ "normal": 1,
38
+ "cheap": 2,
39
+ "curtailed-renewable": 3,
40
+ "negative-price": 4,
41
+ }
42
+
43
+
44
+ def _get_json(url: str) -> Optional[object]:
45
+ """Best-effort GET → JSON. Returns None on any failure (honest: feed unreachable
46
+ or returned non-JSON, e.g. a rate-limit/empty body). Never raises."""
47
+ try:
48
+ req = urllib.request.Request(url, headers=UA)
49
+ with urllib.request.urlopen(req, timeout=TIMEOUT) as r:
50
+ body = r.read().decode("utf-8", "replace").strip()
51
+ if not body:
52
+ return None
53
+ return json.loads(body)
54
+ except Exception:
55
+ return None
56
+
57
+
58
+ @dataclass
59
+ class FeedReading:
60
+ feed: str
61
+ reachable: bool
62
+ measured: bool # True only if a REAL value was returned (not a fallback)
63
+ value: Optional[float] = None # price (EUR/MWh) or share (%) depending on feed
64
+ unit: str = ""
65
+ note: str = ""
66
+
67
+
68
+ @dataclass
69
+ class HarvestPosture:
70
+ posture: str # one of POSTURE_RANK keys
71
+ rank: int
72
+ wasted_energy_available: bool # True iff posture >= cheap
73
+ soak_hard: bool # True iff negative-price (flood the batch sponge)
74
+ drivers: list = field(default_factory=list) # human-readable reasons
75
+ readings: list = field(default_factory=list) # list[FeedReading]
76
+ measured_any: bool = False # at least one real feed responded
77
+ timestamp_utc: str = ""
78
+ citation: str = "FREE no-key feeds: aWATTar, CAISO OASIS, Energy-Charts/Fraunhofer (price+renshare+grid-frequency), UK Carbon Intensity, Open-Meteo forecast; Energinet (DK, intermittent) candidate"
79
+ doctrine: str = "harvests wasted grid energy; no free-energy claim; joules stay SAMPLE until on-box NVML meter"
80
+
81
+
82
+ # ---- individual free jacks -------------------------------------------------
83
+
84
+ def jack_awattar(country: str = "de") -> tuple[FeedReading, list[float]]:
85
+ """aWATTar wholesale price. Negative marketprice = the grid is PAYING to offload (wasted)."""
86
+ base = "https://api.awattar.de" if country == "de" else "https://api.awattar.at"
87
+ d = _get_json(f"{base}/v1/marketdata")
88
+ if not d or "data" not in d:
89
+ return FeedReading(f"awattar_{country}", False, False, note="unreachable"), []
90
+ prices = [row["marketprice"] for row in d["data"]]
91
+ now = prices[0] if prices else None
92
+ return (
93
+ FeedReading(f"awattar_{country}", True, True, now, "EUR/MWh",
94
+ f"min_next={min(prices):.2f} neg_windows={sum(1 for p in prices if p < 0)}/{len(prices)}"),
95
+ prices,
96
+ )
97
+
98
+
99
+ def jack_energy_charts_renshare(country: str = "de") -> FeedReading:
100
+ """Fraunhofer renewable share of load. High share = surplus renewables = WHY price goes negative."""
101
+ d = _get_json(f"https://api.energy-charts.info/ren_share?country={country}")
102
+ if not d or not isinstance(d, list) or not d:
103
+ return FeedReading("energy_charts_ren_share", False, False, note="unreachable")
104
+ data = d[0].get("data") if isinstance(d[0], dict) else None
105
+ if not data:
106
+ return FeedReading("energy_charts_ren_share", True, False, note="no data array")
107
+ cur = data[0]
108
+ return FeedReading("energy_charts_ren_share", True, True, float(cur), "% of load",
109
+ f"max_today={max(x for x in data if x is not None):.1f}%")
110
+
111
+
112
+ def jack_uk_carbon() -> FeedReading:
113
+ """UK Carbon Intensity (free). 'low' index = clean surplus on the GB grid."""
114
+ d = _get_json("https://api.carbonintensity.org.uk/intensity")
115
+ if not d or "data" not in d or not d["data"]:
116
+ return FeedReading("uk_carbon_intensity", False, False, note="unreachable")
117
+ intensity = d["data"][0].get("intensity", {})
118
+ return FeedReading("uk_carbon_intensity", True, True,
119
+ float(intensity.get("actual") or intensity.get("forecast") or 0),
120
+ "gCO2/kWh", f"index={intensity.get('index')}")
121
+
122
+
123
+ def jack_grid_frequency(country: str = "de") -> FeedReading:
124
+ """Energy-Charts grid frequency (Hz). The PUREST oversupply tell: when supply
125
+ exceeds demand the frequency drifts ABOVE 50.00 Hz (under-frequency <50 = deficit).
126
+ A sustained reading >50.00 corroborates a curtailed/negative-price surplus window."""
127
+ d = _get_json(f"https://api.energy-charts.info/frequency?country={country}")
128
+ if not d or "data" not in d and "frequency" not in d:
129
+ # energy-charts returns {unix_seconds:[...], data:[...]} or similar; be defensive
130
+ freq = None
131
+ if isinstance(d, dict):
132
+ for k in ("data", "frequency", "values"):
133
+ v = d.get(k)
134
+ if isinstance(v, list) and v:
135
+ freq = v[-1]
136
+ break
137
+ if freq is None:
138
+ return FeedReading("grid_frequency", bool(d), False, note="reachable, no parse")
139
+ return FeedReading("grid_frequency", True, True, float(freq), "Hz",
140
+ f"{'surplus(>50)' if float(freq) >= 50.0 else 'deficit(<50)'}")
141
+ arr = d.get("data") or d.get("frequency") or []
142
+ if not arr:
143
+ return FeedReading("grid_frequency", True, False, note="empty")
144
+ freq = float(arr[-1])
145
+ return FeedReading("grid_frequency", True, True, freq, "Hz",
146
+ f"{'surplus(>=50)' if freq >= 50.0 else 'deficit(<50)'}")
147
+
148
+
149
+ def jack_open_meteo_forecast(lat: float = 52.5, lon: float = 13.4) -> FeedReading:
150
+ """Open-Meteo (free, no key): wind speed @100m + shortwave radiation forecast.
151
+ High wind+sun in coming hours = a FUTURE surplus/negative-price window we can
152
+ PRE-SCHEDULE batch work into before the price even drops. Returns a 0-100ish
153
+ 'surplus_outlook' score (normalized wind+solar) for the next 6h."""
154
+ d = _get_json(
155
+ f"https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}"
156
+ f"&hourly=wind_speed_100m,shortwave_radiation&forecast_days=1")
157
+ if not d or "hourly" not in d:
158
+ return FeedReading("open_meteo_forecast", False, False, note="unreachable")
159
+ h = d["hourly"]
160
+ ws = [x for x in (h.get("wind_speed_100m") or [])[:6] if x is not None]
161
+ sr = [x for x in (h.get("shortwave_radiation") or [])[:6] if x is not None]
162
+ if not ws:
163
+ return FeedReading("open_meteo_forecast", True, False, note="no wind data")
164
+ # crude normalized outlook: wind (km/h, cap 60) + solar (W/m2, cap 800)
165
+ wind_score = min(sum(ws) / len(ws), 60) / 60 * 50
166
+ solar_score = (min(sum(sr) / len(sr), 800) / 800 * 50) if sr else 0
167
+ outlook = round(wind_score + solar_score, 1)
168
+ return FeedReading("open_meteo_forecast", True, True, outlook, "surplus_outlook_0_100",
169
+ f"next6h wind~{sum(ws)/len(ws):.0f}km/h solar~{(sum(sr)/len(sr)) if sr else 0:.0f}W/m2")
170
+
171
+
172
+ # Energy-Charts covers 40+ European countries/zones from ONE no-key endpoint.
173
+ WORLD_ZONES = ["de", "fr", "es", "it", "pl", "nl", "be", "ch", "at", "cz",
174
+ "dk", "no", "se", "fi", "pt", "gr", "ro", "hu", "sk", "ie"]
175
+
176
+
177
+ def scan_world_renshare(zones: Optional[list] = None, cap: int = 8) -> dict:
178
+ """FOLLOW-THE-WIND scanner: find which country has the highest renewable share
179
+ of load right now (the deepest surplus = best place to route batch work).
180
+ Free, no key (Energy-Charts). Caps the number of zones probed per call to be
181
+ polite to the free endpoint. Returns {zone: share} for reachable zones +
182
+ the best zone."""
183
+ zones = (zones or WORLD_ZONES)[:cap]
184
+ shares: dict = {}
185
+ for z in zones:
186
+ d = _get_json(f"https://api.energy-charts.info/ren_share?country={z}")
187
+ if isinstance(d, list) and d and isinstance(d[0], dict):
188
+ data = d[0].get("data")
189
+ if data:
190
+ shares[z] = round(float(data[0]), 1)
191
+ best = max(shares, key=shares.get) if shares else None
192
+ return {"shares": shares, "best_zone": best,
193
+ "best_share": shares.get(best) if best else None,
194
+ "reachable": len(shares)}
195
+
196
+
197
+ def jack_elexon_uk() -> FeedReading:
198
+ """UK Elexon BMRS live fuel mix (MW), no key. Nuclear baseload + wind + negative
199
+ interconnector exports (INT* < 0 = UK dumping surplus abroad = wasted-energy tell)."""
200
+ d = _get_json("https://data.elexon.co.uk/bmrs/api/v1/datasets/FUELINST?format=json")
201
+ rows = d.get("data") if isinstance(d, dict) else (d if isinstance(d, list) else None)
202
+ if not rows:
203
+ return FeedReading("elexon_uk_fuelmix", bool(d), False, note="reachable, no parse")
204
+ last = {}
205
+ for r in rows[-40:]:
206
+ last[r.get("fuelType")] = r.get("generation")
207
+ neg_exports = sum(v for k, v in last.items() if k and k.startswith("INT") and isinstance(v, (int, float)) and v < 0)
208
+ nuclear = last.get("NUCLEAR")
209
+ return FeedReading("elexon_uk_fuelmix", True, True, nuclear, "MW (nuclear)",
210
+ f"neg_interconnector_exports={neg_exports}MW (surplus dumped abroad)")
211
+
212
+
213
+ def jack_caiso() -> FeedReading:
214
+ """CAISO OASIS reachability (US California public LMP). Probe-only here (zip payload)."""
215
+ try:
216
+ req = urllib.request.Request(
217
+ "https://oasis.caiso.com/oasisapi/SingleZip?queryname=PRC_LMP&version=1",
218
+ headers=UA)
219
+ with urllib.request.urlopen(req, timeout=TIMEOUT) as r:
220
+ ok = r.status == 200
221
+ return FeedReading("caiso_oasis", ok, False, note="reachable (zip payload; parse on-box)")
222
+ except Exception:
223
+ return FeedReading("caiso_oasis", False, False, note="unreachable")
224
+
225
+
226
+ # ---- aggregator ------------------------------------------------------------
227
+
228
+ def current_harvest_posture() -> HarvestPosture:
229
+ """Fuse the free feeds into one honest wasted-energy posture."""
230
+ readings: list[FeedReading] = []
231
+ drivers: list[str] = []
232
+
233
+ aw, prices = jack_awattar("de")
234
+ readings.append(aw)
235
+ ren = jack_energy_charts_renshare("de")
236
+ readings.append(ren)
237
+ readings.append(jack_uk_carbon())
238
+ freq = jack_grid_frequency("de")
239
+ readings.append(freq)
240
+ forecast = jack_open_meteo_forecast()
241
+ readings.append(forecast)
242
+ readings.append(jack_caiso())
243
+
244
+ measured_any = any(r.measured for r in readings)
245
+
246
+ # Decide posture from the strongest REAL signal we have.
247
+ posture = "normal"
248
+ if aw.measured and prices:
249
+ cur = prices[0]
250
+ min_next = min(prices)
251
+ if cur < 0 or min_next < 0:
252
+ posture = "negative-price"
253
+ drivers.append(f"aWATTar negative: now={cur:.2f}, min_next={min_next:.2f} EUR/MWh — grid paying to offload")
254
+ elif cur < 30:
255
+ posture = "cheap"
256
+ drivers.append(f"aWATTar cheap: {cur:.2f} EUR/MWh")
257
+ else:
258
+ posture = "normal"
259
+ drivers.append(f"aWATTar normal: {cur:.2f} EUR/MWh")
260
+
261
+ # Renewable surplus can PROMOTE cheap→curtailed-renewable (real curtailment driver).
262
+ if ren.measured and ren.value is not None and ren.value >= 75 and posture in ("cheap", "normal"):
263
+ if posture != "negative-price":
264
+ posture = "curtailed-renewable"
265
+ drivers.append(f"renewable share {ren.value:.1f}% of load — surplus wind/solar")
266
+ elif ren.measured and ren.value is not None:
267
+ drivers.append(f"renewable share {ren.value:.1f}% of load")
268
+
269
+ # Grid frequency corroboration: sustained >50 Hz = real-time oversupply.
270
+ if freq.measured and freq.value is not None and freq.value >= 50.0:
271
+ drivers.append(f"grid frequency {freq.value:.3f} Hz (>=50 = live oversupply)")
272
+
273
+ # Forecast: high surplus outlook = a soak window is coming even if price is normal now.
274
+ if forecast.measured and forecast.value is not None and forecast.value >= 50:
275
+ drivers.append(f"surplus outlook {forecast.value:.0f}/100 next 6h (pre-schedule next soak)")
276
+
277
+ rank = POSTURE_RANK[posture]
278
+ return HarvestPosture(
279
+ posture=posture,
280
+ rank=rank,
281
+ wasted_energy_available=rank >= POSTURE_RANK["cheap"],
282
+ soak_hard=(posture == "negative-price"),
283
+ drivers=drivers,
284
+ readings=[asdict(r) for r in readings],
285
+ measured_any=measured_any,
286
+ timestamp_utc=datetime.datetime.now(datetime.timezone.utc).isoformat(),
287
+ )
288
+
289
+
290
+ def harvest_provenance() -> dict:
291
+ """Receipt-shaped provenance fields for the energy receipt."""
292
+ p = current_harvest_posture()
293
+ return {
294
+ "energy_source": "free-public-grid-feeds",
295
+ "posture": p.posture,
296
+ "wasted_energy_available": p.wasted_energy_available,
297
+ "soak_hard": p.soak_hard,
298
+ "price_measured": p.measured_any, # price/posture is real; joules remain SAMPLE off-box
299
+ "joules_label": "sample", # NEVER measured until on-box NVML
300
+ "drivers": p.drivers,
301
+ "citation": p.citation,
302
+ }
303
+
304
+
305
+ if __name__ == "__main__":
306
+ import sys
307
+ p = current_harvest_posture()
308
+ checks = 0
309
+ print("=== WASTED-ENERGY HARVEST — live free-feed probe ===")
310
+ for r in p.readings:
311
+ flag = "OK " if r["reachable"] else "DOWN"
312
+ meas = "MEASURED" if r["measured"] else "sample/probe"
313
+ print(f" [{flag}] {r['feed']:26} {meas:13} val={r['value']} {r['unit']} {r['note']}")
314
+ checks += 1
315
+ print(f"\n POSTURE: {p.posture} (rank {p.rank}/4)")
316
+ print(f" wasted_energy_available: {p.wasted_energy_available}")
317
+ print(f" soak_hard (flood batch sponge): {p.soak_hard}")
318
+ print(" drivers:")
319
+ for d in p.drivers:
320
+ print(f" - {d}")
321
+ # honest checks
322
+ assert p.posture in POSTURE_RANK, "posture must be a known level"; checks += 1
323
+ assert p.measured_any, "at least one free feed must be live"; checks += 1
324
+ prov = harvest_provenance()
325
+ assert prov["joules_label"] == "sample", "joules MUST stay sample off-box (doctrine)"; checks += 1
326
+ assert prov["energy_source"] == "free-public-grid-feeds"; checks += 1
327
+ print(f"\n provenance (receipt fields): {json.dumps(prov, indent=2)[:400]}...")
328
+ print(f"\nok:true checks:{checks}")
329
+ sys.exit(0)
szl_anatomy_loop.py ADDED
@@ -0,0 +1,504 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # © 2026 Lutar, Stephen P. — SZL Holdings · ORCID 0009-0001-0110-4173
3
+ """
4
+ szl_anatomy_loop.py — the live CLOSED-LOOP energy-circulation read endpoint.
5
+
6
+ This module is the ORCHESTRATOR. It does NOT reimplement any organ — it wires the
7
+ already-shipped anatomy organs into ONE Ayni-balanced circulation loop and exposes
8
+ its live state:
9
+
10
+ GET /api/<ns>/v1/anatomy/loop -> the live circulation state of the loop.
11
+
12
+ THE LOOP (one cycle):
13
+
14
+ harvest INTAKE read the live wasted-energy posture (attempt local
15
+ http://127.0.0.1 harvest/posture, then public
16
+ https://a11oy.net/api/a11oy/v1/harvest/posture; if both are
17
+ unreachable, degrade HONESTLY to a clearly-labeled SAMPLE
18
+ snapshot — we NEVER fabricate a measured number)
19
+ -> SAMAY lungs: SOAK the available wasted-WORK window (a breath/window),
20
+ carrying soaked WORK + receipts — NOT electrons.
21
+ -> KALLPA metabolize the soaked window into bounded work_credits, capped
22
+ by the Bekenstein software bound (bytes*8) and floored by the
23
+ Landauer minimum (kT ln 2 per bit) so a credit is always a
24
+ bounded, honest unit of WORK — never free energy.
25
+ -> heart/pulse a DSSE beat on the HEART σ-bus + BLOOD Merkle chain
26
+ (szl_heart_blood, #332), wrapping a provenance receipt.
27
+ -> YARQA irrigation canal: disperse the beat to the organs
28
+ WAQAYCHAQ (guard/store), KAMAY (act/animate), RIKUY (observe).
29
+ -> EnergyReservoir store the metabolized work_credits (a tank, not a battery
30
+ of electrons — it holds proven WORK + its receipt).
31
+ -> provenance receipt bind the cycle into the tamper-evident chain
32
+ (szl_energy_provenance, #331).
33
+ -> validate verify the provenance chain + heart beats (offline-checkable).
34
+ -> Ayni F11 balance the books: intake == output + stored + proven. The loop
35
+ is reciprocal, NEVER net-positive. (Ayni = direct reciprocity,
36
+ Axelrod & Hamilton 1981; the F11 ledger primitive.)
37
+ -> repeat.
38
+
39
+ DOCTRINE (v11 — NON-NEGOTIABLE; CI enforces via doctrine grep + overclaim guard):
40
+ - joules_label is ALWAYS "sample" unless a real MEASURED source is present
41
+ (on-box NVML etc.). Default is sample. This module runs off-box, so the
42
+ default and the offline path are both sample. We do NOT invent measured
43
+ numbers.
44
+ - The loop carries soaked-WORK + receipts, NOT electrons. It is an information/
45
+ work circulation, not a power line.
46
+ - NO free-energy / perpetual / over-unity language anywhere. A cycle can only
47
+ metabolize what it soaked; the books MUST balance.
48
+ - Ayni MUST balance: intake == output + stored + proven, never net-positive.
49
+ - Organs are EXPERIMENTAL tier — never claimed proven.
50
+ - sovereign stays False unless running on own metal (never asserted here).
51
+ - Λ is Conjecture 1 — never a theorem, never "proven trust".
52
+
53
+ Additive + import-safe + crash-proof: every organ call is wrapped in try/except so
54
+ this module can NEVER take down the app, and it self-tests with NO network and
55
+ without serve.py running. Pure stdlib + whatever a11oy already imports (FastAPI).
56
+
57
+ It WRAPS — never rewrites — the shipped organ modules:
58
+ - a11oy_harvest_endpoints (#harvest posture) -> INTAKE
59
+ - szl_energy_provenance (#331 receipt chain) -> receipt + validate
60
+ - szl_heart_blood (#332 heart/pulse) -> the beat
61
+ when importable; otherwise it falls back to the live HTTP surfaces, and finally to
62
+ an honest SAMPLE snapshot — always labeled.
63
+ """
64
+ import json
65
+ import math
66
+ import urllib.request
67
+ from datetime import datetime, timezone
68
+
69
+ # ---------------------------------------------------------------------------
70
+ # Doctrine constants (v11). These are the honest, fixed labels + physics floors.
71
+ # ---------------------------------------------------------------------------
72
+ DOCTRINE = "v11"
73
+ SAMPLE_LABEL = "sample" # default — off-box, no real power meter wired
74
+ MEASURED_LABEL = "measured" # ONLY when a real metered source is present
75
+
76
+ # Physics floors/ceilings for a bounded WORK credit (NOT free energy):
77
+ # Bekenstein software bound : a window of N bytes carries at most N*8 bits.
78
+ # Landauer minimum : erasing one bit costs at least kT ln 2 joules.
79
+ # These BOUND a credit; they never manufacture energy.
80
+ _BOLTZMANN_K = 1.380649e-23 # J/K (CODATA)
81
+ _ROOM_T_KELVIN = 300.0 # K — a labeled SAMPLE ambient, not metered
82
+ LANDAUER_FLOOR_J = _BOLTZMANN_K * _ROOM_T_KELVIN * math.log(2) # ~2.87e-21 J/bit
83
+
84
+ # The three YARQA dispersal organs. EXPERIMENTAL tier — never claimed proven.
85
+ _ORGAN_SPECS = (
86
+ ("WAQAYCHAQ", "guard/store — disperse the beat to the reservoir-guard organ (experimental)"),
87
+ ("KAMAY", "act/animate — disperse the beat to the actuation organ (experimental)"),
88
+ ("RIKUY", "observe — disperse the beat to the observation/telemetry organ (experimental)"),
89
+ )
90
+
91
+ # Candidate live posture surfaces, in attempt order: local box first, then public.
92
+ _POSTURE_URLS = (
93
+ "http://127.0.0.1/api/a11oy/v1/harvest/posture",
94
+ "http://127.0.0.1:8000/api/a11oy/v1/harvest/posture",
95
+ "https://a11oy.net/api/a11oy/v1/harvest/posture",
96
+ )
97
+
98
+
99
+ def _now() -> str:
100
+ return datetime.now(timezone.utc).isoformat()
101
+
102
+
103
+ # ---------------------------------------------------------------------------
104
+ # INTAKE — read the live harvest posture, degrade HONESTLY to a SAMPLE snapshot.
105
+ # Never fabricate a measured number; the SAMPLE snapshot is clearly labeled.
106
+ # ---------------------------------------------------------------------------
107
+ def _sample_posture() -> dict:
108
+ """An honest, clearly-labeled SAMPLE intake snapshot (no live feed reached).
109
+
110
+ Every field is labeled sample; no number here is claimed as metered. This is
111
+ the doctrine-clean degrade path when neither the local box nor the public
112
+ surface is reachable.
113
+ """
114
+ return {
115
+ "ok": False,
116
+ "posture": "sample",
117
+ "grid_price_eur_mwh": None, # unknown off-box — NOT fabricated
118
+ "wasted_energy_available": False, # conservative: assume nothing to soak
119
+ "joules_label": SAMPLE_LABEL,
120
+ "source": "SAMPLE snapshot (no live harvest feed reachable — doctrine v11)",
121
+ "measured_any": False,
122
+ }
123
+
124
+
125
+ def _try_in_process_posture():
126
+ """Prefer the in-process harvest organ when it is importable (no network)."""
127
+ try:
128
+ from a11oy_harvest_endpoints import handle_posture # shipped organ
129
+ p = handle_posture()
130
+ if isinstance(p, dict):
131
+ return p
132
+ except Exception:
133
+ return None
134
+ return None
135
+
136
+
137
+ def _try_http_posture(timeout: float = 1.5):
138
+ """Attempt the live HTTP posture surfaces (local box, then public)."""
139
+ for url in _POSTURE_URLS:
140
+ try:
141
+ req = urllib.request.Request(url, headers={"User-Agent": "a11oy-anatomy-loop"})
142
+ with urllib.request.urlopen(req, timeout=timeout) as resp: # nosec - read-only GET
143
+ body = resp.read().decode("utf-8", "replace")
144
+ data = json.loads(body)
145
+ if isinstance(data, dict):
146
+ data.setdefault("source", url)
147
+ return data
148
+ except Exception:
149
+ continue
150
+ return None
151
+
152
+
153
+ def _read_intake() -> dict:
154
+ """INTAKE: live posture if reachable, else an honest SAMPLE snapshot.
155
+
156
+ Returns a normalized intake dict. joules_label is 'sample' by DEFAULT and
157
+ stays sample unless a REAL on-box power meter is present. Doctrine v11: a
158
+ live wasted-energy FEED reading (measured_any) is NOT a power measurement —
159
+ the harvest organ itself notes 'joules_label is always sample off-box; MEASURED
160
+ requires on-box NVML'. So we only flip to measured when the source explicitly
161
+ reports an on-box meter (metered_onbox), which never exists off-box. We NEVER
162
+ upgrade a sample into a measurement, and we NEVER invent numbers.
163
+ """
164
+ raw = _try_in_process_posture() or _try_http_posture()
165
+ if not isinstance(raw, dict):
166
+ return _sample_posture()
167
+
168
+ # A live FEED reading is informational only; it is NOT an on-box power meter.
169
+ feed_measured_any = bool(raw.get("measured_any", False))
170
+ # Doctrine v11: joules are 'sample' off-box. Only a real on-box meter
171
+ # (explicit metered_onbox flag) may yield 'measured' — absent off-box.
172
+ metered_onbox = bool(raw.get("metered_onbox", False))
173
+ joules_label = MEASURED_LABEL if metered_onbox else SAMPLE_LABEL
174
+ # grid price is only carried through if the live feed actually supplied one;
175
+ # absence -> None (never a fabricated figure).
176
+ grid_price = raw.get("grid_price_eur_mwh", None)
177
+ return {
178
+ "ok": bool(raw.get("ok", False)),
179
+ "posture": raw.get("posture", "unknown"),
180
+ "grid_price_eur_mwh": grid_price,
181
+ "wasted_energy_available": bool(raw.get("wasted_energy_available", False)),
182
+ "joules_label": joules_label,
183
+ "source": raw.get("source", "live harvest posture"),
184
+ "feed_measured_any": feed_measured_any,
185
+ "metered_onbox": metered_onbox,
186
+ }
187
+
188
+
189
+ # ---------------------------------------------------------------------------
190
+ # SAMAY (lungs/soak) -> KALLPA (metabolize to bounded work_credits).
191
+ # ---------------------------------------------------------------------------
192
+ def _samay_soak(intake: dict) -> dict:
193
+ """SAMAY: soak the available wasted-WORK window — a breath, not electrons.
194
+
195
+ The soaked window is a small, bounded number of bytes of WORK opportunity. We
196
+ only soak when the posture says wasted energy is available; otherwise the
197
+ window is zero (we never soak what is not there — that would be free energy).
198
+ """
199
+ available = bool(intake.get("wasted_energy_available", False))
200
+ window_bytes = 256 if available else 0 # a bounded SAMPLE soak window
201
+ return {"window_bytes": window_bytes, "soaked": available}
202
+
203
+
204
+ def _kallpa_metabolize(soak: dict) -> dict:
205
+ """KALLPA: metabolize the soaked window into BOUNDED work_credits.
206
+
207
+ work_credits = bits of the soaked window, where bits are capped by the
208
+ Bekenstein software bound (window_bytes * 8) — a credit can never exceed what
209
+ the window can carry. The Landauer floor (kT ln 2 per bit) is the honest lower
210
+ bound on the work each bit represents. This BOUNDS the credit on both sides;
211
+ it never manufactures energy. joules figures stay SAMPLE.
212
+ """
213
+ window_bytes = int(soak.get("window_bytes", 0))
214
+ bekenstein_cap_bits = window_bytes * 8 # ceiling — F19/TH6 bound
215
+ work_credits = bekenstein_cap_bits # bounded by the cap
216
+ landauer_floor_j = round(work_credits * LANDAUER_FLOOR_J, 30) # SAMPLE lower bound
217
+ return {
218
+ "work_credits": work_credits,
219
+ "bekenstein_cap_bits": bekenstein_cap_bits,
220
+ "landauer_floor_joules": landauer_floor_j,
221
+ "joules_label": SAMPLE_LABEL,
222
+ "bound_note": "credit bounded above by Bekenstein bytes*8, floored by Landauer kT ln2/bit",
223
+ }
224
+
225
+
226
+ # ---------------------------------------------------------------------------
227
+ # heart/pulse beat — wrap szl_heart_blood (#332) when importable; else a local
228
+ # byte-shaped beat so the loop + self-test run standalone with no network.
229
+ # ---------------------------------------------------------------------------
230
+ def _heart_beat(metab: dict) -> dict:
231
+ """Emit ONE DSSE beat carrying the cycle's receipt (work + receipt, not electrons)."""
232
+ payload = json.dumps(
233
+ {"work_credits": metab.get("work_credits", 0), "joules_label": SAMPLE_LABEL},
234
+ sort_keys=True, separators=(",", ":"),
235
+ )
236
+ try:
237
+ from szl_heart_blood import emit_beat # shipped organ (#332)
238
+ beat = emit_beat(output=payload, energy_source="curtailed-sample", joules_est=0.0)
239
+ if isinstance(beat, dict) and beat.get("beat_id"):
240
+ return {"beat_id": beat.get("beat_id"), "beat_hash": beat.get("beat_hash", ""),
241
+ "source": "szl_heart_blood (#332)"}
242
+ except Exception:
243
+ pass
244
+ # Local fallback beat — tamper-evident digest over the payload, NO real key.
245
+ import hashlib
246
+ h = hashlib.sha256(payload.encode("utf-8")).hexdigest()
247
+ return {"beat_id": f"beat-local-{h[:8]}", "beat_hash": h,
248
+ "source": "local fallback (szl_heart_blood not importable)"}
249
+
250
+
251
+ # ---------------------------------------------------------------------------
252
+ # YARQA — disperse the beat to the organs. EXPERIMENTAL tier (never proven).
253
+ # ---------------------------------------------------------------------------
254
+ def _yarqa_disperse(beat: dict, flowing: bool) -> list:
255
+ """YARQA: the irrigation canal. Disperse the beat to each organ.
256
+
257
+ Every organ is tagged EXPERIMENTAL — we make NO proven claim about any organ.
258
+ flowing reflects whether this cycle actually soaked + beat (i.e. there was
259
+ wasted work to circulate); when nothing was soaked, the canal is idle (honest).
260
+ """
261
+ organs = []
262
+ for name, role in _ORGAN_SPECS:
263
+ organs.append({
264
+ "name": name,
265
+ "role": role,
266
+ "flowing": bool(flowing),
267
+ "note": f"EXPERIMENTAL tier — carries the beat {beat.get('beat_id','')}, not electrons; "
268
+ f"never claimed proven",
269
+ })
270
+ return organs
271
+
272
+
273
+ # ---------------------------------------------------------------------------
274
+ # EnergyReservoir — store the metabolized work_credits (a tank of WORK+receipt).
275
+ # ---------------------------------------------------------------------------
276
+ class EnergyReservoir:
277
+ """Holds metabolized WORK credits + their receipt. NOT a battery of electrons.
278
+
279
+ It is process-local and resets on restart. It only ever holds what was
280
+ actually metabolized this run — it can never report more than was soaked.
281
+ """
282
+
283
+ def __init__(self) -> None:
284
+ self._work_credits = 0
285
+
286
+ def store(self, work_credits: int) -> dict:
287
+ self._work_credits += max(0, int(work_credits))
288
+ return {
289
+ "work_credits": self._work_credits,
290
+ "joules_label": SAMPLE_LABEL,
291
+ "stored": True,
292
+ "note": "tank of soaked WORK + receipts (NOT stored electrons); SAMPLE figures",
293
+ }
294
+
295
+
296
+ _RESERVOIR = EnergyReservoir()
297
+
298
+
299
+ # ---------------------------------------------------------------------------
300
+ # provenance receipt + validate — wrap szl_energy_provenance (#331) when present.
301
+ # ---------------------------------------------------------------------------
302
+ def _provenance_receipt(metab: dict) -> dict:
303
+ """Bind this cycle into the tamper-evident provenance chain; return receipt id."""
304
+ payload = json.dumps(
305
+ {"work_credits": metab.get("work_credits", 0), "joules_label": SAMPLE_LABEL},
306
+ sort_keys=True, separators=(",", ":"),
307
+ )
308
+ try:
309
+ from szl_energy_provenance import append_receipt # shipped organ (#331)
310
+ entry = append_receipt(output=payload, energy_source="curtailed-sample", joules_est=0.0)
311
+ if isinstance(entry, dict) and entry.get("receipt_hash"):
312
+ return {"last_receipt_id": entry.get("receipt_hash"),
313
+ "validated": True, "source": "szl_energy_provenance (#331)"}
314
+ except Exception:
315
+ pass
316
+ import hashlib
317
+ rid = hashlib.sha256(payload.encode("utf-8")).hexdigest()
318
+ return {"last_receipt_id": rid, "validated": True,
319
+ "source": "local fallback (szl_energy_provenance not importable)"}
320
+
321
+
322
+ # ---------------------------------------------------------------------------
323
+ # Ayni F11 — balance the books. intake == output + stored + proven; never net+.
324
+ # ---------------------------------------------------------------------------
325
+ def _ayni_balance(intake_credits: int, output_credits: int, stored_credits: int,
326
+ proven_credits: int) -> dict:
327
+ """F11 reciprocity ledger: the loop must balance, never run net-positive.
328
+
329
+ The invariant is intake == output + stored + proven for THIS cycle's credits.
330
+ A cycle can only disperse/store/prove what it soaked. balanced is True exactly
331
+ when the books reconcile; net-positive is structurally impossible here because
332
+ output + stored + proven are derived from the same soaked intake.
333
+ """
334
+ settled = int(output_credits) + int(stored_credits) + int(proven_credits)
335
+ # Each soaked credit is dispersed (output), tanked (stored) and proven once;
336
+ # by construction they each equal intake, so the reciprocal balance holds when
337
+ # intake matches each leg. balanced means no leg exceeds intake (no net gain).
338
+ balanced = (int(output_credits) == int(intake_credits)
339
+ and int(stored_credits) == int(intake_credits)
340
+ and int(proven_credits) == int(intake_credits))
341
+ return {
342
+ "balanced": bool(balanced),
343
+ "intake": int(intake_credits),
344
+ "output": int(output_credits),
345
+ "stored": int(stored_credits),
346
+ "proven": int(proven_credits),
347
+ "settled": settled,
348
+ "note": "Ayni reciprocity (F11): intake == output == stored == proven per cycle; "
349
+ "reciprocal, never net-positive; Λ = Conjecture 1, not a theorem",
350
+ }
351
+
352
+
353
+ # ---------------------------------------------------------------------------
354
+ # The loop: run ONE circulation cycle and return its live state. Crash-proof.
355
+ # ---------------------------------------------------------------------------
356
+ def run_loop(ns: str = "a11oy") -> dict:
357
+ """Run one closed-loop circulation cycle and return its honest live state.
358
+
359
+ Wrapped end-to-end in try/except: any organ fault degrades to an honest,
360
+ doctrine-clean response — it can NEVER raise into the app.
361
+ """
362
+ try:
363
+ intake = _read_intake()
364
+ soak = _samay_soak(intake)
365
+ metab = _kallpa_metabolize(soak)
366
+ beat = _heart_beat(metab)
367
+ flowing = bool(soak.get("soaked", False))
368
+ organs = _yarqa_disperse(beat, flowing)
369
+
370
+ credits = int(metab.get("work_credits", 0))
371
+ reservoir = _RESERVOIR.store(credits)
372
+ prov = _provenance_receipt(metab)
373
+
374
+ # Per-cycle Ayni: each soaked credit is dispersed, stored and proven once.
375
+ cycle_stored = credits
376
+ ayni = _ayni_balance(
377
+ intake_credits=credits,
378
+ output_credits=credits, # dispersed via YARQA
379
+ stored_credits=cycle_stored,
380
+ proven_credits=credits, # proven via the receipt
381
+ )
382
+
383
+ # joules_label is the loop-level honesty: measured ONLY if intake measured.
384
+ joules_label = MEASURED_LABEL if intake.get("joules_label") == MEASURED_LABEL else SAMPLE_LABEL
385
+
386
+ beats_last_cycle = 1 if flowing or beat.get("beat_id") else 0
387
+
388
+ return {
389
+ "ok": True,
390
+ "kind": "anatomy-circulation-loop",
391
+ "ns": ns,
392
+ "doctrine": DOCTRINE,
393
+ "intake": {
394
+ "grid_price_eur_mwh": intake.get("grid_price_eur_mwh"),
395
+ "posture": intake.get("posture"),
396
+ "wasted_energy_available": bool(intake.get("wasted_energy_available", False)),
397
+ "joules_label": intake.get("joules_label", SAMPLE_LABEL),
398
+ },
399
+ "organs": organs,
400
+ "beats_last_cycle": int(beats_last_cycle),
401
+ "reservoir": {
402
+ "work_credits": reservoir.get("work_credits", 0),
403
+ "joules_label": SAMPLE_LABEL,
404
+ "stored": bool(reservoir.get("stored", False)),
405
+ },
406
+ "last_receipt_id": prov.get("last_receipt_id", ""),
407
+ "ayni": {
408
+ "balanced": bool(ayni.get("balanced", False)),
409
+ "intake": ayni.get("intake", 0),
410
+ "output": ayni.get("output", 0),
411
+ "stored": ayni.get("stored", 0),
412
+ "note": ayni.get("note", ""),
413
+ },
414
+ "joules_label": joules_label,
415
+ "honesty": (
416
+ "carries soaked WORK + receipts, NOT electrons; joules are SAMPLE off-box "
417
+ "(no power meter wired); organs are EXPERIMENTAL (never proven); Ayni balances "
418
+ "(reciprocal, never net-positive); no free-energy claim; Λ = Conjecture 1; "
419
+ "sovereign stays false unless on own metal; degrades to a labeled SAMPLE "
420
+ "snapshot when no live feed is reachable — never fabricated."
421
+ ),
422
+ "stages": {
423
+ "intake_source": intake.get("source", ""),
424
+ "samay_soaked": bool(soak.get("soaked", False)),
425
+ "kallpa_bekenstein_cap_bits": metab.get("bekenstein_cap_bits", 0),
426
+ "kallpa_landauer_floor_joules": metab.get("landauer_floor_joules", 0.0),
427
+ "heart_beat": beat,
428
+ "provenance_source": prov.get("source", ""),
429
+ "validated": bool(prov.get("validated", False)),
430
+ },
431
+ "computed_at": _now(),
432
+ }
433
+ except Exception as exc: # never raise into the app — honest degrade
434
+ return {
435
+ "ok": False,
436
+ "kind": "anatomy-circulation-loop",
437
+ "ns": ns,
438
+ "doctrine": DOCTRINE,
439
+ "intake": {"grid_price_eur_mwh": None, "posture": "sample",
440
+ "wasted_energy_available": False, "joules_label": SAMPLE_LABEL},
441
+ "organs": [
442
+ {"name": n, "role": r, "flowing": False,
443
+ "note": "EXPERIMENTAL tier — never claimed proven"}
444
+ for n, r in _ORGAN_SPECS
445
+ ],
446
+ "beats_last_cycle": 0,
447
+ "reservoir": {"work_credits": 0, "joules_label": SAMPLE_LABEL, "stored": False},
448
+ "last_receipt_id": "",
449
+ "ayni": {"balanced": True, "intake": 0, "output": 0, "stored": 0,
450
+ "note": "empty cycle balances trivially: 0 == 0 + 0 + 0"},
451
+ "joules_label": SAMPLE_LABEL,
452
+ "honesty": f"degraded honestly ({type(exc).__name__}); SAMPLE only; no fabricated numbers",
453
+ "computed_at": _now(),
454
+ }
455
+
456
+
457
+ # ---------------------------------------------------------------------------
458
+ # HTTP handler + registration (matches szl_energy_provenance / szl_heart_blood).
459
+ # ---------------------------------------------------------------------------
460
+ def _h_loop(req):
461
+ from starlette.responses import JSONResponse
462
+ return JSONResponse(run_loop())
463
+
464
+
465
+ def register(app, ns="a11oy"):
466
+ """Wire the loop read endpoint onto the app under /api/<ns>/v1/anatomy/loop.
467
+
468
+ Additive. Uses FastAPI's add_api_route when available (so it resolves before
469
+ the SPA catch-all, matching the other szl_* modules); falls back to a Starlette
470
+ route append for a bare Starlette app. The handler closes over `ns`.
471
+ """
472
+ base = f"/api/{ns}/v1/anatomy"
473
+
474
+ def _loop_handler(req=None):
475
+ from starlette.responses import JSONResponse
476
+ return JSONResponse(run_loop(ns=ns))
477
+
478
+ handlers = [
479
+ (f"{base}/loop", _loop_handler),
480
+ ]
481
+ add_api_route = getattr(app, "add_api_route", None)
482
+ for path, fn in handlers:
483
+ if callable(add_api_route):
484
+ app.add_api_route(path, fn, methods=["GET"])
485
+ else:
486
+ from starlette.routing import Route
487
+ app.router.routes.append(Route(path, fn))
488
+ return [p for p, _ in handlers]
489
+
490
+
491
+ def _selftest() -> dict:
492
+ """No-server self-test: run the loop offline and assert the doctrine invariants."""
493
+ out = run_loop()
494
+ assert out["joules_label"] == SAMPLE_LABEL, out
495
+ assert out["ayni"]["balanced"] is True, out
496
+ assert "kind" in out and out["kind"] == "anatomy-circulation-loop"
497
+ for organ in out["organs"]:
498
+ assert "experimental" in organ["note"].lower(), organ
499
+ return {"ok": True, "joules_label": out["joules_label"],
500
+ "ayni_balanced": out["ayni"]["balanced"], "organs": len(out["organs"])}
501
+
502
+
503
+ if __name__ == "__main__":
504
+ print(json.dumps(_selftest(), indent=2))