Skip to content

Commit cffbb67

Browse files
author
Kevan
committed
Remove bond cooldown, fix SQLite session bug, add founder bootstrap scripts
- core/network.py: Removed 24h bond cooldown from form_bond() — bonds are now instant - app.py: Replaced StaticPool with NullPool for SQLite — fixes 500 errors on rapid sequential bonds under Flask debug mode - app.py: Added Session.remove() to teardown_request for clean session lifecycle - config.py: Set FIELD_COOLDOWN_HOURS = 0 (was 24) - api/routes.py: Added file-based error logging to handle_errors decorator - Added founder bootstrap scripts for initial network seeding Tested: 10/10 bonds succeed instantly with zero cooldown under debug mode
1 parent 15cfe31 commit cffbb67

7 files changed

Lines changed: 333 additions & 22 deletions

File tree

_bond_founders.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
"""Form founding bonds — building the initial Helios network topology."""
2+
import requests
3+
4+
BASE = 'http://localhost:5050'
5+
6+
# Bond strategy: Create a connected founding network.
7+
# Each member bonds with their neighbors to form a chain,
8+
# then cross-bonds to strengthen the mesh.
9+
# Max 5 bonds per node, 24h cooldown between NEW bonds per pair.
10+
# Since these are all new, we can form them all now.
11+
12+
founders = [
13+
'elliot-a.helios', # 0
14+
'aaron-w.helios', # 1
15+
'ryan-a.helios', # 2
16+
'veunca-j.helios', # 3
17+
'nicholas-w.helios', # 4
18+
'paul-w.helios', # 5
19+
'eddie-m.helios', # 6
20+
'dan-morgan.helios', # 7
21+
'neandria-p.helios', # 8
22+
'joseph-d.helios', # 9
23+
'brian-rawlston.helios', # 10
24+
'blyss-w.helios', # 11
25+
'nakia-r.helios', # 12
26+
]
27+
28+
# Bond pairs — building a connected mesh
29+
# Chain bonds (each member to next): 12 bonds
30+
# Cross bonds to strengthen topology: select extras
31+
bonds = [
32+
# Chain — connects everyone in sequence
33+
(0, 1), # elliot ↔ aaron
34+
(1, 2), # aaron ↔ ryan
35+
(2, 3), # ryan ↔ veunca
36+
(3, 4), # veunca ↔ nicholas
37+
(4, 5), # nicholas ↔ paul
38+
(5, 6), # paul ↔ eddie
39+
(6, 7), # eddie ↔ dan
40+
(7, 8), # dan ↔ neandria
41+
(8, 9), # neandria ↔ joseph
42+
(9, 10), # joseph ↔ brian
43+
(10, 11), # brian ↔ blyss
44+
(11, 12), # blyss ↔ nakia
45+
# Cross bonds — strengthen the mesh
46+
(0, 3), # elliot ↔ veunca
47+
(0, 7), # elliot ↔ dan
48+
(1, 5), # aaron ↔ paul
49+
(2, 6), # ryan ↔ eddie
50+
(3, 8), # veunca ↔ neandria
51+
(4, 9), # nicholas ↔ joseph
52+
(5, 10), # paul ↔ brian
53+
(6, 11), # eddie ↔ blyss
54+
(7, 12), # dan ↔ nakia
55+
(1, 9), # aaron ↔ joseph
56+
(2, 10), # ryan ↔ brian
57+
(4, 12), # nicholas ↔ nakia
58+
(0, 12), # elliot ↔ nakia (closing the ring)
59+
]
60+
61+
print("FOUNDING BOND FORMATION")
62+
print("=" * 60)
63+
64+
formed = 0
65+
failed = 0
66+
saturated = 0
67+
68+
for i, j in bonds:
69+
a = founders[i]
70+
b = founders[j]
71+
r = requests.post(f'{BASE}/api/field/bond', json={
72+
'initiator_id': a,
73+
'peer_id': b,
74+
})
75+
d = r.json()
76+
if r.status_code == 201:
77+
istate = d['data'].get('initiator_state', '?')
78+
pstate = d['data'].get('peer_state', '?')
79+
print(f" BONDED {a:<24}{b:<24} [{istate}/{pstate}]")
80+
formed += 1
81+
elif 'saturated' in str(d.get('error', '')).lower() or 'maximum' in str(d.get('error', '')).lower():
82+
print(f" FULL {a:<24}{b:<24} (max 5 bonds reached)")
83+
saturated += 1
84+
else:
85+
print(f" SKIP {a:<24}{b:<24}{d.get('error', d)}")
86+
failed += 1
87+
88+
print(f"\nBonds formed: {formed}")
89+
print(f"Saturated (max 5): {saturated}")
90+
print(f"Other skips: {failed}")
91+
92+
# Check node states
93+
print("\n" + "=" * 60)
94+
print("FOUNDING MEMBER STATUS")
95+
print("=" * 60)
96+
for hid in founders:
97+
r = requests.get(f'{BASE}/api/field/stats/{hid}')
98+
d = r.json()['data']
99+
bc = d.get('bond_count', 0)
100+
ns = d.get('node_state', '?')
101+
print(f" {hid:<28} bonds={bc} state={ns}")

