feat: site WIP — downloads, explorer, hasher, TestFlight enroll

Commit restored mcp.verae.ai site pages (app, register, macos, windows,
explorer), hasher/download artifacts, index/sample/hasher updates, ledger
and global_client improvements, NATS stamp helper, and TestFlight enroll
service. Ignore debug-trace/ and editor backups. Standard Readme for
download and enroll docs.
This commit is contained in:
George Lambert 2026-09-29 11:18:59 -04:00
commit 1ca834900e
72 changed files with 5137 additions and 51 deletions

6
.gitignore vendored
View file

@ -21,3 +21,9 @@ build/
.worktrees/ .worktrees/
.superpowers/ .superpowers/
# Local stamp debug-trace runs and editor backups
debug-trace/
*.bak
*.bak-*
index.html.bak*

View file

@ -34,7 +34,7 @@ Scope rank: local < shared < global.
| `org_id` | empty | Filter when set | | `org_id` | empty | Filter when set |
| `want_scope` | empty | Drop receipts below this rank | | `want_scope` | empty | Drop receipts below this rank |
Returns `{ "receipts": [ ... ] }` from the local file. Global-only certificates are not listed. Returns `{ "receipts": [ ... ] }`. `want_scope=global` queries VeraeTime `GET /api/hash/{sha256}` (Carfax). Empty `want_scope` merges the local file ledger with the central history.
## login ## login

View file

@ -24,7 +24,7 @@ This process is an MCP server (Model Context Protocol). Clients are assistants a
## Global history ## Global history
`hash_report` reads the local file only. VeraeTime is not queried for a digest history. A global stamp can verify by certificate id and still be absent from `hash_report`. An empty list means "not in the local file," not "VeraeTime has no record." `hash_report` with `want_scope=global` calls VeraeTime `GET /api/hash/{digest}` (Carfax). Without that scope it reads the local file and also merges central history when available. An empty list means no receipts were found for that digest in the queried ledgers.
## Credentials ## Credentials

158
scripts/nats_stamp.py Normal file
View file

