Spaces:
Running
Running
chore(sync): mirror backend .py + Dockerfile to Space (hf-sync-backend)
Browse filesAutomated backend sync from szl-holdings/a11oy main via hf-sync-backend.
Updated (differed from the Space): a11oy_forge_family.py, ayllu/model_binding.py, model_release/receipt-agent/reconciliation/reconcile_artifact_binding.py
Deleted (gone from the repo + Dockerfile COPY set): (none)
Keeps the Space-built backend (serve.py + the Dockerfile-COPY'd .py
modules) identical to GitHub main so the Space never rebuilds from a
stale backend, new endpoints don't 404 there, and orphaned modules
removed from the repo don't linger in the Space tree.
a11oy_forge_family.py
CHANGED
|
@@ -41,6 +41,7 @@ import json
|
|
| 41 |
import os
|
| 42 |
import time
|
| 43 |
from datetime import datetime, timezone
|
|
|
|
| 44 |
|
| 45 |
import httpx
|
| 46 |
from cryptography.hazmat.primitives.serialization import load_der_public_key
|
|
@@ -50,6 +51,14 @@ _ROUTE = "/api/forge/family"
|
|
| 50 |
_HF = "https://huggingface.co"
|
| 51 |
_RECEIPT_FILES = ("owner_pubkey.json", "training_receipt.signed.json", "eval_receipt.signed.json")
|
| 52 |
_CACHE_TTL_SECONDS = 300 # receipt BYTES only; verification always re-runs
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
|
| 54 |
_MODELS = (
|
| 55 |
{
|
|
@@ -57,6 +66,7 @@ _MODELS = (
|
|
| 57 |
"displayName": "SZL-Forge-1.5B-ReceiptAgent",
|
| 58 |
"hfRepo": "SZLHOLDINGS/SZL-Forge-1.5B-ReceiptAgent",
|
| 59 |
"pinEnv": "A11OY_OWNER_KEYID",
|
|
|
|
| 60 |
},
|
| 61 |
{
|
| 62 |
"model": "khipu",
|
|
@@ -66,8 +76,8 @@ _MODELS = (
|
|
| 66 |
},
|
| 67 |
)
|
| 68 |
|
| 69 |
-
# repo -> {"at": epoch, "files": {name: bytes}}
|
| 70 |
-
_byte_cache: dict = {}
|
| 71 |
|
| 72 |
|
| 73 |
def _now_iso() -> str:
|
|
@@ -83,20 +93,67 @@ def _canonical(payload: dict) -> str:
|
|
| 83 |
return json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
|
| 84 |
|
| 85 |
|
| 86 |
-
async def _fetch_receipt_bytes(
|
| 87 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
if cached and (time.time() - cached["at"]) < _CACHE_TTL_SECONDS:
|
| 89 |
return cached
|
| 90 |
files = {}
|
| 91 |
for name in _RECEIPT_FILES:
|
| 92 |
-
resp = await client.get(f"{_HF}/{repo}/resolve/
|
| 93 |
resp.raise_for_status()
|
| 94 |
files[name] = resp.content
|
| 95 |
entry = {"at": time.time(), "files": files}
|
| 96 |
-
_byte_cache[
|
| 97 |
return entry
|
| 98 |
|
| 99 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
def _verify_one_receipt(receipt: dict, owner_spki_b64: str, owner_key_id: str, expected_kind_word: str) -> dict:
|
| 101 |
"""Run every check for one signed receipt. Returns {check_name: bool} + meta."""
|
| 102 |
checks = {}
|
|
@@ -130,7 +187,12 @@ def _verify_one_receipt(receipt: dict, owner_spki_b64: str, owner_key_id: str, e
|
|
| 130 |
}
|
| 131 |
|
| 132 |
|
| 133 |
-
def _band_for_model(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
"""Build one fully-verified wall band. Verification runs on every call."""
|
| 135 |
owner = json.loads(raw_files["owner_pubkey.json"])
|
| 136 |
training = json.loads(raw_files["training_receipt.signed.json"])
|
|
@@ -141,7 +203,7 @@ def _band_for_model(cfg: dict, raw_files: dict, fetched_at_epoch: float) -> dict
|
|
| 141 |
owner_checks = {
|
| 142 |
"ownerAlgoIsEd25519": owner.get("algo") == "ed25519",
|
| 143 |
"ownerKeyIdDerivesFromSpki": (
|
| 144 |
-
_sha256_hex(base64.b64decode(owner_spki_b64))
|
| 145 |
if owner_spki_b64 else False
|
| 146 |
),
|
| 147 |
}
|
|
@@ -168,6 +230,71 @@ def _band_for_model(cfg: dict, raw_files: dict, fetched_at_epoch: float) -> dict
|
|
| 168 |
|
| 169 |
training_verified = owner_checks["ownerAlgoIsEd25519"] and owner_checks["ownerKeyIdDerivesFromSpki"] and training_result["allPassed"] and pin_ok
|
| 170 |
eval_verified = owner_checks["ownerAlgoIsEd25519"] and owner_checks["ownerKeyIdDerivesFromSpki"] and eval_result["allPassed"] and chain_ok and pin_ok
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
ep = eval_result["payload"]
|
| 172 |
tp = training_result["payload"]
|
| 173 |
|
|
@@ -178,11 +305,26 @@ def _band_for_model(cfg: dict, raw_files: dict, fetched_at_epoch: float) -> dict
|
|
| 178 |
"keyId": owner_key_id,
|
| 179 |
"keyTrust": key_trust,
|
| 180 |
"pinEnv": pin_env,
|
| 181 |
-
"verified":
|
|
|
|
| 182 |
"status": (
|
| 183 |
(["TRAINED_RECEIPT_VERIFIED"] if training_verified else ["TRAINING_RECEIPT_FAILED"])
|
| 184 |
+ (["EVAL_RECEIPT_VERIFIED"] if eval_verified else ["EVAL_RECEIPT_FAILED"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
),
|
|
|
|
| 186 |
"checks": {
|
| 187 |
"owner": owner_checks,
|
| 188 |
"training": training_result["checks"],
|
|
@@ -215,8 +357,22 @@ async def _forge_family_handler():
|
|
| 215 |
async with httpx.AsyncClient(timeout=10.0, follow_redirects=True) as client:
|
| 216 |
for cfg in _MODELS:
|
| 217 |
try:
|
| 218 |
-
|
| 219 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 220 |
except Exception as band_error: # loud, honest, isolated per band
|
| 221 |
bands.append({
|
| 222 |
"model": cfg["model"],
|
|
|
|
| 41 |
import os
|
| 42 |
import time
|
| 43 |
from datetime import datetime, timezone
|
| 44 |
+
from pathlib import Path
|
| 45 |
|
| 46 |
import httpx
|
| 47 |
from cryptography.hazmat.primitives.serialization import load_der_public_key
|
|
|
|
| 51 |
_HF = "https://huggingface.co"
|
| 52 |
_RECEIPT_FILES = ("owner_pubkey.json", "training_receipt.signed.json", "eval_receipt.signed.json")
|
| 53 |
_CACHE_TTL_SECONDS = 300 # receipt BYTES only; verification always re-runs
|
| 54 |
+
_RECONCILIATION_STATE = "ARTIFACT_BYTES_RECONCILED_MEASURED_NOT_PROMOTED"
|
| 55 |
+
_RECONCILIATION_PATH = (
|
| 56 |
+
Path(__file__).resolve().parent
|
| 57 |
+
/ "model_release"
|
| 58 |
+
/ "receipt-agent"
|
| 59 |
+
/ "reconciliation"
|
| 60 |
+
/ "receipt-agent-artifact-reconciliation.v1.json"
|
| 61 |
+
)
|
| 62 |
|
| 63 |
_MODELS = (
|
| 64 |
{
|
|
|
|
| 66 |
"displayName": "SZL-Forge-1.5B-ReceiptAgent",
|
| 67 |
"hfRepo": "SZLHOLDINGS/SZL-Forge-1.5B-ReceiptAgent",
|
| 68 |
"pinEnv": "A11OY_OWNER_KEYID",
|
| 69 |
+
"artifactReconciliation": True,
|
| 70 |
},
|
| 71 |
{
|
| 72 |
"model": "khipu",
|
|
|
|
| 76 |
},
|
| 77 |
)
|
| 78 |
|
| 79 |
+
# (repo, immutable revision) -> {"at": epoch, "files": {name: bytes}}
|
| 80 |
+
_byte_cache: dict[tuple[str, str], dict] = {}
|
| 81 |
|
| 82 |
|
| 83 |
def _now_iso() -> str:
|
|
|
|
| 93 |
return json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
|
| 94 |
|
| 95 |
|
| 96 |
+
async def _fetch_receipt_bytes(
|
| 97 |
+
client: httpx.AsyncClient,
|
| 98 |
+
repo: str,
|
| 99 |
+
revision: str,
|
| 100 |
+
) -> dict:
|
| 101 |
+
cache_key = (repo, revision)
|
| 102 |
+
cached = _byte_cache.get(cache_key)
|
| 103 |
if cached and (time.time() - cached["at"]) < _CACHE_TTL_SECONDS:
|
| 104 |
return cached
|
| 105 |
files = {}
|
| 106 |
for name in _RECEIPT_FILES:
|
| 107 |
+
resp = await client.get(f"{_HF}/{repo}/resolve/{revision}/{name}")
|
| 108 |
resp.raise_for_status()
|
| 109 |
files[name] = resp.content
|
| 110 |
entry = {"at": time.time(), "files": files}
|
| 111 |
+
_byte_cache[cache_key] = entry
|
| 112 |
return entry
|
| 113 |
|
| 114 |
|
| 115 |
+
async def _fetch_public_head(client: httpx.AsyncClient, repo: str) -> str:
|
| 116 |
+
"""Observe the current public head independently of cached receipt bytes."""
|
| 117 |
+
response = await client.get(f"{_HF}/api/models/{repo}")
|
| 118 |
+
response.raise_for_status()
|
| 119 |
+
payload = response.json()
|
| 120 |
+
revision = payload.get("sha") if isinstance(payload, dict) else None
|
| 121 |
+
if (
|
| 122 |
+
not isinstance(revision, str)
|
| 123 |
+
or len(revision) != 40
|
| 124 |
+
or any(character not in "0123456789abcdef" for character in revision)
|
| 125 |
+
):
|
| 126 |
+
raise ValueError("Hugging Face model API did not return a lowercase hex head")
|
| 127 |
+
return revision
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def _load_receipt_agent_reconciliation() -> dict:
|
| 131 |
+
"""Load and self-verify the frozen, non-promoting reconciliation artifact."""
|
| 132 |
+
value = json.loads(_RECONCILIATION_PATH.read_text(encoding="utf-8"))
|
| 133 |
+
recorded = value.get("reconciliation_sha256")
|
| 134 |
+
unsigned = dict(value)
|
| 135 |
+
unsigned.pop("reconciliation_sha256", None)
|
| 136 |
+
computed = _sha256_hex(_canonical(unsigned).encode("utf-8"))
|
| 137 |
+
if recorded != computed:
|
| 138 |
+
raise ValueError("ReceiptAgent reconciliation self-digest mismatch")
|
| 139 |
+
if value.get("state") != _RECONCILIATION_STATE:
|
| 140 |
+
raise ValueError("ReceiptAgent reconciliation state mismatch")
|
| 141 |
+
if value.get("repository") != "SZLHOLDINGS/SZL-Forge-1.5B-ReceiptAgent":
|
| 142 |
+
raise ValueError("ReceiptAgent reconciliation repository mismatch")
|
| 143 |
+
if value.get("authorization") != {
|
| 144 |
+
"trained": False,
|
| 145 |
+
"uploaded": False,
|
| 146 |
+
"promoted": False,
|
| 147 |
+
"deployed": False,
|
| 148 |
+
}:
|
| 149 |
+
raise ValueError("ReceiptAgent reconciliation overstates authorization")
|
| 150 |
+
binding = value.get("exact_qualified_artifact_binding") or {}
|
| 151 |
+
head = value.get("current_public_head_equivalence") or {}
|
| 152 |
+
if binding.get("verified") is not True or head.get("verified") is not True:
|
| 153 |
+
raise ValueError("ReceiptAgent reconciliation is not fully verified")
|
| 154 |
+
return value
|
| 155 |
+
|
| 156 |
+
|
| 157 |
def _verify_one_receipt(receipt: dict, owner_spki_b64: str, owner_key_id: str, expected_kind_word: str) -> dict:
|
| 158 |
"""Run every check for one signed receipt. Returns {check_name: bool} + meta."""
|
| 159 |
checks = {}
|
|
|
|
| 187 |
}
|
| 188 |
|
| 189 |
|
| 190 |
+
def _band_for_model(
|
| 191 |
+
cfg: dict,
|
| 192 |
+
raw_files: dict,
|
| 193 |
+
fetched_at_epoch: float,
|
| 194 |
+
public_head_revision: str | None = None,
|
| 195 |
+
) -> dict:
|
| 196 |
"""Build one fully-verified wall band. Verification runs on every call."""
|
| 197 |
owner = json.loads(raw_files["owner_pubkey.json"])
|
| 198 |
training = json.loads(raw_files["training_receipt.signed.json"])
|
|
|
|
| 203 |
owner_checks = {
|
| 204 |
"ownerAlgoIsEd25519": owner.get("algo") == "ed25519",
|
| 205 |
"ownerKeyIdDerivesFromSpki": (
|
| 206 |
+
_sha256_hex(base64.b64decode(owner_spki_b64))[:16] == owner_key_id
|
| 207 |
if owner_spki_b64 else False
|
| 208 |
),
|
| 209 |
}
|
|
|
|
| 230 |
|
| 231 |
training_verified = owner_checks["ownerAlgoIsEd25519"] and owner_checks["ownerKeyIdDerivesFromSpki"] and training_result["allPassed"] and pin_ok
|
| 232 |
eval_verified = owner_checks["ownerAlgoIsEd25519"] and owner_checks["ownerKeyIdDerivesFromSpki"] and eval_result["allPassed"] and chain_ok and pin_ok
|
| 233 |
+
receipt_signatures_verified = training_verified and eval_verified
|
| 234 |
+
verification_layers = {
|
| 235 |
+
"receiptSignatureValidity": {
|
| 236 |
+
"verified": receipt_signatures_verified,
|
| 237 |
+
"training": training_verified,
|
| 238 |
+
"evaluation": eval_verified,
|
| 239 |
+
"trustBoundary": {
|
| 240 |
+
"PINNED": "PINNED_OWNER_KEY",
|
| 241 |
+
"PIN_MISMATCH": "PIN_MISMATCH_FAIL_CLOSED",
|
| 242 |
+
"REPO_DECLARED": (
|
| 243 |
+
"REPOSITORY_DECLARED_KEY_NOT_INDEPENDENTLY_PINNED"
|
| 244 |
+
),
|
| 245 |
+
}[key_trust],
|
| 246 |
+
},
|
| 247 |
+
"exactQualifiedArtifactBinding": {
|
| 248 |
+
"verified": None,
|
| 249 |
+
"state": "NOT_EVALUATED_FOR_THIS_PROFILE",
|
| 250 |
+
},
|
| 251 |
+
"currentPublicHeadEquivalence": {
|
| 252 |
+
"verified": None,
|
| 253 |
+
"state": "NOT_EVALUATED_FOR_THIS_PROFILE",
|
| 254 |
+
"observedHead": public_head_revision,
|
| 255 |
+
},
|
| 256 |
+
"promotion": {
|
| 257 |
+
"authorized": False,
|
| 258 |
+
"state": "NOT_PROMOTED",
|
| 259 |
+
},
|
| 260 |
+
}
|
| 261 |
+
profile_state = None
|
| 262 |
+
fully_verified = receipt_signatures_verified
|
| 263 |
+
if cfg.get("artifactReconciliation"):
|
| 264 |
+
reconciliation = _load_receipt_agent_reconciliation()
|
| 265 |
+
artifact_binding = reconciliation["exact_qualified_artifact_binding"]
|
| 266 |
+
frozen_head = reconciliation["public_head_revision"]
|
| 267 |
+
current_head_equivalent = public_head_revision == frozen_head
|
| 268 |
+
verification_layers["exactQualifiedArtifactBinding"] = {
|
| 269 |
+
"verified": artifact_binding["verified"] is True,
|
| 270 |
+
"state": _RECONCILIATION_STATE,
|
| 271 |
+
"qualifiedRevision": reconciliation["qualified_revision"],
|
| 272 |
+
"qualificationReceiptSha256": artifact_binding[
|
| 273 |
+
"qualification_receipt_sha256"
|
| 274 |
+
],
|
| 275 |
+
"digestDomain": artifact_binding["digest_domain"],
|
| 276 |
+
}
|
| 277 |
+
verification_layers["currentPublicHeadEquivalence"] = {
|
| 278 |
+
"verified": current_head_equivalent,
|
| 279 |
+
"state": (
|
| 280 |
+
"INFERENCE_BEARING_BLOBS_EQUIVALENT_TO_QUALIFIED_REVISION"
|
| 281 |
+
if current_head_equivalent
|
| 282 |
+
else "PUBLIC_HEAD_CHANGED_RECONCILIATION_REQUIRED"
|
| 283 |
+
),
|
| 284 |
+
"observedHead": public_head_revision,
|
| 285 |
+
"reconciledHead": frozen_head,
|
| 286 |
+
"failClosedOnHeadChange": True,
|
| 287 |
+
}
|
| 288 |
+
fully_verified = (
|
| 289 |
+
receipt_signatures_verified
|
| 290 |
+
and artifact_binding["verified"] is True
|
| 291 |
+
and current_head_equivalent
|
| 292 |
+
)
|
| 293 |
+
profile_state = (
|
| 294 |
+
_RECONCILIATION_STATE
|
| 295 |
+
if fully_verified
|
| 296 |
+
else "ARTIFACT_RECONCILIATION_FAILED_CLOSED"
|
| 297 |
+
)
|
| 298 |
ep = eval_result["payload"]
|
| 299 |
tp = training_result["payload"]
|
| 300 |
|
|
|
|
| 305 |
"keyId": owner_key_id,
|
| 306 |
"keyTrust": key_trust,
|
| 307 |
"pinEnv": pin_env,
|
| 308 |
+
"verified": fully_verified,
|
| 309 |
+
"profileState": profile_state,
|
| 310 |
"status": (
|
| 311 |
(["TRAINED_RECEIPT_VERIFIED"] if training_verified else ["TRAINING_RECEIPT_FAILED"])
|
| 312 |
+ (["EVAL_RECEIPT_VERIFIED"] if eval_verified else ["EVAL_RECEIPT_FAILED"])
|
| 313 |
+
+ (
|
| 314 |
+
[
|
| 315 |
+
"ARTIFACT_BYTES_RECONCILED_MEASURED",
|
| 316 |
+
"CURRENT_PUBLIC_HEAD_EQUIVALENT",
|
| 317 |
+
"NOT_PROMOTED",
|
| 318 |
+
]
|
| 319 |
+
if cfg.get("artifactReconciliation") and fully_verified
|
| 320 |
+
else (
|
| 321 |
+
["ARTIFACT_RECONCILIATION_FAILED_CLOSED", "NOT_PROMOTED"]
|
| 322 |
+
if cfg.get("artifactReconciliation")
|
| 323 |
+
else []
|
| 324 |
+
)
|
| 325 |
+
)
|
| 326 |
),
|
| 327 |
+
"verificationLayers": verification_layers,
|
| 328 |
"checks": {
|
| 329 |
"owner": owner_checks,
|
| 330 |
"training": training_result["checks"],
|
|
|
|
| 357 |
async with httpx.AsyncClient(timeout=10.0, follow_redirects=True) as client:
|
| 358 |
for cfg in _MODELS:
|
| 359 |
try:
|
| 360 |
+
public_head_revision = await _fetch_public_head(
|
| 361 |
+
client, cfg["hfRepo"]
|
| 362 |
+
)
|
| 363 |
+
entry = await _fetch_receipt_bytes(
|
| 364 |
+
client,
|
| 365 |
+
cfg["hfRepo"],
|
| 366 |
+
public_head_revision,
|
| 367 |
+
)
|
| 368 |
+
bands.append(
|
| 369 |
+
_band_for_model(
|
| 370 |
+
cfg,
|
| 371 |
+
entry["files"],
|
| 372 |
+
entry["at"],
|
| 373 |
+
public_head_revision,
|
| 374 |
+
)
|
| 375 |
+
)
|
| 376 |
except Exception as band_error: # loud, honest, isolated per band
|
| 377 |
bands.append({
|
| 378 |
"model": cfg["model"],
|
ayllu/model_binding.py
CHANGED
|
@@ -33,7 +33,7 @@ _ALL_COMPUTE_OPERATIONS = (
|
|
| 33 |
)
|
| 34 |
|
| 35 |
_PROFILE_STATES = {
|
| 36 |
-
"ReceiptAgent-v1": "
|
| 37 |
"BrainNavigator-v1": "SIGNED_RECEIPTS_VALID_ARTIFACT_BINDING_CONFLICT",
|
| 38 |
"Operator-v1": "PLANNED_TOOL_CONTRACT_REQUIRED",
|
| 39 |
"Sentinel-v1": "PLANNED_SECURITY_ADMISSION_REQUIRED",
|
|
|
|
| 33 |
)
|
| 34 |
|
| 35 |
_PROFILE_STATES = {
|
| 36 |
+
"ReceiptAgent-v1": "ARTIFACT_BYTES_RECONCILED_MEASURED_NOT_PROMOTED",
|
| 37 |
"BrainNavigator-v1": "SIGNED_RECEIPTS_VALID_ARTIFACT_BINDING_CONFLICT",
|
| 38 |
"Operator-v1": "PLANNED_TOOL_CONTRACT_REQUIRED",
|
| 39 |
"Sentinel-v1": "PLANNED_SECURITY_ADMISSION_REQUIRED",
|
model_release/receipt-agent/reconciliation/reconcile_artifact_binding.py
ADDED
|
@@ -0,0 +1,578 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Offline ReceiptAgent artifact-binding reconciliation.
|
| 3 |
+
|
| 4 |
+
The input fixture is a frozen observation captured from the public Hugging Face
|
| 5 |
+
Git repository. This program performs no network access and does not download,
|
| 6 |
+
train, upload, promote, or deploy a model. It separates three claims:
|
| 7 |
+
|
| 8 |
+
1. the repository-declared Ed25519 receipt signatures verified during the
|
| 9 |
+
qualification run;
|
| 10 |
+
2. the exact qualified weight/adapter byte binding, including the ReceiptAgent
|
| 11 |
+
signer's basename-plus-bytes digest domain; and
|
| 12 |
+
3. equivalence of every inference-bearing Git blob at the observed public head
|
| 13 |
+
to the qualified revision.
|
| 14 |
+
|
| 15 |
+
Any inference-bearing drift, digest-domain mismatch, invalid qualification
|
| 16 |
+
self-digest, or promotion claim is a hard refusal.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from __future__ import annotations
|
| 20 |
+
|
| 21 |
+
import argparse
|
| 22 |
+
import base64
|
| 23 |
+
import hashlib
|
| 24 |
+
import json
|
| 25 |
+
from pathlib import Path
|
| 26 |
+
from typing import Any
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
SCHEMA_VERSION = "szl.receiptagent-artifact-reconciliation.v1"
|
| 30 |
+
FIXTURE_SCHEMA_VERSION = "szl.receiptagent-artifact-reconciliation-fixture.v1"
|
| 31 |
+
RECONCILED_STATE = "ARTIFACT_BYTES_RECONCILED_MEASURED_NOT_PROMOTED"
|
| 32 |
+
REPOSITORY = "SZLHOLDINGS/SZL-Forge-1.5B-ReceiptAgent"
|
| 33 |
+
QUALIFIED_REVISION = "fa73dc1bd8eeece727d0b5c1db52448ec0703e8b"
|
| 34 |
+
PUBLIC_HEAD_REVISION = "2e62cb5f8e6a17052da532305a467861094a2109"
|
| 35 |
+
HEX40 = set("0123456789abcdef")
|
| 36 |
+
HEX64 = HEX40
|
| 37 |
+
INFERENCE_BEARING_PATHS = frozenset(
|
| 38 |
+
{
|
| 39 |
+
"adapter/adapter_config.json",
|
| 40 |
+
"adapter/adapter_model.safetensors",
|
| 41 |
+
"chat_template.jinja",
|
| 42 |
+
"config.json",
|
| 43 |
+
"eval_receipt.signed.json",
|
| 44 |
+
"generation_config.json",
|
| 45 |
+
"model.safetensors",
|
| 46 |
+
"owner_pubkey.json",
|
| 47 |
+
"receiptagent.schema.json",
|
| 48 |
+
"tokenizer.json",
|
| 49 |
+
"tokenizer_config.json",
|
| 50 |
+
"training_receipt.signed.json",
|
| 51 |
+
}
|
| 52 |
+
)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
class ReconciliationRefusal(RuntimeError):
|
| 56 |
+
"""The frozen evidence cannot support artifact equivalence."""
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def _require(condition: bool, message: str) -> None:
|
| 60 |
+
if not condition:
|
| 61 |
+
raise ReconciliationRefusal(message)
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def _strict_keys(value: dict[str, Any], expected: set[str], label: str) -> None:
|
| 65 |
+
_require(set(value) == expected, f"{label} keys do not match the frozen contract")
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def _is_hex(value: Any, length: int) -> bool:
|
| 69 |
+
return (
|
| 70 |
+
isinstance(value, str)
|
| 71 |
+
and len(value) == length
|
| 72 |
+
and all(character in HEX40 for character in value)
|
| 73 |
+
)
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def canonical_bytes(value: Any) -> bytes:
|
| 77 |
+
return json.dumps(
|
| 78 |
+
value, sort_keys=True, separators=(",", ":"), ensure_ascii=False
|
| 79 |
+
).encode("utf-8")
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def sha256_bytes(value: bytes) -> str:
|
| 83 |
+
return hashlib.sha256(value).hexdigest()
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def git_blob_sha1(value: bytes) -> str:
|
| 87 |
+
header = b"blob " + str(len(value)).encode("ascii") + b"\0"
|
| 88 |
+
return hashlib.sha1(header + value, usedforsecurity=False).hexdigest()
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def git_object_sha1(kind: str, value: bytes) -> str:
|
| 92 |
+
_require(kind in {"commit", "tree"}, "unsupported Git object kind")
|
| 93 |
+
header = kind.encode("ascii") + b" " + str(len(value)).encode("ascii") + b"\0"
|
| 94 |
+
return hashlib.sha1(header + value, usedforsecurity=False).hexdigest()
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def lfs_pointer_git_blob_sha1(raw_sha256: str, size: int) -> str:
|
| 98 |
+
pointer = (
|
| 99 |
+
"version https://git-lfs.github.com/spec/v1\n"
|
| 100 |
+
f"oid sha256:{raw_sha256}\n"
|
| 101 |
+
f"size {size}\n"
|
| 102 |
+
).encode("ascii")
|
| 103 |
+
return git_blob_sha1(pointer)
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def self_digest(value: dict[str, Any], field: str) -> str:
|
| 107 |
+
unsigned = dict(value)
|
| 108 |
+
unsigned.pop(field, None)
|
| 109 |
+
return sha256_bytes(canonical_bytes(unsigned))
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def receipt_digest(value: dict[str, Any]) -> str:
|
| 113 |
+
return self_digest(value, "receipt_sha256")
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def raw_and_receipt_digest(basename: str, content: bytes) -> tuple[str, str]:
|
| 117 |
+
"""Return raw bytes and ReceiptAgent signer-domain digests.
|
| 118 |
+
|
| 119 |
+
The training signer used sha256(UTF8(basename) || raw_file_bytes) for each
|
| 120 |
+
directory containing a single safetensors artifact. It did not claim that
|
| 121 |
+
this digest was the raw LFS object SHA-256.
|
| 122 |
+
"""
|
| 123 |
+
|
| 124 |
+
return (
|
| 125 |
+
sha256_bytes(content),
|
| 126 |
+
sha256_bytes(basename.encode("utf-8") + content),
|
| 127 |
+
)
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def load_json(path: Path) -> dict[str, Any]:
|
| 131 |
+
value = json.loads(path.read_text(encoding="utf-8"))
|
| 132 |
+
_require(isinstance(value, dict), f"{path} must contain one JSON object")
|
| 133 |
+
return value
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
def _verify_digest_vector(vector: dict[str, Any]) -> None:
|
| 137 |
+
_strict_keys(
|
| 138 |
+
vector,
|
| 139 |
+
{
|
| 140 |
+
"basename",
|
| 141 |
+
"content_base64",
|
| 142 |
+
"bytes",
|
| 143 |
+
"raw_sha256",
|
| 144 |
+
"receipt_directory_sha256",
|
| 145 |
+
},
|
| 146 |
+
"digest_test_vector",
|
| 147 |
+
)
|
| 148 |
+
content = base64.b64decode(vector["content_base64"], validate=True)
|
| 149 |
+
_require(len(content) == vector["bytes"], "digest test-vector byte count mismatch")
|
| 150 |
+
raw_sha, receipt_sha = raw_and_receipt_digest(vector["basename"], content)
|
| 151 |
+
_require(raw_sha == vector["raw_sha256"], "raw digest domain test-vector mismatch")
|
| 152 |
+
_require(
|
| 153 |
+
receipt_sha == vector["receipt_directory_sha256"],
|
| 154 |
+
"basename-plus-bytes digest domain test-vector mismatch",
|
| 155 |
+
)
|
| 156 |
+
_require(raw_sha != receipt_sha, "digest domains must remain explicitly distinct")
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
def _verify_revision_git_evidence(
|
| 160 |
+
evidence: dict[str, Any],
|
| 161 |
+
*,
|
| 162 |
+
label: str,
|
| 163 |
+
expected_revision: str,
|
| 164 |
+
) -> dict[str, str]:
|
| 165 |
+
_strict_keys(
|
| 166 |
+
evidence,
|
| 167 |
+
{"revision", "commit_object_base64", "trees"},
|
| 168 |
+
f"{label}_git_evidence",
|
| 169 |
+
)
|
| 170 |
+
_require(evidence["revision"] == expected_revision, f"{label} evidence revision mismatch")
|
| 171 |
+
commit_object = base64.b64decode(evidence["commit_object_base64"], validate=True)
|
| 172 |
+
_require(
|
| 173 |
+
git_object_sha1("commit", commit_object) == expected_revision,
|
| 174 |
+
f"{label} commit object identity mismatch",
|
| 175 |
+
)
|
| 176 |
+
commit_lines = commit_object.splitlines()
|
| 177 |
+
_require(
|
| 178 |
+
commit_lines
|
| 179 |
+
and commit_lines[0].startswith(b"tree ")
|
| 180 |
+
and len(commit_lines[0]) == 45,
|
| 181 |
+
f"{label} commit object lacks one root tree",
|
| 182 |
+
)
|
| 183 |
+
root_tree_sha1 = commit_lines[0][5:].decode("ascii")
|
| 184 |
+
_require(_is_hex(root_tree_sha1, 40), f"{label} root tree identity is invalid")
|
| 185 |
+
|
| 186 |
+
tree_records = evidence["trees"]
|
| 187 |
+
_require(isinstance(tree_records, list), f"{label} tree evidence must be a list")
|
| 188 |
+
trees_by_path: dict[str, dict[str, Any]] = {}
|
| 189 |
+
for tree in tree_records:
|
| 190 |
+
_strict_keys(tree, {"path", "tree_sha1", "entries"}, f"{label} tree")
|
| 191 |
+
path = tree["path"]
|
| 192 |
+
_require(
|
| 193 |
+
isinstance(path, str)
|
| 194 |
+
and (path == "" or (not path.startswith("/") and not path.endswith("/"))),
|
| 195 |
+
f"{label} tree path is invalid",
|
| 196 |
+
)
|
| 197 |
+
_require(path not in trees_by_path, f"{label} duplicate tree path: {path}")
|
| 198 |
+
_require(_is_hex(tree["tree_sha1"], 40), f"{label} tree identity is invalid: {path}")
|
| 199 |
+
entries = tree["entries"]
|
| 200 |
+
_require(isinstance(entries, list), f"{label} tree entries must be a list: {path}")
|
| 201 |
+
names: set[str] = set()
|
| 202 |
+
serialized = bytearray()
|
| 203 |
+
for entry in entries:
|
| 204 |
+
_strict_keys(
|
| 205 |
+
entry,
|
| 206 |
+
{"mode", "type", "object_sha1", "name"},
|
| 207 |
+
f"{label} tree entry",
|
| 208 |
+
)
|
| 209 |
+
name = entry["name"]
|
| 210 |
+
_require(
|
| 211 |
+
isinstance(name, str)
|
| 212 |
+
and name
|
| 213 |
+
and "/" not in name
|
| 214 |
+
and "\0" not in name,
|
| 215 |
+
f"{label} tree entry name is invalid",
|
| 216 |
+
)
|
| 217 |
+
_require(name not in names, f"{label} duplicate tree entry: {path}/{name}")
|
| 218 |
+
names.add(name)
|
| 219 |
+
is_tree = entry["type"] == "tree"
|
| 220 |
+
_require(
|
| 221 |
+
(is_tree and entry["mode"] == "040000")
|
| 222 |
+
or (entry["type"] == "blob" and entry["mode"] in {"100644", "100755"}),
|
| 223 |
+
f"{label} tree entry mode/type mismatch: {path}/{name}",
|
| 224 |
+
)
|
| 225 |
+
_require(
|
| 226 |
+
_is_hex(entry["object_sha1"], 40),
|
| 227 |
+
f"{label} tree entry identity is invalid: {path}/{name}",
|
| 228 |
+
)
|
| 229 |
+
object_mode = "40000" if is_tree else entry["mode"]
|
| 230 |
+
serialized.extend(object_mode.encode("ascii"))
|
| 231 |
+
serialized.extend(b" ")
|
| 232 |
+
serialized.extend(name.encode("utf-8"))
|
| 233 |
+
serialized.extend(b"\0")
|
| 234 |
+
serialized.extend(bytes.fromhex(entry["object_sha1"]))
|
| 235 |
+
_require(
|
| 236 |
+
git_object_sha1("tree", bytes(serialized)) == tree["tree_sha1"],
|
| 237 |
+
f"{label} tree object identity mismatch: {path or '<root>'}",
|
| 238 |
+
)
|
| 239 |
+
trees_by_path[path] = tree
|
| 240 |
+
|
| 241 |
+
root = trees_by_path.get("")
|
| 242 |
+
_require(root is not None, f"{label} root tree evidence is missing")
|
| 243 |
+
_require(
|
| 244 |
+
root["tree_sha1"] == root_tree_sha1,
|
| 245 |
+
f"{label} root tree does not bind to the commit object",
|
| 246 |
+
)
|
| 247 |
+
|
| 248 |
+
blobs: dict[str, str] = {}
|
| 249 |
+
visited_trees: set[str] = set()
|
| 250 |
+
|
| 251 |
+
def walk_tree(path: str, expected_tree_sha1: str) -> None:
|
| 252 |
+
tree = trees_by_path.get(path)
|
| 253 |
+
_require(tree is not None, f"{label} referenced tree evidence is missing: {path}")
|
| 254 |
+
_require(
|
| 255 |
+
tree["tree_sha1"] == expected_tree_sha1,
|
| 256 |
+
f"{label} referenced tree identity mismatch: {path}",
|
| 257 |
+
)
|
| 258 |
+
visited_trees.add(path)
|
| 259 |
+
for entry in tree["entries"]:
|
| 260 |
+
child_path = f"{path}/{entry['name']}" if path else entry["name"]
|
| 261 |
+
if entry["type"] == "tree":
|
| 262 |
+
walk_tree(child_path, entry["object_sha1"])
|
| 263 |
+
else:
|
| 264 |
+
_require(child_path not in blobs, f"{label} duplicate blob path: {child_path}")
|
| 265 |
+
blobs[child_path] = entry["object_sha1"]
|
| 266 |
+
|
| 267 |
+
walk_tree("", root_tree_sha1)
|
| 268 |
+
_require(
|
| 269 |
+
visited_trees == set(trees_by_path),
|
| 270 |
+
f"{label} tree evidence contains an unreachable tree",
|
| 271 |
+
)
|
| 272 |
+
return blobs
|
| 273 |
+
|
| 274 |
+
|
| 275 |
+
def reconcile(
|
| 276 |
+
fixture: dict[str, Any],
|
| 277 |
+
qualification_receipt: dict[str, Any],
|
| 278 |
+
) -> dict[str, Any]:
|
| 279 |
+
_strict_keys(
|
| 280 |
+
fixture,
|
| 281 |
+
{
|
| 282 |
+
"schema_version",
|
| 283 |
+
"repository",
|
| 284 |
+
"observed_at",
|
| 285 |
+
"qualified_revision",
|
| 286 |
+
"public_head_revision",
|
| 287 |
+
"qualification_receipt",
|
| 288 |
+
"signature_evidence",
|
| 289 |
+
"digest_test_vectors",
|
| 290 |
+
"git_object_evidence",
|
| 291 |
+
"inference_bearing_blobs",
|
| 292 |
+
"public_head_delta",
|
| 293 |
+
"authorization",
|
| 294 |
+
"non_claims",
|
| 295 |
+
},
|
| 296 |
+
"fixture",
|
| 297 |
+
)
|
| 298 |
+
_require(
|
| 299 |
+
fixture["schema_version"] == FIXTURE_SCHEMA_VERSION,
|
| 300 |
+
"fixture schema version mismatch",
|
| 301 |
+
)
|
| 302 |
+
_require(fixture["repository"] == REPOSITORY, "repository mismatch")
|
| 303 |
+
_require(
|
| 304 |
+
fixture["qualified_revision"] == QUALIFIED_REVISION,
|
| 305 |
+
"qualified revision mismatch",
|
| 306 |
+
)
|
| 307 |
+
_require(
|
| 308 |
+
fixture["public_head_revision"] == PUBLIC_HEAD_REVISION,
|
| 309 |
+
"public-head revision mismatch",
|
| 310 |
+
)
|
| 311 |
+
_require(
|
| 312 |
+
qualification_receipt.get("schema_version")
|
| 313 |
+
== "szl.receipt-agent-public-candidate-qualification-receipt.v1",
|
| 314 |
+
"qualification receipt schema mismatch",
|
| 315 |
+
)
|
| 316 |
+
_require(
|
| 317 |
+
receipt_digest(qualification_receipt)
|
| 318 |
+
== qualification_receipt.get("receipt_sha256"),
|
| 319 |
+
"qualification receipt self-digest mismatch",
|
| 320 |
+
)
|
| 321 |
+
_require(
|
| 322 |
+
qualification_receipt.get("receipt_sha256")
|
| 323 |
+
== fixture["qualification_receipt"]["receipt_sha256"],
|
| 324 |
+
"fixture does not bind the qualification receipt",
|
| 325 |
+
)
|
| 326 |
+
_require(
|
| 327 |
+
qualification_receipt.get("result") == "PASS"
|
| 328 |
+
and qualification_receipt.get("maturity") == "MEASURED",
|
| 329 |
+
"qualification is not a measured pass",
|
| 330 |
+
)
|
| 331 |
+
_require(
|
| 332 |
+
qualification_receipt["candidate"]["repository"] == REPOSITORY
|
| 333 |
+
and qualification_receipt["candidate"]["revision"] == QUALIFIED_REVISION,
|
| 334 |
+
"qualification candidate identity mismatch",
|
| 335 |
+
)
|
| 336 |
+
|
| 337 |
+
signature_evidence = fixture["signature_evidence"]
|
| 338 |
+
_strict_keys(
|
| 339 |
+
signature_evidence,
|
| 340 |
+
{"training", "evaluation", "trust_boundary"},
|
| 341 |
+
"signature_evidence",
|
| 342 |
+
)
|
| 343 |
+
for kind in ("training", "evaluation"):
|
| 344 |
+
frozen = signature_evidence[kind]
|
| 345 |
+
receipt_value = qualification_receipt["candidate"][f"{kind}_receipt"]
|
| 346 |
+
_require(frozen["verified"] is True, f"{kind} signature is not verified")
|
| 347 |
+
_require(
|
| 348 |
+
frozen["verified"] == receipt_value["verified"]
|
| 349 |
+
and frozen["key_id"] == receipt_value["key_id"]
|
| 350 |
+
and frozen["canonical_sha256"] == receipt_value["canonical_sha256"],
|
| 351 |
+
f"{kind} signature evidence differs from the qualification receipt",
|
| 352 |
+
)
|
| 353 |
+
_require(
|
| 354 |
+
signature_evidence["trust_boundary"]
|
| 355 |
+
== "REPOSITORY_DECLARED_KEY_NOT_INDEPENDENTLY_PINNED",
|
| 356 |
+
"signature trust boundary was overstated",
|
| 357 |
+
)
|
| 358 |
+
|
| 359 |
+
vectors = fixture["digest_test_vectors"]
|
| 360 |
+
_require(len(vectors) == 2, "exactly two digest-domain vectors are required")
|
| 361 |
+
for vector in vectors:
|
| 362 |
+
_verify_digest_vector(vector)
|
| 363 |
+
|
| 364 |
+
git_evidence = fixture["git_object_evidence"]
|
| 365 |
+
_strict_keys(
|
| 366 |
+
git_evidence,
|
| 367 |
+
{"qualified", "public_head"},
|
| 368 |
+
"git_object_evidence",
|
| 369 |
+
)
|
| 370 |
+
qualified_tree_blobs = _verify_revision_git_evidence(
|
| 371 |
+
git_evidence["qualified"],
|
| 372 |
+
label="qualified",
|
| 373 |
+
expected_revision=QUALIFIED_REVISION,
|
| 374 |
+
)
|
| 375 |
+
public_head_tree_blobs = _verify_revision_git_evidence(
|
| 376 |
+
git_evidence["public_head"],
|
| 377 |
+
label="public-head",
|
| 378 |
+
expected_revision=PUBLIC_HEAD_REVISION,
|
| 379 |
+
)
|
| 380 |
+
|
| 381 |
+
entries = fixture["inference_bearing_blobs"]
|
| 382 |
+
_require(isinstance(entries, list), "inference-bearing blob inventory must be a list")
|
| 383 |
+
paths = {entry["path"] for entry in entries}
|
| 384 |
+
_require(paths == INFERENCE_BEARING_PATHS, "inference-bearing path set mismatch")
|
| 385 |
+
_require(len(paths) == len(entries), "duplicate inference-bearing path")
|
| 386 |
+
for entry in entries:
|
| 387 |
+
_strict_keys(
|
| 388 |
+
entry,
|
| 389 |
+
{
|
| 390 |
+
"path",
|
| 391 |
+
"qualified_git_blob_sha1",
|
| 392 |
+
"public_head_git_blob_sha1",
|
| 393 |
+
"inference_bearing",
|
| 394 |
+
},
|
| 395 |
+
f"inference blob {entry.get('path')}",
|
| 396 |
+
)
|
| 397 |
+
_require(entry["inference_bearing"] is True, "inference path mislabeled")
|
| 398 |
+
_require(
|
| 399 |
+
_is_hex(entry["qualified_git_blob_sha1"], 40)
|
| 400 |
+
and _is_hex(entry["public_head_git_blob_sha1"], 40),
|
| 401 |
+
f"invalid Git blob identity: {entry['path']}",
|
| 402 |
+
)
|
| 403 |
+
_require(
|
| 404 |
+
entry["qualified_git_blob_sha1"] == entry["public_head_git_blob_sha1"],
|
| 405 |
+
f"inference-bearing public-head drift: {entry['path']}",
|
| 406 |
+
)
|
| 407 |
+
_require(
|
| 408 |
+
qualified_tree_blobs.get(entry["path"])
|
| 409 |
+
== entry["qualified_git_blob_sha1"],
|
| 410 |
+
f"qualified path/blob is not bound to the declared revision: {entry['path']}",
|
| 411 |
+
)
|
| 412 |
+
_require(
|
| 413 |
+
public_head_tree_blobs.get(entry["path"])
|
| 414 |
+
== entry["public_head_git_blob_sha1"],
|
| 415 |
+
f"public-head path/blob is not bound to the declared revision: {entry['path']}",
|
| 416 |
+
)
|
| 417 |
+
|
| 418 |
+
candidate = qualification_receipt["candidate"]
|
| 419 |
+
fixture_binding = fixture["qualification_receipt"]["artifact_binding"]
|
| 420 |
+
_strict_keys(
|
| 421 |
+
fixture_binding,
|
| 422 |
+
{"model_file", "adapter_file", "digest_domain"},
|
| 423 |
+
"qualification_receipt.artifact_binding",
|
| 424 |
+
)
|
| 425 |
+
_require(
|
| 426 |
+
fixture_binding["digest_domain"]
|
| 427 |
+
== "SHA256_UTF8_BASENAME_CONCAT_RAW_FILE_BYTES",
|
| 428 |
+
"artifact digest domain mismatch",
|
| 429 |
+
)
|
| 430 |
+
entry_by_path = {entry["path"]: entry for entry in entries}
|
| 431 |
+
lfs_pointer_blobs: dict[str, str] = {}
|
| 432 |
+
for key in ("model_file", "adapter_file"):
|
| 433 |
+
frozen = fixture_binding[key]
|
| 434 |
+
measured = candidate[key]
|
| 435 |
+
_require(
|
| 436 |
+
frozen["path"] == measured["path"]
|
| 437 |
+
and frozen["bytes"] == measured["bytes"]
|
| 438 |
+
and frozen["raw_sha256"] == measured["sha256"]
|
| 439 |
+
and frozen["receipt_directory_sha256"]
|
| 440 |
+
== measured["receipt_directory_sha256"],
|
| 441 |
+
f"{key} differs from the measured qualification receipt",
|
| 442 |
+
)
|
| 443 |
+
_require(
|
| 444 |
+
frozen["raw_sha256"] != frozen["receipt_directory_sha256"],
|
| 445 |
+
f"{key} digest domains were collapsed",
|
| 446 |
+
)
|
| 447 |
+
_require(
|
| 448 |
+
_is_hex(frozen["raw_sha256"], 64),
|
| 449 |
+
f"{key} raw SHA-256 is invalid",
|
| 450 |
+
)
|
| 451 |
+
_require(
|
| 452 |
+
isinstance(frozen["bytes"], int)
|
| 453 |
+
and not isinstance(frozen["bytes"], bool)
|
| 454 |
+
and frozen["bytes"] > 0,
|
| 455 |
+
f"{key} byte count is invalid",
|
| 456 |
+
)
|
| 457 |
+
recorded_blob = entry_by_path.get(frozen["path"])
|
| 458 |
+
_require(
|
| 459 |
+
recorded_blob is not None,
|
| 460 |
+
f"{key} is absent from the frozen Git tree",
|
| 461 |
+
)
|
| 462 |
+
pointer_blob = lfs_pointer_git_blob_sha1(
|
| 463 |
+
frozen["raw_sha256"],
|
| 464 |
+
frozen["bytes"],
|
| 465 |
+
)
|
| 466 |
+
_require(
|
| 467 |
+
pointer_blob == recorded_blob["qualified_git_blob_sha1"],
|
| 468 |
+
f"{key} raw artifact claim does not bind to the frozen LFS pointer",
|
| 469 |
+
)
|
| 470 |
+
lfs_pointer_blobs[key] = pointer_blob
|
| 471 |
+
|
| 472 |
+
delta = fixture["public_head_delta"]
|
| 473 |
+
_require(
|
| 474 |
+
delta
|
| 475 |
+
== [
|
| 476 |
+
{
|
| 477 |
+
"path": "SZL_ESTATE_MANAGED.json",
|
| 478 |
+
"change": "ADDED",
|
| 479 |
+
"inference_bearing": False,
|
| 480 |
+
}
|
| 481 |
+
],
|
| 482 |
+
"public-head delta is not the frozen metadata-only change",
|
| 483 |
+
)
|
| 484 |
+
_require(
|
| 485 |
+
set(public_head_tree_blobs) - set(qualified_tree_blobs)
|
| 486 |
+
== {"SZL_ESTATE_MANAGED.json"}
|
| 487 |
+
and set(qualified_tree_blobs) - set(public_head_tree_blobs) == set()
|
| 488 |
+
and all(
|
| 489 |
+
qualified_tree_blobs[path] == public_head_tree_blobs[path]
|
| 490 |
+
for path in qualified_tree_blobs
|
| 491 |
+
),
|
| 492 |
+
"complete revision tree delta is not the frozen metadata-only addition",
|
| 493 |
+
)
|
| 494 |
+
_require(
|
| 495 |
+
fixture["authorization"]
|
| 496 |
+
== {
|
| 497 |
+
"trained": False,
|
| 498 |
+
"uploaded": False,
|
| 499 |
+
"promoted": False,
|
| 500 |
+
"deployed": False,
|
| 501 |
+
},
|
| 502 |
+
"reconciliation cannot authorize training, upload, promotion, or deployment",
|
| 503 |
+
)
|
| 504 |
+
|
| 505 |
+
result: dict[str, Any] = {
|
| 506 |
+
"schema_version": SCHEMA_VERSION,
|
| 507 |
+
"state": RECONCILED_STATE,
|
| 508 |
+
"maturity": "MEASURED",
|
| 509 |
+
"observed_at": fixture["observed_at"],
|
| 510 |
+
"repository": REPOSITORY,
|
| 511 |
+
"qualified_revision": QUALIFIED_REVISION,
|
| 512 |
+
"public_head_revision": PUBLIC_HEAD_REVISION,
|
| 513 |
+
"receipt_signature_validity": {
|
| 514 |
+
"training": True,
|
| 515 |
+
"evaluation": True,
|
| 516 |
+
"key_id": signature_evidence["training"]["key_id"],
|
| 517 |
+
"trust_boundary": signature_evidence["trust_boundary"],
|
| 518 |
+
},
|
| 519 |
+
"exact_qualified_artifact_binding": {
|
| 520 |
+
"verified": True,
|
| 521 |
+
"qualification_receipt_sha256": qualification_receipt["receipt_sha256"],
|
| 522 |
+
"model_raw_sha256": candidate["model_file"]["sha256"],
|
| 523 |
+
"model_receipt_directory_sha256": candidate["model_file"][
|
| 524 |
+
"receipt_directory_sha256"
|
| 525 |
+
],
|
| 526 |
+
"adapter_raw_sha256": candidate["adapter_file"]["sha256"],
|
| 527 |
+
"adapter_receipt_directory_sha256": candidate["adapter_file"][
|
| 528 |
+
"receipt_directory_sha256"
|
| 529 |
+
],
|
| 530 |
+
"model_lfs_pointer_git_blob_sha1": lfs_pointer_blobs["model_file"],
|
| 531 |
+
"adapter_lfs_pointer_git_blob_sha1": lfs_pointer_blobs["adapter_file"],
|
| 532 |
+
"digest_domain": fixture_binding["digest_domain"],
|
| 533 |
+
},
|
| 534 |
+
"current_public_head_equivalence": {
|
| 535 |
+
"verified": True,
|
| 536 |
+
"qualified_commit_git_object_verified": True,
|
| 537 |
+
"public_head_commit_git_object_verified": True,
|
| 538 |
+
"complete_revision_tree_delta_verified": True,
|
| 539 |
+
"inference_bearing_blob_count": len(entries),
|
| 540 |
+
"all_inference_bearing_git_blobs_equal": True,
|
| 541 |
+
"non_inference_delta": delta,
|
| 542 |
+
},
|
| 543 |
+
"authorization": fixture["authorization"],
|
| 544 |
+
"non_claims": fixture["non_claims"],
|
| 545 |
+
"fixture_sha256": sha256_bytes(canonical_bytes(fixture)),
|
| 546 |
+
}
|
| 547 |
+
result["reconciliation_sha256"] = self_digest(result, "reconciliation_sha256")
|
| 548 |
+
return result
|
| 549 |
+
|
| 550 |
+
|
| 551 |
+
def main() -> int:
|
| 552 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 553 |
+
parser.add_argument("--fixture", type=Path, required=True)
|
| 554 |
+
parser.add_argument("--qualification-receipt", type=Path, required=True)
|
| 555 |
+
parser.add_argument("--output", type=Path)
|
| 556 |
+
parser.add_argument("--check", type=Path)
|
| 557 |
+
args = parser.parse_args()
|
| 558 |
+
|
| 559 |
+
try:
|
| 560 |
+
result = reconcile(load_json(args.fixture), load_json(args.qualification_receipt))
|
| 561 |
+
if args.check:
|
| 562 |
+
expected = load_json(args.check)
|
| 563 |
+
_require(expected == result, "stored reconciliation artifact is stale")
|
| 564 |
+
if args.output:
|
| 565 |
+
args.output.parent.mkdir(parents=True, exist_ok=True)
|
| 566 |
+
args.output.write_text(
|
| 567 |
+
json.dumps(result, indent=2, ensure_ascii=False) + "\n",
|
| 568 |
+
encoding="utf-8",
|
| 569 |
+
)
|
| 570 |
+
print(json.dumps(result, sort_keys=True, separators=(",", ":")))
|
| 571 |
+
return 0
|
| 572 |
+
except (OSError, ValueError, json.JSONDecodeError, ReconciliationRefusal) as exc:
|
| 573 |
+
print(f"REFUSED: {exc}")
|
| 574 |
+
return 2
|
| 575 |
+
|
| 576 |
+
|
| 577 |
+
if __name__ == "__main__":
|
| 578 |
+
raise SystemExit(main())
|