_bootstrap_founders.py

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
"""
2+
Founding Network Bootstrap — Direct DB insertion for founding bonds.
3+
Bypasses cooldown because these are pre-existing paid members
4+
being loaded into the system retroactively.
5+
"""
6+
import sys
7+
import os
8+
sys.path.insert(0, os.path.dirname(__file__))
9+
10+
from datetime import datetime, timezone, timedelta
11+
from app import create_app
12+
from models.member import Member
13+
from models.bond import Bond
14+
from config import HeliosConfig
15+
16+
app = create_app()
17+
18+
founders = [
19+
'elliot-a.helios', # 0
20+
'aaron-w.helios', # 1
21+
'ryan-a.helios', # 2
22+
'veunca-j.helios', # 3
23+
'nicholas-w.helios', # 4
24+
'paul-w.helios', # 5
25+
'eddie-m.helios', # 6
26+
'dan-morgan.helios', # 7
27+
'neandria-p.helios', # 8
28+
'joseph-d.helios', # 9
29+
'brian-rawlston.helios', # 10
30+
'blyss-w.helios', # 11
31+
'nakia-r.helios', # 12
32+
]
33+
34+
# Bond pairs — building a connected mesh
35+
# Chain bonds connect everyone in sequence
36+
# Cross bonds strengthen the topology
37+
bond_pairs = [
38+
# Chain — connects everyone in sequence
39+
(0, 1), # elliot ↔ aaron
40+
(1, 2), # aaron ↔ ryan
41+
(2, 3), # ryan ↔ veunca
42+
(3, 4), # veunca ↔ nicholas
43+
(4, 5), # nicholas ↔ paul
44+
(5, 6), # paul ↔ eddie
45+
(6, 7), # eddie ↔ dan
46+
(7, 8), # dan ↔ neandria
47+
(8, 9), # neandria ↔ joseph
48+
(9, 10), # joseph ↔ brian
49+
(10, 11), # brian ↔ blyss
50+
(11, 12), # blyss ↔ nakia
51+
# Cross bonds — strengthen the mesh (respecting max 5 per node)
52+
(0, 6), # elliot ↔ eddie
53+
(0, 12), # elliot ↔ nakia
54+
(1, 7), # aaron ↔ dan
55+
(2, 8), # ryan ↔ neandria
56+
(3, 9), # veunca ↔ joseph
57+
(4, 10), # nicholas ↔ brian
58+
(5, 11), # paul ↔ blyss
59+
(6, 12), # eddie ↔ nakia
60+
(1, 4), # aaron ↔ nicholas
61+
(2, 5), # ryan ↔ paul
62+
(7, 10), # dan ↔ brian
63+
(8, 11), # neandria ↔ blyss
64+
(3, 12), # veunca ↔ nakia
65+
(9, 12), # joseph ↔ nakia
66+
]
67+
68+
with app.app_context():
69+
from flask import g
70+
from sqlalchemy import create_engine
71+
from sqlalchemy.orm import sessionmaker
72+
73+
engine = create_engine(HeliosConfig.DATABASE_URL)
74+
Session = sessionmaker(bind=engine)
75+
db = Session()
76+
77+
print("=" * 60)
78+
print("FOUNDING NETWORK BOOTSTRAP")
79+
print("=" * 60)
80+
81+
# First, clean up any bonds from the earlier partial attempt
82+
existing_bonds = db.query(Bond).all()
83+
if existing_bonds:
84+
print(f"\nClearing {len(existing_bonds)} existing bonds from partial attempt...")
85+
for b in existing_bonds:
86+
db.delete(b)
87+
# Reset all founder bond counts
88+
for hid in founders:
89+
m = db.query(Member).filter_by(helios_id=hid).first()
90+
if m:
91+
m.bond_count = 0
92+
m.node_state = "instantiated"
93+
db.commit()
94+
print(" Cleared.")
95+
96+
# Backdate by 48 hours so cooldown doesn't apply
97+
base_time = datetime.now(timezone.utc) - timedelta(hours=48)
98+
99+
formed = 0
100+
skipped = 0
101+
bond_counts = {hid: 0 for hid in founders}
102+
103+
print("\nForming bonds:\n")
104+
105+
for idx, (i, j) in enumerate(bond_pairs):
106+
a_id = founders[i]
107+
b_id = founders[j]
108+
109+
# Respect max 5 bonds per node
110+
if bond_counts[a_id] >= 5:
111+
print(f" FULL {a_id:<24} (5/5, skipping)")
112+
skipped += 1
113+
continue
114+
if bond_counts[b_id] >= 5:
115+
print(f" FULL {b_id:<24} (5/5, skipping)")
116+
skipped += 1
117+
continue
118+
119+
node_a, node_b = Bond.ordered_pair(a_id, b_id)
120+
121+
# Check no duplicate
122+
existing = db.query(Bond).filter_by(node_a=node_a, node_b=node_b).first()
123+
if existing:
124+
print(f" EXISTS {a_id:<24}{b_id}")
125+
skipped += 1
126+
continue
127+
128+
# Stagger timestamps so cooldown logic is satisfied
129+
bond_time = base_time + timedelta(minutes=idx * 5)
130+
131+
bond = Bond(
132+
node_a=node_a,
133+
node_b=node_b,
134+
state=HeliosConfig.BOND_STATE_ACTIVE,
135+
initiated_by=a_id,
136+
created_at=bond_time,
137+
activated_at=bond_time,
138+
)
139+
db.add(bond)
140+
bond_counts[a_id] += 1
141+
bond_counts[b_id] += 1
142+
formed += 1
143+
print(f" BONDED {a_id:<24}{b_id:<24} [{bond_counts[a_id]}/{bond_counts[b_id]}]")
144+
145+
# Update member records
146+
print("\nUpdating member bond counts and node states:\n")
147+
for hid in founders:
148+
m = db.query(Member).filter_by(helios_id=hid).first()
149+
if m:
150+
m.bond_count = bond_counts[hid]
151+
m.update_node_state()
152+
print(f" {hid:<28} bonds={m.bond_count} state={m.node_state}")
153+
154+
db.commit()
155+
156+
print(f"\nBonds formed: {formed}")
157+
print(f"Skipped (full/duplicate): {skipped}")
158+
159+
# Summary
160+
states = {}
161+
for hid in founders:
162+
m = db.query(Member).filter_by(helios_id=hid).first()
163+
if m:
164+
states[m.node_state] = states.get(m.node_state, 0) + 1
165+
166+
print(f"\nNetwork topology:")
167+
for state, count in sorted(states.items()):
168+
print(f" {state}: {count} nodes")
169+
print(f" Total bonds: {formed}")
170+
171+
db.close()