@ -0,0 +1,158 @@
"""Stamp digests over core NATS. Subscribe to verae.ts.completed before requesting.
No status poll and no retry sleep. A known digest returns its certificate on the
request reply. A new digest returns a jobId, then verae.ts.completed carries the
certificate.
Usage:
python scripts/nats_stamp.py <64-hex-or-text> [...]
VERAE_NATS_URL=nats://127.0.0.1:4222 python scripts/nats_stamp.py "Hello World!"
"""
from __future__ import annotations
import hashlib
import json
import os
import socket
import sys
import time
SUBJECT_REQUEST = "verae.ts.request"
SUBJECT_COMPLETED = "verae.ts.completed"
def digest_of(text: str) -> str:
raw = text.strip().lower()
if raw.startswith("sha256:"):
raw = raw[7:]
if len(raw) == 64 and all(c in "0123456789abcdef" for c in raw):
return raw
return hashlib.sha256(text.encode()).hexdigest()
class Conn:
def __init__(self, url: str) -> None:
host_port = url.split("://", 1)[-1]
host, port_s = host_port.rsplit(":", 1)
self.sock = socket.create_connection((host, int(port_s)), timeout=5)
self.buf = b""
info = self._read_line()
if not info.startswith("INFO "):
raise RuntimeError(info)
self.queued: dict[str, list[dict]] = {}
self._send(
'CONNECT {"verbose":false,"pedantic":false,"lang":"verae-stamp","version":"1","protocol":1}\r\n'
"PING\r\n"
)
self._expect_pong()
def _send(self, text: str) -> None:
self.sock.sendall(text.encode())
def _read_line(self) -> str:
while b"\r\n" not in self.buf:
chunk = self.sock.recv(65536)
if not chunk:
raise RuntimeError("nats closed")
self.buf += chunk
line, self.buf = self.buf.split(b"\r\n", 1)
return line.decode()
def _expect_pong(self) -> None:
while True:
line = self._read_line()
if line == "PONG" or line.startswith("+OK"):
if line == "PONG":
return
continue
if line.startswith("-ERR"):
raise RuntimeError(line)
def subscribe(self, subject: str, sid: str) -> None:
self._send(f"SUB {subject} {sid}\r\n")
def request(self, subject: str, payload: dict, inbox: str, sid: str) -> dict:
raw = json.dumps(payload).encode()
self.subscribe(inbox, sid)
self._send(f"PUB {subject} {inbox} {len(raw)}\r\n")
self._send(raw.decode() + "\r\n")
return self._wait_sid(sid, 30.0)
def _read_msg(self) -> tuple[str, dict]:
while True:
line = self._read_line()
if line.startswith("PING"):
self._send("PONG\r\n")
continue
if line.startswith("PONG") or line.startswith("+OK"):
continue
if not line.startswith("MSG "):
continue
parts = line.split()
size = int(parts[-1])
msg_sid = parts[2]
while len(self.buf) < size + 2:
chunk = self.sock.recv(65536)
if not chunk:
raise RuntimeError("nats closed")
self.buf += chunk
body = self.buf[:size]
self.buf = self.buf[size + 2 :]
return msg_sid, json.loads(body.decode())
def _wait_sid(self, sid: str, timeout: float) -> dict:
queued = self.queued.get(sid) or []
if queued:
return queued.pop(0)
deadline = time.time() + timeout
while time.time() < deadline:
self.sock.settimeout(max(0.1, deadline - time.time()))
msg_sid, body = self._read_msg()
if msg_sid == sid:
return body
self.queued.setdefault(msg_sid, []).append(body)
raise TimeoutError(sid)
def next_completed(self, timeout: float) -> dict:
return self._wait_sid("done", timeout)
def main(argv: list[str]) -> int:
if not argv:
print("usage: nats_stamp.py <text-or-digest> [...]", file=sys.stderr)
return 2
url = os.environ.get("VERAE_NATS_URL", "nats://127.0.0.1:4222")
digests = [digest_of(item) for item in argv]
conn = Conn(url)
conn.subscribe(SUBJECT_COMPLETED, "done")
pending: dict[str, str] = {}
receipts: dict[str, str] = {}
t0 = time.perf_counter()
for i, digest in enumerate(digests):
started = time.perf_counter()
body = conn.request(SUBJECT_REQUEST, {"hash": digest}, f"_INBOX.{i}", f"inbox{i}")
if body.get("error"):
print(json.dumps({"hash": digest, "error": body["error"]}))
return 1
elapsed = (time.perf_counter() - started) * 1000
if body.get("receipt_id"):
receipts[digest] = body["receipt_id"]
print(json.dumps({"hash": digest, "receipt_id": body["receipt_id"], "existing": body.get("existing"), "ms": round(elapsed, 3)}))
else:
pending[body["jobId"]] = digest
print(json.dumps({"hash": digest, "jobId": body.get("jobId"), "existing": False, "ms": round(elapsed, 3)}))
while pending:
msg = conn.next_completed(30)
job_id = msg.get("jobId")
digest = pending.pop(job_id, None)
if digest is None:
continue
receipts[digest] = msg.get("receipt_id") or ""
print(json.dumps({"hash": digest, "jobId": job_id, "receipt_id": receipts[digest], "via": "verae.ts.completed"}))
print(json.dumps({"n": len(digests), "receipts": len(receipts), "wall_ms": round((time.perf_counter() - t0) * 1000, 3)}))
return 0
if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))

View file

@ -0,0 +1,30 @@
# TestFlight enroll service
HTTP helper that adds an Apple ID email to the Verae Timestamping TestFlight group via App Store Connect.
## Install
```sh
# On ns1: secrets at /SSD2/secrets/asc/ (AuthKey_*.p8 + testflight-enroll.env, mode 600)
# Service unit: verae-testflight-enroll.service (venv under /SSD2/services/verae-testflight-enroll)
cd scripts/testflight-enroll
python -m venv .venv && .venv/bin/pip install -r requirements.txt 2>/dev/null || true
```
Public invite endpoint (behind Caddy): `POST https://mcp.verae.ai/api/testflight-invite` with JSON `{"email":"you@apple-id.example","note":"..."}`.
## Usage
```sh
sudo -u marchon bash -lc 'set -a; source /SSD2/secrets/asc/testflight-enroll.env; set +a; /SSD2/services/verae-testflight-enroll/venv/bin/python enroll_cli.py you@example.com'
```
Env keys: `ASC_KEY_ID`, `ASC_ISSUER_ID`, `ASC_KEY_PATH`, optional app/group ids. Do not commit `.p8` keys or env files.
## Contributing
Questions: marchon@gmail.com or the Verae research workspace. PRs coordinated in-workspace first.
## License
UNLICENSED © George Lambert

