app
Adom Project Manager
Public Made by Adomby adom
Adom Project Manager — the central hub for navigating wiki boards, molecules and libraries into Hydrogen: browse your wiki boards/projects (Personal / Organization / Public), view symbol + footprint + 3D and the per-EDA footprint layer stackup, explore the board and schematic in INTERACTIVE cross-probing viewers (click a net, hover a part for stock and price), add components to your KiCad/Altium libraries, and deliver self-contained KiCad projects. Built on adom-lbr. (Formerly Adom Project Manag
master
John Lauer
add tools/proxy-fidelity.py: proves a dashboard forwards ah's control API unchanged (byte-identical body, preserved non…
d0c073e
1mo ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
#!/usr/bin/env python3
"""proxy-fidelity — prove a dock dashboard forwards the control API FAITHFULLY.
The newbie-panel module calls two routes same-origin against your dashboard:
GET /updater/status (version gate)
POST /ai-threads/run-prompt (the send)
Your app forwards both to ah's control API. This checks that it forwards them
*unchanged*, which a "does it return 200" probe cannot see.
Three assertions, each against a real bug:
1. response bytes identical (sha256), awkward whitespace and nested
delivered_to included -> catches parse-then-re-emit
2. upstream's 207 comes back 207 -> catches a hand-built 200 {ok:true} wrapper
3. an unproxied path under the same prefix returns YOUR 404, not upstream's
418 -> catches blanket-proxying the control API
Why 1 matters even when the JSON "looks the same": the module splits
updater.current_version on dots and compares NUMERICALLY, and reads
delivered_to as an ARRAY. A re-serialisation that retypes either one changes
behaviour while every status probe stays green.
USAGE
# recommended: the script runs your app itself, in a throwaway HOME, so it
# never touches a real workspace
proxy-fidelity.py --app-cmd 'adom-project-manager --port $PORT'
# or point it at an already-running app (needs --home so the stub URL can be
# published where the app reads it; the file is backed up and restored)
proxy-fidelity.py --base http://127.0.0.1:8785 --home ~
# negative control: prove RED actually shows, using a deliberately
# re-serialising proxy. Run this once before you trust a green run.
proxy-fidelity.py --self-test
Exits non-zero if any assertion is RED, so it can sit in a build gate.
python3 stdlib only.
"""
import argparse, hashlib, json, os, random, shutil, signal, socket, subprocess, sys, time
import urllib.request, urllib.error
from http.server import BaseHTTPRequestHandler, HTTPServer
from threading import Thread
# Deliberately awkward: leading/trailing spaces that survive only if the bytes are
# untouched, a NUMERIC version, and delivered_to as an ARRAY.
STUB_BODY = (b'{"updater":{"current_version":"1.0.148","build":142},'
b'"delivered_to":["tab-7"],"spacing":" preserved "}')
TEAPOT = b'{"teapot":true}'
def free_port():
s = socket.socket()
s.bind(('127.0.0.1', 0))
p = s.getsockname()[1]
s.close()
return p
class Stub(BaseHTTPRequestHandler):
"""Stands in for ah's control API."""
def _send(self, code, body):
self.send_response(code)
self.send_header('Content-Type', 'application/json')
self.send_header('Content-Length', str(len(body)))
self.end_headers()
self.wfile.write(body)
def do_GET(self):
if self.path == '/updater/status':
self._send(200, STUB_BODY)
else:
# anything else is a teapot, so a blanket proxy is visible
self._send(418, TEAPOT)
def do_POST(self):
n = int(self.headers.get('Content-Length') or 0)
raw = self.rfile.read(n)
try:
echo = json.loads(raw or b'{}')
except Exception:
echo = {'unparsed': raw.decode('utf-8', 'replace')}
# 207 on purpose: a hand-built 200 wrapper cannot fake it
self._send(207, json.dumps({'echo': echo, 'delivered_to': ['tab-9']}).encode())
def log_message(self, *a):
pass
class BadProxy(BaseHTTPRequestHandler):
"""The NEGATIVE CONTROL: forwards correctly except it parses and re-emits,
which is exactly what most hand-written forwarding routes do."""
upstream = None
def do_GET(self):
if self.path == '/updater/status':
with urllib.request.urlopen(self.upstream + '/updater/status') as r:
data = json.loads(r.read())
out = json.dumps(data).encode() # <-- the bug, in one line
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.send_header('Content-Length', str(len(out)))
self.end_headers()
self.wfile.write(out)
else:
self.send_response(404); self.end_headers(); self.wfile.write(b'not found')
def do_POST(self):
n = int(self.headers.get('Content-Length') or 0)
raw = self.rfile.read(n)
req = urllib.request.Request(self.upstream + self.path, data=raw,
headers={'Content-Type': 'application/json'})
try:
with urllib.request.urlopen(req) as r:
body = r.read()
except urllib.error.HTTPError as e:
body = e.read()
out = json.dumps(json.loads(body)).encode()
self.send_response(200) # <-- collapses 207 to 200
self.send_header('Content-Type', 'application/json')
self.send_header('Content-Length', str(len(out)))
self.end_headers()
self.wfile.write(out)
def log_message(self, *a):
pass
def serve(handler, port):
srv = HTTPServer(('127.0.0.1', port), handler)
Thread(target=srv.serve_forever, daemon=True).start()
return srv
def http(method, url, body=None, timeout=20):
req = urllib.request.Request(url, method=method,
data=body.encode() if isinstance(body, str) else body,
headers={'Content-Type': 'application/json'} if body else {})
try:
with urllib.request.urlopen(req, timeout=timeout) as r:
return r.status, r.read()
except urllib.error.HTTPError as e:
return e.code, e.read()
except Exception as e:
return 0, str(e).encode()
def sha(b):
return hashlib.sha256(b).hexdigest()
def wait_ready(base, timeout=45):
end = time.time() + timeout
while time.time() < end:
code, _ = http('GET', base + '/', timeout=3)
if code:
return True
time.sleep(0.4)
return False
def run_checks(base, stub_port):
rows = []
code, body = http('GET', base + '/updater/status')
ok = (code == 200 and body == STUB_BODY)
rows.append(('GET /updater/status forwarded byte-for-byte', ok,
'status %s sha %s' % (code, sha(body)[:16]),
'status 200 sha %s' % sha(STUB_BODY)[:16],
None if ok else b'want: ' + STUB_BODY + b'\n got: ' + body))
sent = '{"new":true,"prompt":"proxy-fidelity probe","submit":false}'
code, body = http('POST', base + '/ai-threads/run-prompt', sent)
echoed = False
try:
echoed = json.loads(body).get('echo') == json.loads(sent)
except Exception:
pass
ok2 = (code == 207 and echoed)
rows.append(('POST /ai-threads/run-prompt: body intact, upstream 207 preserved', ok2,
'status %s, body echoed=%s' % (code, echoed), 'status 207, body echoed=True',
None if ok2 else b'got: ' + body))
code, body = http('GET', base + '/updater/definitely-not-a-real-path')
ok3 = (code != 418 and TEAPOT not in body)
rows.append(('unproxied path is NOT blanket-forwarded', ok3,
'status %s' % code, 'anything but upstream 418',
None if ok3 else b'upstream teapot leaked through: ' + body))
return rows
def report(rows):
print()
worst = 0
for what, ok, got, want, detail in rows:
print((' \033[32mPASS\033[0m ' if ok else ' \033[31mRED \033[0m ') + what)
print(' want %s' % want)
print(' got %s' % got)
if detail:
worst = 1
for line in detail.decode('utf-8', 'replace').splitlines():
print(' %s' % line)
print()
return worst
def main():
ap = argparse.ArgumentParser(description='Prove a dashboard forwards the control API unchanged.')
ap.add_argument('--base', help='base URL of an already-running dashboard, e.g. http://127.0.0.1:8785')
ap.add_argument('--home', help='the HOME that running app reads (its .adom/hydrogen-control-url is '
'briefly repointed at the stub, then restored)')
ap.add_argument('--app-cmd', help='command to start your app; $PORT and $HOME are set for you. '
'Safest: runs in a throwaway HOME.')
ap.add_argument('--self-test', action='store_true',
help='negative control: run a deliberately re-serialising proxy and show it go RED')
a = ap.parse_args()
stub_port = free_port()
serve(Stub, stub_port)
stub_url = 'http://127.0.0.1:%d' % stub_port
if a.self_test:
print('NEGATIVE CONTROL: a proxy that parses and re-emits, which should go RED.')
BadProxy.upstream = stub_url
bad_port = free_port()
serve(BadProxy, bad_port)
rows = run_checks('http://127.0.0.1:%d' % bad_port, stub_port)
rc = report(rows)
if all(r[1] for r in rows):
print('SELF-TEST FAILED: the negative control PASSED, so this harness proves nothing.')
return 2
print('Self-test OK: the harness detects a re-serialising proxy. A green run means something.')
return 0
proc = tmp_home = backup = ctrl = None
try:
if a.app_cmd:
tmp_home = os.path.join('/tmp', 'proxy-fidelity-%d' % random.randint(1000, 9999))
os.makedirs(os.path.join(tmp_home, '.adom'), exist_ok=True)
open(os.path.join(tmp_home, '.adom', 'hydrogen-control-url'), 'w').write(stub_url)
port = free_port()
env = dict(os.environ, HOME=tmp_home, PORT=str(port))
cmd = a.app_cmd.replace('$PORT', str(port)).replace('$HOME', tmp_home)
proc = subprocess.Popen(cmd, shell=True, env=env,
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
preexec_fn=os.setsid)
base = 'http://127.0.0.1:%d' % port
if not wait_ready(base):
print('RED: app did not answer on %s within 45s' % base)
return 1
elif a.base:
base = a.base.rstrip('/')
home = os.path.expanduser(a.home or os.environ.get('HOME', '~'))
ctrl = os.path.join(home, '.adom', 'hydrogen-control-url')
os.makedirs(os.path.dirname(ctrl), exist_ok=True)
if os.path.exists(ctrl):
backup = ctrl + '.proxy-fidelity.bak'
shutil.copy2(ctrl, backup)
open(ctrl, 'w').write(stub_url)
# Relies on the published rule that the control URL is read at REQUEST
# time. If your app caches it at startup, use --app-cmd instead.
else:
ap.error('give --app-cmd (recommended), --base, or --self-test')
print('stub control API on %s' % stub_url)
print('testing %s' % base)
rows = run_checks(base, stub_port)
rc = report(rows)
if rc:
print('One or more assertions are RED. Run --self-test once to confirm this harness')
print('can detect a known-bad proxy, then fix the route rather than the harness.')
return rc
finally:
if proc:
try:
os.killpg(os.getpgid(proc.pid), signal.SIGTERM)
except Exception:
pass
if backup:
shutil.move(backup, ctrl)
elif ctrl and os.path.exists(ctrl):
os.remove(ctrl)
if tmp_home:
shutil.rmtree(tmp_home, ignore_errors=True)
if __name__ == '__main__':
sys.exit(main())