skill
Adom UI Design System
Public Made by Adomby adom
How the AI builds and tests interfaces people love to use in Adom. Ships as one installable skill that bundles focused skills (tooltips, floating-windows, toasts, responsive-ui, app-shell, ai-driven-apps, clickable-paths).
← Commit history
2.10.0: ship proxy-fidelity.py - prove your control-API routes forward byte-for-byte without touching John's machine
3 files changed
+319−5
assets/tools/proxy-fidelity.pyadded+290@@ -0,0 +1,290 @@+#!/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())
package.json+2−1@@ -1,6 +1,6 @@ { "slug": "adom-ui-design",- "version": "2.9.0",+ "version": "2.10.0", "type": "skill", "description": "The Adom UI Design System plus a bundle of focused UI skills (tooltips, floating-windows, toasts, responsive-ui, app-shell, ai-driven-apps, clickable-paths, dark-default, input-feedback, settings-layout). One install drops the whole set of skill files onto your container, plus shared icon assets (the settings cog) so every Adom app draws the same glyph.", "dependencies": {},@@ -13,6 +13,7 @@ "assets/icons/action-send.svg", "assets/icons/settings-cog-outline.svg", "assets/module/newbie-panel.js",+ "assets/tools/proxy-fidelity.py", "assets/tools/shot-webview.py", "install.sh", "page.json",
skills/newbie-panel/SKILL.md+27−4@@ -116,10 +116,25 @@ drops whitespace, and can turn a numeric version into a string. The module compa ARRAY, so a re-serialisation that changes either type changes behaviour while every "does it return 200" probe still passes. -You can prove fidelity without John's machine, and in about forty lines: stand up a stub-control API returning a known body, point a throwaway `HOME`'s `hydrogen-control-url` at it,-and **compare the sha256 of your route's response to the stub's**. project-manager's version-checks three things, and all three are failures a 200-probe sails past:+**The harness ships in this pack: `assets/tools/proxy-fidelity.py`.** python3 stdlib only, no+dependencies.++```sh+python3 proxy-fidelity.py --app-cmd "your-server --port $PORT" # the mode to use+python3 proxy-fidelity.py --self-test # watch it go red first+```++`--app-cmd` creates a throwaway `HOME`, publishes a stub control URL into it, starts your app+with `$PORT` and `$HOME`, runs the assertions and tears it all down. **It never touches a real+workspace**, so it is safe in a build gate. There is a `--base --home` mode for an+already-running app, but prefer `--app-cmd`: the `--base` mode works only because the control+URL is read at REQUEST time, so **an app that caches the URL at startup silently tests+nothing.** Exits non-zero on any RED.++**It cannot create a thread in John's list.** The POST assertion sends `submit:false` and only+ever to its own stub, never to a real control API.++It checks three things, and all three are failures a 200-probe sails past: | assertion | the bug it catches | |---|---|@@ -129,6 +144,14 @@ checks three things, and all three are failures a 200-probe sails past: Run this before spending a slot. A round trip through his box tells you less and costs more. +**Run `--self-test` first, every time.** Its negative control is a proxy whose only sin is one+line of parse-then-re-emit, and it goes RED on two of the three while the third stays GREEN.+That third staying green is the point: **the assertions are independent, so a run tells you+WHICH bug you have** rather than only that something is wrong. The diff it prints on the+byte-for-byte failure shows the damage is purely `{"updater":{` becoming `{"updater": {` —+JSON that is equal, bytes that are not, and behaviour that can differ. The self-test exits 2+if the negative control somehow passes, i.e. if the harness itself is broken.+ **READ THE CONTROL PORT AT REQUEST TIME. IT IS DYNAMIC PER ah LAUNCH.** Read `~/.adom/hydrogen-control-url`; do not hardcode 47084 or any port you once observed. A pinned port works until his next ah restart and then fails silently, which is the worst failure shape