Files
playbook/antigravity-awesome-skills/skills/find-complementary-founders/scripts/validate_profile.py
T
2026-07-29 07:59:17 +00:00

474 lines
15 KiB
Python

#!/usr/bin/env python3
"""Validate a public FindMate owner profile without network access."""
from __future__ import annotations
import argparse
import hashlib
import json
import re
import sys
from datetime import date, datetime, timezone
from pathlib import Path
from urllib.parse import urlparse
SCHEMA_VERSION = "1.0"
SCHEMA_URL = (
"https://raw.githubusercontent.com/merc1305/findMate/main/"
"schemas/findmate-owner-profile-v1.schema.json"
)
STAGES = (
"zero_to_one",
"one_to_ten",
"ten_to_hundred",
)
FUNCTIONS = (
"problem_discovery",
"product",
"engineering",
"design",
"go_to_market",
"operations",
"people_leadership",
"capital_partnerships",
)
DIMENSIONS = STAGES + FUNCTIONS
LEVELS = ("unknown", "observed", "practiced", "strong", "standout")
CONFIDENCE_LEVELS = ("none", "low", "medium", "high")
ROOT_KEYS = {
"schema_version",
"profile_type",
"alias",
"summary",
"generated_at",
"expires_on",
"stage_contributions",
"functional_contributions",
"preferences",
"seeking",
"public_evidence",
"contact",
"consent",
"interpretation",
}
SENSITIVE_PATTERNS = {
"email address": re.compile(
r"\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b", re.IGNORECASE
),
"phone-like number": re.compile(r"(?<!\w)(?:\+?\d[\s().-]*){9,}(?!\w)"),
"IP address": re.compile(
r"\b(?:25[0-5]|2[0-4]\d|1?\d?\d)"
r"(?:\.(?:25[0-5]|2[0-4]\d|1?\d?\d)){3}\b"
),
"secret-like text": re.compile(
r"(?:api[_ -]?key|password|passwd|private[_ -]?key|"
r"authorization:\s*bearer|moltbook_[A-Za-z0-9_-]{8,}|"
r"gh[opusr]_[A-Za-z0-9_]{12,})",
re.IGNORECASE,
),
"local filesystem path": re.compile(r"(?:/Users/|/home/|[A-Z]:\\Users\\)"),
}
class ValidationError(ValueError):
"""Raised when a public profile is malformed, unsafe, or expired."""
def load_json(path: Path) -> dict:
try:
value = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
raise ValidationError(f"Cannot load {path}: {exc}") from exc
if not isinstance(value, dict):
raise ValidationError("profile must be a JSON object")
return value
def exact_object(value: object, field: str, keys: set[str]) -> dict:
if not isinstance(value, dict):
raise ValidationError(f"{field} must be an object")
actual = set(value)
missing = sorted(keys - actual)
extra = sorted(actual - keys)
if missing:
raise ValidationError(f"{field} is missing: {', '.join(missing)}")
if extra:
raise ValidationError(f"{field} has unsupported fields: {', '.join(extra)}")
return value
def text(value: object, field: str, *, maximum: int) -> str:
if not isinstance(value, str) or not value.strip():
raise ValidationError(f"{field} must be a non-empty string")
clean = value.strip()
if clean != value:
raise ValidationError(f"{field} must not have surrounding whitespace")
if len(clean) > maximum:
raise ValidationError(f"{field} exceeds {maximum} characters")
return clean
def public_text(value: object, field: str, *, maximum: int) -> str:
clean = text(value, field, maximum=maximum)
for label, pattern in SENSITIVE_PATTERNS.items():
if pattern.search(clean):
raise ValidationError(f"{field} appears to contain a {label}")
return clean
def alias(value: object, field: str = "alias") -> str:
clean = public_text(value, field, maximum=50)
if not re.fullmatch(r"[a-zA-Z0-9][a-zA-Z0-9_-]{1,49}", clean):
raise ValidationError(
f"{field} must be a 2-50 character pseudonym using letters, "
"digits, _ or -"
)
return clean
def evidence_identifier(value: object, field: str) -> str:
clean = public_text(value, field, maximum=60)
if not re.fullmatch(r"[a-zA-Z0-9][a-zA-Z0-9_-]{0,59}", clean):
raise ValidationError(
f"{field} must use letters, digits, _ or - and begin with "
"a letter or digit"
)
return clean
def iso_date(value: object, field: str) -> date:
raw = text(value, field, maximum=10)
try:
parsed = date.fromisoformat(raw)
except ValueError as exc:
raise ValidationError(f"{field} must use YYYY-MM-DD") from exc
if parsed.isoformat() != raw:
raise ValidationError(f"{field} must use canonical YYYY-MM-DD")
return parsed
def iso_datetime(value: object, field: str) -> datetime:
raw = text(value, field, maximum=35)
normalized = raw[:-1] + "+00:00" if raw.endswith("Z") else raw
try:
parsed = datetime.fromisoformat(normalized)
except ValueError as exc:
raise ValidationError(f"{field} must be an ISO 8601 timestamp") from exc
if parsed.tzinfo is None or parsed.utcoffset() is None:
raise ValidationError(f"{field} must include a UTC offset")
return parsed.astimezone(timezone.utc)
def https_url(
value: object,
field: str,
*,
github_contact_type: str | None = None,
) -> str:
raw = public_text(value, field, maximum=500)
parsed = urlparse(raw)
if (
parsed.scheme != "https"
or not parsed.hostname
or parsed.username
or parsed.password
or parsed.query
or parsed.fragment
):
raise ValidationError(
f"{field} must be a credential-free HTTPS URL without query or fragment"
)
if github_contact_type:
if parsed.hostname.lower() != "github.com":
raise ValidationError(f"{field} must use github.com")
parts = [part for part in parsed.path.split("/") if part]
expected = "issues" if github_contact_type == "github_issues" else "discussions"
if len(parts) < 3 or parts[2] != expected:
raise ValidationError(f"{field} must point to a GitHub {expected} page")
return raw
def unique_enum_list(
value: object,
field: str,
allowed: tuple[str, ...],
*,
required: bool = False,
) -> list[str]:
if not isinstance(value, list):
raise ValidationError(f"{field} must be a list")
if required and not value:
raise ValidationError(f"{field} must not be empty")
if len(value) != len(set(value)):
raise ValidationError(f"{field} must not contain duplicates")
for item in value:
if item not in allowed:
raise ValidationError(f"{field} contains unsupported value {item!r}")
return value
def public_string_list(
value: object,
field: str,
*,
limit: int,
) -> list[str]:
if not isinstance(value, list) or len(value) > limit:
raise ValidationError(f"{field} must be a list with at most {limit} items")
output: list[str] = []
for index, item in enumerate(value):
output.append(public_text(item, f"{field}[{index}]", maximum=80))
if len(output) != len(set(output)):
raise ValidationError(f"{field} must not contain duplicates")
return output
def score_level(score: int) -> str:
if score == 0:
return "unknown"
if score < 25:
return "observed"
if score < 50:
return "practiced"
if score < 75:
return "strong"
return "standout"
def contribution_vectors(
value: object,
field: str,
dimensions: tuple[str, ...],
) -> None:
vectors = exact_object(value, field, set(dimensions))
vector_keys = {"score", "level", "confidence", "evidence_count"}
for dimension in dimensions:
prefix = f"{field}.{dimension}"
item = exact_object(vectors[dimension], prefix, vector_keys)
score = item["score"]
count = item["evidence_count"]
if isinstance(score, bool) or not isinstance(score, int) or not 0 <= score <= 100:
raise ValidationError(f"{prefix}.score must be an integer from 0 to 100")
if (
isinstance(count, bool)
or not isinstance(count, int)
or not 0 <= count <= 50
):
raise ValidationError(
f"{prefix}.evidence_count must be an integer from 0 to 50"
)
if item["level"] not in LEVELS:
raise ValidationError(f"{prefix}.level is unsupported")
expected_level = score_level(score)
if item["level"] != expected_level:
raise ValidationError(
f"{prefix}.level must be {expected_level!r} for score {score}"
)
if item["confidence"] not in CONFIDENCE_LEVELS:
raise ValidationError(f"{prefix}.confidence is unsupported")
if count == 0 and (score != 0 or item["confidence"] != "none"):
raise ValidationError(
f"{prefix} with no evidence must have score 0 and confidence none"
)
if count > 0 and item["confidence"] == "none":
raise ValidationError(
f"{prefix} with evidence must not use confidence none"
)
def validate_profile(profile: dict) -> dict:
exact_object(profile, "profile", ROOT_KEYS)
if profile["schema_version"] != SCHEMA_VERSION:
raise ValidationError(f"schema_version must be {SCHEMA_VERSION}")
if profile["profile_type"] != "founder-collaboration":
raise ValidationError("profile_type must be founder-collaboration")
profile_alias = alias(profile["alias"])
public_text(profile["summary"], "summary", maximum=280)
generated_at = iso_datetime(profile["generated_at"], "generated_at")
expires_on = iso_date(profile["expires_on"], "expires_on")
today = datetime.now(timezone.utc).date()
if expires_on < today:
raise ValidationError(f"profile expired on {expires_on.isoformat()}")
if generated_at.date() > expires_on:
raise ValidationError("generated_at must not be after expires_on")
contribution_vectors(
profile["stage_contributions"],
"stage_contributions",
STAGES,
)
contribution_vectors(
profile["functional_contributions"],
"functional_contributions",
FUNCTIONS,
)
preferences = exact_object(
profile["preferences"],
"preferences",
{"stages", "functions"},
)
unique_enum_list(preferences["stages"], "preferences.stages", STAGES)
unique_enum_list(preferences["functions"], "preferences.functions", FUNCTIONS)
seeking = exact_object(
profile["seeking"],
"seeking",
{
"stages",
"functions",
"project_themes",
"collaboration_modes",
"shared_principles",
},
)
sought_stages = unique_enum_list(seeking["stages"], "seeking.stages", STAGES)
sought_functions = unique_enum_list(
seeking["functions"],
"seeking.functions",
FUNCTIONS,
)
if not sought_stages and not sought_functions:
raise ValidationError(
"seeking must name at least one stage or functional capability"
)
public_string_list(
seeking["project_themes"],
"seeking.project_themes",
limit=10,
)
public_string_list(
seeking["collaboration_modes"],
"seeking.collaboration_modes",
limit=5,
)
public_string_list(
seeking["shared_principles"],
"seeking.shared_principles",
limit=10,
)
evidence = profile["public_evidence"]
if not isinstance(evidence, list) or len(evidence) > 50:
raise ValidationError("public_evidence must be a list with at most 50 items")
evidence_ids: set[str] = set()
for index, raw in enumerate(evidence):
prefix = f"public_evidence[{index}]"
item = exact_object(raw, prefix, {"id", "claim", "proof", "supports"})
evidence_id = evidence_identifier(item["id"], f"{prefix}.id")
if evidence_id in evidence_ids:
raise ValidationError(f"duplicate public evidence id: {evidence_id}")
evidence_ids.add(evidence_id)
public_text(item["claim"], f"{prefix}.claim", maximum=180)
https_url(item["proof"], f"{prefix}.proof")
unique_enum_list(
item["supports"],
f"{prefix}.supports",
DIMENSIONS,
required=True,
)
contact = exact_object(profile["contact"], "contact", {"type", "url"})
if contact["type"] not in {"github_issues", "github_discussions"}:
raise ValidationError("contact.type is unsupported")
https_url(
contact["url"],
"contact.url",
github_contact_type=contact["type"],
)
consent = exact_object(
profile["consent"],
"consent",
{"state", "approved_at", "expires_on", "scope"},
)
if consent["state"] != "public_profile_approved":
raise ValidationError("consent.state must be public_profile_approved")
approved_at = iso_date(consent["approved_at"], "consent.approved_at")
consent_expires = iso_date(consent["expires_on"], "consent.expires_on")
if approved_at > today:
raise ValidationError("consent.approved_at must not be in the future")
if approved_at > generated_at.date():
raise ValidationError(
"consent.approved_at must not be after generated_at"
)
if consent_expires != expires_on:
raise ValidationError("consent.expires_on must equal profile expires_on")
if consent_expires < approved_at:
raise ValidationError("consent.expires_on must not precede approved_at")
if (consent_expires - approved_at).days > 180:
raise ValidationError("public profile consent may cover at most 180 days")
public_text(consent["scope"], "consent.scope", maximum=180)
interpretation = exact_object(
profile["interpretation"],
"interpretation",
{"status", "not_for"},
)
if interpretation["status"] != "owner-approved collaboration hypothesis":
raise ValidationError("interpretation.status is unsupported")
required_exclusions = {
"employment screening",
"psychometric diagnosis",
"sensitive-trait inference",
}
excluded = unique_enum_list(
interpretation["not_for"],
"interpretation.not_for",
tuple(sorted(required_exclusions)),
required=True,
)
if set(excluded) != required_exclusions:
raise ValidationError(
"interpretation.not_for must contain all protocol exclusions"
)
canonical = json.dumps(
profile,
sort_keys=True,
separators=(",", ":"),
ensure_ascii=False,
).encode("utf-8")
return {
"valid": True,
"schema": SCHEMA_URL,
"schema_version": SCHEMA_VERSION,
"alias": profile_alias,
"expires_on": expires_on.isoformat(),
"canonical_sha256": hashlib.sha256(canonical).hexdigest(),
"network_access": False,
}
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description=(
"Validate a privacy-minimized FindMate owner profile and print its "
"canonical SHA-256. Performs no network access."
)
)
parser.add_argument("profile", type=Path)
return parser.parse_args()
def main() -> int:
args = parse_args()
try:
result = validate_profile(load_json(args.profile))
except ValidationError as exc:
print(f"error: {exc}", file=sys.stderr)
return 2
json.dump(result, sys.stdout, indent=2, ensure_ascii=False, sort_keys=True)
sys.stdout.write("\n")
return 0
if __name__ == "__main__":
raise SystemExit(main())