View file

@ -0,0 +1,29 @@
#!/usr/bin/env python3
"""CLI for Grok Bot / operators: enroll one Apple ID into external TestFlight."""
from __future__ import annotations
import argparse
import json
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from enroll_lib import enroll
def main():
p = argparse.ArgumentParser(description="Enroll macOS TestFlight external tester via ASC API")
p.add_argument("email", help="Apple ID email")
p.add_argument("--note", default="", help="optional note (ignored by ASC; logged locally only if you redirect)")
args = p.parse_args()
try:
result = enroll(args.email, note=args.note)
except Exception as e:
print(json.dumps({"ok": False, "error": str(e)}))
sys.exit(1)
print(json.dumps(result))
sys.exit(0 if result.get("ok") else 2)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,225 @@
#!/usr/bin/env python3
"""App Store Connect TestFlight external-tester enrollment helpers.
Secrets come from env (never hard-coded):
ASC_KEY_ID, ASC_ISSUER_ID, ASC_KEY_PATH
Optional:
ASC_APP_ID (default 6817018890)
ASC_BETA_GROUP_ID (default fcfa572e-6494-4781-97b2-728f73e5b3ae)
"""
from __future__ import annotations
import json
import os
import re
import time
import urllib.error
import urllib.parse
import urllib.request
from typing import Any
EMAIL_RE = re.compile(r"^[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}$")
DEFAULT_APP_ID = "6817018890"
DEFAULT_GROUP_ID = "fcfa572e-6494-4781-97b2-728f73e5b3ae"
ASC_BASE = "https://api.appstoreconnect.apple.com"
def validate_email(email: str) -> str:
email = (email or "").strip()
if not email or len(email) > 254 or not EMAIL_RE.match(email):
raise ValueError("invalid_email")
return email.lower()
def _require_env() -> tuple[str, str, str]:
key_id = os.environ.get("ASC_KEY_ID", "").strip()
issuer = os.environ.get("ASC_ISSUER_ID", "").strip()
path = os.environ.get("ASC_KEY_PATH", "").strip()
if not key_id or not issuer or not path:
raise RuntimeError("missing_asc_env")
if not os.path.isfile(path):
raise RuntimeError("missing_asc_key_file")
return key_id, issuer, path
def make_token() -> str:
"""ES256 JWT for App Store Connect API (max ~20 min)."""
import jwt # PyJWT
key_id, issuer, path = _require_env()
with open(path, "r", encoding="utf-8") as f:
private_key = f.read()
now = int(time.time())
payload = {
"iss": issuer,
"iat": now,
"exp": now + 15 * 60,
"aud": "appstoreconnect-v1",
}
return jwt.encode(
payload,
private_key,
algorithm="ES256",
headers={"kid": key_id, "typ": "JWT"},
)
def _request(method: str, path: str, body: dict | None = None, token: str | None = None) -> tuple[int, Any]:
token = token or make_token()
url = path if path.startswith("http") else ASC_BASE + path
data = None if body is None else json.dumps(body).encode("utf-8")
req = urllib.request.Request(url, data=data, method=method)
req.add_header("Authorization", "Bearer " + token)
req.add_header("Accept", "application/json")
if body is not None:
req.add_header("Content-Type", "application/json")
try:
with urllib.request.urlopen(req, timeout=45) as resp:
raw = resp.read().decode("utf-8") or "{}"
return resp.status, json.loads(raw) if raw.strip() else {}
except urllib.error.HTTPError as e:
raw = e.read().decode("utf-8", errors="replace")
try:
parsed = json.loads(raw) if raw.strip() else {}
except json.JSONDecodeError:
parsed = {"raw": raw}
return e.code, parsed
def find_tester_by_email(email: str, token: str) -> dict | None:
q = urllib.parse.urlencode({
"filter[email]": email,
"filter[apps]": os.environ.get("ASC_APP_ID", DEFAULT_APP_ID),
"limit": "5",
})
status, data = _request("GET", f"/v1/betaTesters?{q}", token=token)
if status != 200:
return None
items = data.get("data") or []
return items[0] if items else None
def create_tester(email: str, token: str, first: str = "Verae", last: str = "Tester") -> tuple[int, Any]:
app_id = os.environ.get("ASC_APP_ID", DEFAULT_APP_ID)
group_id = os.environ.get("ASC_BETA_GROUP_ID", DEFAULT_GROUP_ID)
body = {
"data": {
"type": "betaTesters",
"attributes": {
"email": email,
"firstName": first[:30] or "Verae",
"lastName": last[:30] or "Tester",
},
"relationships": {
"betaGroups": {
"data": [{"type": "betaGroups", "id": group_id}]
},
"apps": {
"data": [{"type": "apps", "id": app_id}]
},
},
}
}
return _request("POST", "/v1/betaTesters", body=body, token=token)
def add_tester_to_group(tester_id: str, token: str) -> tuple[int, Any]:
group_id = os.environ.get("ASC_BETA_GROUP_ID", DEFAULT_GROUP_ID)
body = {
"data": [{"type": "betaTesters", "id": tester_id}]
}
return _request(
"POST",
f"/v1/betaGroups/{group_id}/relationships/betaTesters",
body=body,
token=token,
)
def resend_invitation(tester_id: str, token: str) -> tuple[int, Any]:
# App Store Connect: POST /v1/betaTesterInvitations
app_id = os.environ.get("ASC_APP_ID", DEFAULT_APP_ID)
body = {
"data": {
"type": "betaTesterInvitations",
"relationships": {
"betaTester": {"data": {"type": "betaTesters", "id": tester_id}},
"app": {"data": {"type": "apps", "id": app_id}},
},
}
}
return _request("POST", "/v1/betaTesterInvitations", body=body, token=token)
def enroll(email: str, note: str = "") -> dict:
"""Validate + create/add external tester + resend invite. Returns public result dict."""
email = validate_email(email)
token = make_token()
existing = find_tester_by_email(email, token)
created = False
tester_id = None
steps: list[str] = []
if existing:
tester_id = existing["id"]
steps.append("found_existing")
st, _ = add_tester_to_group(tester_id, token)
# 204 success; 409 often already in group
if st in (204, 409):
steps.append("group_ok" if st == 204 else "already_in_group")
else:
steps.append(f"group_status_{st}")
else:
st, data = create_tester(email, token)
if st in (201, 200):
created = True
tester_id = (data.get("data") or {}).get("id")
steps.append("created")
elif st == 409:
# race / already exists — look up again
steps.append("create_conflict")
existing = find_tester_by_email(email, token)
if not existing:
return {
"ok": False,
"error": "tester_conflict",
"message": "Apple reported a conflict creating this tester. Try again or email marchon@gmail.com.",
"steps": steps,
}
tester_id = existing["id"]
st2, _ = add_tester_to_group(tester_id, token)
steps.append("group_ok" if st2 in (204, 409) else f"group_status_{st2}")
else:
detail = ""
errs = (data or {}).get("errors") or []
if errs:
detail = errs[0].get("detail") or errs[0].get("title") or ""
return {
"ok": False,
"error": "asc_create_failed",
"message": detail or f"App Store Connect create failed ({st}).",
"steps": steps,
"status": st,
}
if tester_id:
rst, _ = resend_invitation(tester_id, token)
if rst in (201, 200, 204):
steps.append("invite_resent")
else:
steps.append(f"invite_status_{rst}")
return {
"ok": True,
"email": email,
"created": created,
"tester_id": tester_id,
"steps": steps,
"join_url": "https://testflight.apple.com/join/dsHsyx2Z",
"beta_review_note": (
"External builds may still be WAITING_FOR_BETA_REVIEW. "
"Enrollment succeeded; you may not see the build until Apple approves the beta."
),
"note_received": bool((note or "").strip()),
}