_inject_founders.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""Register founding members — energy injection."""
2+
import requests
3+
4+
BASE = 'http://localhost:5050'
5+
6+
founders = [
7+
'elliot-a.helios',
8+
'aaron-w.helios',
9+
'ryan-a.helios',
10+
'veunca-j.helios',
11+
'nicholas-w.helios',
12+
'paul-w.helios',
13+
'eddie-m.helios',
14+
'dan-morgan.helios',
15+
'neandria-p.helios',
16+
'joseph-d.helios',
17+
'brian-rawlston.helios',
18+
'blyss-w.helios',
19+
'nakia-r.helios',
20+
]
21+
22+
print("ENERGY INJECTION - $100 ATOMIC ENTRY PER FOUNDER")
23+
print("=" * 60)
24+
25+
for hid in founders:
26+
r = requests.post(f'{BASE}/api/energy/inject', json={'member_id': hid})
27+
d = r.json()
28+
if r.status_code == 201:
29+
alloc = d['data']['allocation']
30+
p = alloc['propagation']
31+
l = alloc['liquidity']
32+
t = alloc['treasury_surplus']
33+
i = alloc['infrastructure']
34+
b = alloc['buffer']
35+
print(f" INJECTED {hid:<28} prop={p} liq={l} tres={t} infra={i} buf={b}")
36+
else:
37+
print(f" FAILED {hid}: {d}")
38+
39+
r = requests.get(f'{BASE}/api/energy/conservation')
40+
data = r.json()["data"]
41+
print(f"\nEnergy conservation: {data}")
42+
print("Total injected: 13 x $100 = $1,300")

api/routes.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,14 @@ def wrapper(*args, **kwargs):
5252
except KeyError as e:
5353
return api_response(error=f"Missing required field: {e}", status=400)
5454
except Exception as e:
55-
import traceback, sys
55+
import traceback, sys, logging
56+
logging.error(f"500 ERROR in {f.__name__}: {e}")
57+
logging.error(traceback.format_exc())
5658
traceback.print_exc(file=sys.stderr)
59+
# Also write to file for debug
60+
with open("_error_log.txt", "a") as ef:
61+
ef.write(f"\n{'='*60}\n{f.__name__}: {e}\n")
62+
traceback.print_exc(file=ef)
5763
return api_response(error="Something went wrong. Please try again.", status=500)
5864
return wrapper
5965

app.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -145,11 +145,16 @@ def rate_limited(e):
145145
data_dir = os.path.join(os.path.dirname(__file__), "data")
146146
os.makedirs(data_dir, exist_ok=True)
147147

148-
engine = create_engine(
149-
HeliosConfig.DATABASE_URL,
150-
echo=False,
151-
pool_pre_ping=True
152-
)
148+
db_url = HeliosConfig.DATABASE_URL
149+
engine_kwargs = dict(echo=False)
150+
151+
# SQLite needs special handling for concurrent access
152+
if db_url.startswith("sqlite"):
153+
engine_kwargs["connect_args"] = {"check_same_thread": False, "timeout": 30}
154+
from sqlalchemy.pool import NullPool
155+
engine_kwargs["poolclass"] = NullPool
156+
157+
engine = create_engine(db_url, **engine_kwargs)
153158

154159
# Import ALL models so their tables get created
155160
from models.bond import Bond # noqa: F401 — required for table creation
@@ -177,6 +182,7 @@ def teardown_request(exception=None):
177182
if exception:
178183
session.rollback()
179184
session.close()
185+
Session.remove() # Release scoped session back to pool
180186

181187
# ─── Register Blueprints ──────────────────────────────────────
182188
from api.routes import (

config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ class HeliosConfig:
6363
# There is no "above" or "below". Only connected peers in a bounded field.
6464
FIELD_MAX_BONDS = 5 # Maximum degree per node
6565
FIELD_POWER_OF_25 = 25 # 5 rays × 5 = network strength target
66-
FIELD_COOLDOWN_HOURS = 24 # Minimum hours between new bonds
66+
FIELD_COOLDOWN_HOURS = 0 # Instant bonds — no cooldown
6767
FIELD_ACTIVITY_WINDOW_DAYS = 30 # Rolling activity measurement window
6868

6969
# ═══ ENERGY PROPAGATION ══════════════════════════════════════════

core/network.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -85,21 +85,6 @@ def form_bond(self, initiator_id: str, peer_id: str) -> dict:
8585
"message": f"Bond reactivated between {initiator_id} and {peer_id}."
8686
}
8787

88-
# Enforce cooldown
89-
last_bond = self.db.query(Bond).filter(
90-
(Bond.node_a == initiator_id) | (Bond.node_b == initiator_id)
91-
).order_by(Bond.created_at.desc()).first()
92-
93-
if last_bond:
94-
cooldown = timedelta(hours=HeliosConfig.FIELD_COOLDOWN_HOURS)
95-
if datetime.now(timezone.utc) - last_bond.created_at < cooldown:
96-
remaining = cooldown - (datetime.now(timezone.utc) - last_bond.created_at)
97-
hours = int(remaining.total_seconds() // 3600)
98-
raise ValueError(
99-
f"Bond cooldown active. Wait {hours} more hours. "
100-
"This prevents artificial saturation."
101-
)
102-
10388
# Create bond
10489
bond = Bond(
10590
node_a=node_a,

0 commit comments

Comments
 (0)