Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install validator dependencies
run: python -m pip install -r requirements.txt
- name: Validate JSON and public repo gate
run: |
python -m json.tool schemas/trust-action-receipt.schema.json >/dev/null
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
jsonschema[format]>=4,<5
2 changes: 1 addition & 1 deletion tools/check-public-task-repo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
set -euo pipefail
fail() { echo "❌ $1" >&2; exit 1; }

required_files=(README.md SPEC.md PILOT_ACCESS.md ADOPTION.md SECURITY.md CONTRIBUTING.md GOVERNANCE.md VERSIONING.md schemas/trust-action-receipt.schema.json examples/remote-worker.receipt.json docs/public-vs-pilot.md docs/production-checklist.md docs/standardization-roadmap.md tools/validate-receipts.py)
required_files=(README.md SPEC.md PILOT_ACCESS.md ADOPTION.md SECURITY.md CONTRIBUTING.md GOVERNANCE.md VERSIONING.md requirements.txt schemas/trust-action-receipt.schema.json examples/remote-worker.receipt.json docs/public-vs-pilot.md docs/production-checklist.md docs/standardization-roadmap.md tools/validate-receipts.py)

for f in "${required_files[@]}"; do
[ -f "$f" ] || fail "Missing required file: $f"
Expand Down
100 changes: 81 additions & 19 deletions tools/validate-receipts.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,84 @@
import json
from pathlib import Path

required = ["receipt_type", "scenario_id", "actor", "subject", "action", "decision", "evidence", "issued_at"]
valid_decisions = {"allow", "deny", "review_required", "freeze", "revoke"}
ok = True

for path in sorted(Path("examples").glob("*.json")):
data = json.loads(path.read_text(encoding="utf-8"))
missing = [k for k in required if k not in data]
if missing:
print(f"❌ {path}: missing {', '.join(missing)}")
ok = False
elif data["receipt_type"] != "trust_action_receipt":
print(f"❌ {path}: invalid receipt_type")
ok = False
elif data["decision"] not in valid_decisions:
print(f"❌ {path}: invalid decision")
ok = False
else:
print(f"✅ {path}: valid public draft receipt")
raise SystemExit(0 if ok else 1)
try:
from jsonschema import Draft202012Validator, FormatChecker
except ImportError as exc:
raise SystemExit("Missing dependency. Run: python3 -m pip install -r requirements.txt") from exc


SCHEMA_PATH = Path("schemas/trust-action-receipt.schema.json")
EXAMPLES_DIR = Path("examples")
DEMO_SIGNATURE = "demo-signature-not-production"
FORBIDDEN_PUBLIC_MARKERS = (
"BEGIN " + "PRIVATE KEY",
"PRIVATE_KEY" + "=",
"SECRET" + "=",
"sk_" + "live_",
"pk_" + "live_",
"AKIA",
"scoped key " + "value",
"production signing " + "key",
"real customer " + "receipt",
)


def load_json(path):
return json.loads(path.read_text(encoding="utf-8"))


def public_safety_errors(path, receipt):
errors = []
text = path.read_text(encoding="utf-8")

for marker in FORBIDDEN_PUBLIC_MARKERS:
if marker in text:
errors.append(f"contains forbidden public marker: {marker}")

signature = receipt.get("signature")
if signature != DEMO_SIGNATURE:
errors.append("public examples must keep the demo signature marker")

if receipt.get("receipt_id"):
errors.append("public examples must not claim a production receipt_id")

if receipt.get("policy_ref"):
errors.append("public examples must not include production policy_ref")

if receipt.get("proof_ref"):
errors.append("public examples must not include production proof_ref")

return errors


def main():
schema = load_json(SCHEMA_PATH)
validator = Draft202012Validator(schema, format_checker=FormatChecker())
paths = sorted(EXAMPLES_DIR.glob("*.receipt.json"))
ok = True

if not paths:
print("❌ examples: no *.receipt.json files found")
return 1

for path in paths:
receipt = load_json(path)
schema_errors = sorted(validator.iter_errors(receipt), key=lambda err: list(err.path))
safety_errors = public_safety_errors(path, receipt)

if schema_errors or safety_errors:
ok = False
print(f"❌ {path}: invalid public demo receipt")
for err in schema_errors:
field = ".".join(str(part) for part in err.path) or "<root>"
print(f" schema: {field}: {err.message}")
for err in safety_errors:
print(f" safety: {err}")
continue

print(f"✅ {path}: schema-valid public demo receipt")

return 0 if ok else 1


raise SystemExit(main())
Loading