View file

@ -0,0 +1,115 @@
#!/usr/bin/env python3
"""Minimal HTTP service: POST /api/testflight-invite → ASC external TestFlight enroll.
Bind 127.0.0.1 only; Caddy reverse_proxies https://mcp.verae.ai/api/testflight-invite
"""
from __future__ import annotations
import json
import os
import sys
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
# Allow running from installed dir
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from enroll_lib import enroll, validate_email # noqa: E402
HOST = os.environ.get("ENROLL_BIND", "127.0.0.1")
PORT = int(os.environ.get("ENROLL_PORT", "8011"))
class Handler(BaseHTTPRequestHandler):
server_version = "verae-testflight-enroll/1.0"
def log_message(self, fmt, *args):
# Avoid logging emails/bodies
sys.stderr.write("%s - %s\n" % (self.address_string(), fmt % args))
def _cors(self):
origin = self.headers.get("Origin") or ""
allowed = {
"https://mcp.verae.ai",
"https://mcp.georgelambert.org",
"http://127.0.0.1:8000",
"http://localhost:8000",
}
if origin in allowed:
self.send_header("Access-Control-Allow-Origin", origin)
self.send_header("Vary", "Origin")
self.send_header("Access-Control-Allow-Methods", "POST, OPTIONS")
self.send_header("Access-Control-Allow-Headers", "Content-Type")
def _json(self, code: int, obj: dict):
raw = json.dumps(obj).encode("utf-8")
self.send_response(code)
self.send_header("Content-Type", "application/json; charset=utf-8")
self._cors()
self.send_header("Content-Length", str(len(raw)))
self.end_headers()
self.wfile.write(raw)
def do_OPTIONS(self):
self.send_response(204)
self._cors()
self.end_headers()
def do_GET(self):
if self.path.split("?")[0] in ("/healthz", "/api/testflight-invite/healthz"):
self._json(200, {"ok": True, "service": "verae-testflight-enroll"})
return
self._json(404, {"ok": False, "error": "not_found"})
def do_POST(self):
path = self.path.split("?")[0]
if path not in ("/api/testflight-invite", "/"):
self._json(404, {"ok": False, "error": "not_found"})
return
length = int(self.headers.get("Content-Length") or "0")
if length > 8192:
self._json(413, {"ok": False, "error": "too_large", "message": "Request too large."})
return
raw = self.rfile.read(length) if length else b"{}"
try:
body = json.loads(raw.decode("utf-8") or "{}")
except json.JSONDecodeError:
self._json(400, {"ok": False, "error": "invalid_json", "message": "Expected JSON body."})
return
email = body.get("email") or body.get("apple_id") or ""
note = body.get("note") or ""
try:
validate_email(email)
except ValueError:
self._json(400, {
"ok": False,
"error": "invalid_email",
"message": "Enter a valid Apple ID email address.",
})
return
try:
result = enroll(email, note=str(note)[:500])
except RuntimeError as e:
code = str(e)
self._json(503, {
"ok": False,
"error": code,
"message": "Enrollment service is not configured. Email marchon@gmail.com with your Apple ID.",
})
return
except Exception:
self._json(500, {
"ok": False,
"error": "internal",
"message": "Enrollment failed unexpectedly. Email marchon@gmail.com with your Apple ID.",
})
return
self._json(200 if result.get("ok") else 502, result)
def main():
httpd = ThreadingHTTPServer((HOST, PORT), Handler)
print(f"verae-testflight-enroll listening on {HOST}:{PORT}", flush=True)
httpd.serve_forever()
if __name__ == "__main__":
main()

View file

@ -0,0 +1,19 @@
[Unit]
Description=Verae TestFlight invite enrollment (ASC API)
After=network.target
[Service]
Type=simple
User=marchon
Group=marchon
WorkingDirectory=/SSD2/services/verae-testflight-enroll
EnvironmentFile=-/SSD2/secrets/asc/testflight-enroll.env
ExecStart=/SSD2/services/verae-testflight-enroll/venv/bin/python /SSD2/services/verae-testflight-enroll/enroll_server.py
Restart=on-failure
RestartSec=3
# Do not leak secrets into journal via argv
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target

1151
site/app.html Normal file

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

View file

@ -0,0 +1 @@
c4c2489ebe491a110c26fb66cdfb3545785d4746453a46d21cb6eb2f2972d60c /tmp/verae-windows-docs-45782/sample/sample-contract.txt

View file

@ -0,0 +1,2 @@
path,sha256,scope,receipt_id,created_at,merkle,status
/tmp/verae-windows-docs-45782/sample/sample-contract.txt,c4c2489ebe491a110c26fb66cdfb3545785d4746453a46d21cb6eb2f2972d60c,global,QmTV7gB2vb3ALTCKe1DZ1qxBvawF6h6a4ajYax2chNp6Nc,,,ok
1 path sha256 scope receipt_id created_at merkle status
2 /tmp/verae-windows-docs-45782/sample/sample-contract.txt c4c2489ebe491a110c26fb66cdfb3545785d4746453a46d21cb6eb2f2972d60c global QmTV7gB2vb3ALTCKe1DZ1qxBvawF6h6a4ajYax2chNp6Nc ok

Some files were not shown because too many files have changed in this diff Show more