diff --git a/integrations/alakris-authority-comparison/README.md b/integrations/alakris-authority-comparison/README.md index 1c71563d..46033d15 100644 --- a/integrations/alakris-authority-comparison/README.md +++ b/integrations/alakris-authority-comparison/README.md @@ -13,7 +13,7 @@ python3.12 -m venv .venv .venv/bin/python check_package.py --fetch ``` -The single check command downloads pinned fixtures, runs the tests, writes author appraisals, verifies the corrected Proofable packet, preserves the historical Proofable hash failure, and validates TRACE reference shapes. To rerun individual stages: +The single check command downloads pinned fixtures, runs the tests normally and under `-O`, validates the earlier author packets while retaining their historical reports, writes the separate positive fetch appraisal, preserves the historical Proofable hash failure, and validates TRACE reference shapes. To rerun individual stages (the `runner.py` commands explicitly regenerate the named historical appraisal): ```sh .venv/bin/python fetch_fixtures.py --out fixtures @@ -24,6 +24,7 @@ The single check command downloads pinned fixtures, runs the tests, writes autho .venv/bin/python runner.py mintid --evidence fixtures/mintid \ --record revocation-trace-local-20261005T094532Z --output results/mintid-author-local.json .venv/bin/python runner.py proofable --evidence fixtures/proofable --output results/proofable-author-oct8.json +.venv/bin/python proofable_fetch.py --input fixtures/proofable-fetch --output results/proofable-fetch-author.json .venv/bin/python create_references.py --lock fixtures/download-lock.json --output results/trace-reference-shapes.json ``` @@ -64,6 +65,20 @@ The corrected Proofable Oct 8 packet discloses 11 portable envelopes. The adapte The verifier is vendored unchanged from the reviewed reader revision `2b54ee47491f0d1a86c7d9f47d434f8f02622043`; see `PORTABLE_ENVELOPE_NOTICE.txt` and its retained Apache-2.0 license. `fixtures/proofable` uses docs commit `9851059900e29ba701bb0f4df2bf89a2107f430c`. `fixtures/proofable-historical` preserves the earlier `3a45f026` packet, and `results/proofable-author.json` preserves its checksum FAIL. New results are written separately to `results/proofable-author-oct8.json`. +### Separate Proofable positive fetch appraisal + +`proofable_fetch.py` reads the ten unmodified files in [the retained Oct 24 fetch packet](https://github.com/proofable/docs/tree/e6eb5b9307be24a5d82cc7490f647f0c98e2d24a/public/evidence/aaif/authority-at-dispatch/2026-10-24-fetch-route), at exact publication `e6eb5b9307be24a5d82cc7490f647f0c98e2d24a`. Its per-file SHA-256 pins include the publisher's `SHA256SUMS`, README and verifier script. Downloaded scripts are retained as bytes and never executed. The separate eight-case served run on revision `61724c9f...` is not an input to this adapter. + +The command above exits **0** only for the fixed bytes, exact nine-entry checksum inventory, two valid EIP-191/qHash/DID-chain envelopes, matching declared signer/context, request projection and retained response/job consistency checks. Source-file digests in the output identify the tested appraiser and its helper/test bytes. Unsafe or missing paths, oversized files, duplicate JSON keys (including JSON inside signed content), nonfinite constants and ambiguous SSE messages fail closed. No vendor API, target URL, credential or downloaded code is used. Output must be outside the input packet and is replaced atomically from a fresh temporary file, so an existing output hard link cannot overwrite a packet inode. + +The publisher reports hosted MCP `0.1.7`, service revision `18433a48d74af913550de7a018f02c7c724cc1c9`, and job `d259c7cc-0701-4624-a148-77b149f84e06`. These deployment fields are unsigned publisher claims, not independent binary attestation. The signed dispatch statement commits to SHA-256 of the documented `JSON.stringify` projection, in its specified key order. The URL occurs in `prompt`, the acceptance criterion is the generic `target-ack`, and the full returned marker is signed only in the terminal receipt. `runNowFirst` and the `acceptance.criteria` alias are outside that digest projection. The original publisher README's marker wording is retained unchanged; the appraisal does not infer a precommitment to the full marker. + +`integrity: PASS` means fixed artifact bytes and checksum coverage match. `positive_dispatch_appraisal: PASS` additionally means the signature, request and correlation checks pass. **Neither means scenario conformance.** The four existing scenario cases each remain `UNMET`, with all fourteen fields and explicit missing-measurement reasons. EO01 is unestablished and `not_measured` for this positive run; the historical appraisals and their earlier N/A fields are not rewritten or treated as a passing EO01 result. + +The signer is bound to its declared DID/wallet, but attribution of that wallet to Proofable is `not_measured` without a separately verified canonical trust source. No caller-provided wallet flag can promote that status. Grant and agent-identity proof records are absent. The decision's `jobId` is null; the unsigned create response correlates its qHash to the job ID, and identical requests share a digest. The service reports one GET and a returned marker; there is no executor-signed GET, retained external target ingress log, independent target witness, target-commit authority check or revocation-latency measurement. Service acceptance/completeness flags do not override these gaps. `independent_live_reproduction` and `independent_comparison_completed` remain false. + +Negative controls independently generate signed envelopes using local test keys. They check altered request digests, a different validly signed receipt request, bad signatures/qHashes/DID bindings, different valid signers/chains/principals and unsigned decision/job join mismatches independently of outer artifact hashes. A separate tamper control keeps semantic checks valid while the publication hash fails. These controls exercise the offline appraiser, not Proofable's live enforcement. + ## Native implementation runs Use the vendor's pinned release and official sandbox only, with synthetic identities. The MintID public command is: diff --git a/integrations/alakris-authority-comparison/RESULTS.md b/integrations/alakris-authority-comparison/RESULTS.md index afe97727..989341a7 100644 --- a/integrations/alakris-authority-comparison/RESULTS.md +++ b/integrations/alakris-authority-comparison/RESULTS.md @@ -6,6 +6,8 @@ This contribution is an evidence appraiser and synthetic boundary fixture, with ## Package checks +The figures in this section describe the original 2026-10-06 run. The separate 2026-10-27 appraisal below records the current package check scope; original author and native-run reports remain unchanged. + - Environment: isolated Frankfurt Linux task checkout, Python 3.12.15. - Released dependency: `agentrust-trace==0.11.0`. Inputs are `requirements.txt`; reproducible dependency versions and distribution hashes are in `requirements.lock`. Actual installed versions are in `results/python-packages.txt`. - Command: `python check_package.py --fetch` (or without `--fetch` to reuse pinned downloaded bytes). @@ -63,3 +65,31 @@ Retry raw JSONL SHA-256: `e0618e6fca7322f7e8168ce48b6ebbe8fc9a5a7b8dfdf6bc305254 - Implementation-side dispatch/effect adapters and an independent committed-effect observer, including lost response after a committed write. - Same-input matched runs, original deadlines/fallback scope, retries/duplicates and useful work lost. - Maintainer review, upstream issue/PR publication and links from AAIF WG #13. No Verified badge is requested by this packet. + +## Separate Proofable positive-fetch appraisal — 2026-10-27 + +Input: `proofable/docs@e6eb5b9307be24a5d82cc7490f647f0c98e2d24a`, `public/evidence/aaif/authority-at-dispatch/2026-10-24-fetch-route`. All ten original files are retained by the downloader, with fixed SHA-256 pins in `proofable_fetch.py` and immutable source URLs in `fixtures/download-lock.json`. Output: `results/proofable-fetch-author.json`, separate from the historical and corrected Oct 8 reports. Exact appraiser/helper/test source hashes are included in the new report. + +Commands in a fresh Python 3.12 hash-locked environment: + +```sh +.venv/bin/python check_package.py --fetch +.venv/bin/python proofable_fetch.py --input fixtures/proofable-fetch --output results/proofable-fetch-author.json +``` + +`check_package.py` runs the complete unittest suite normally and under `-O`; the retained transcript is `results/tests.txt`, and installed dependency versions are `results/python-packages.txt`. The pinned fetch appraisal requires ten matching byte hashes, exact publisher checksum coverage, both envelope signatures/qHashes/DID-chain bindings, same declared signer/context, both request-digest reconstructions and unsigned create/job consistency. The generated-key negative controls test these checks independently of fixed artifact hashes. TRACE validation now covers all thirty immutable source pointers and still emits no TRACE record or attestation. + +| Result dimension | Meaning retained in the new report | +|---|---| +| Fixed publication integrity | Ten pinned artifacts and exact nine-entry publisher checksum inventory. | +| Positive dispatch appraisal | Offline validation of two signed statements and request/correlation consistency; not a hosted replay. | +| Deployment | Publisher claims MCP `0.1.7`, revision `18433a48d74af913550de7a018f02c7c724cc1c9`; no binary attestation. | +| Service signing key | Signer/DID consistency verified; attribution to Proofable remains `not_measured`. | +| Request commitment | Unique URL and generic criterion are committed; full returned marker is signed only in terminal receipt. | +| Decision to job | Decision `jobId=null`; create-response join is unsigned and correlation-only. | +| Authority chain | Grant and agent-identity qHash references present; their proof records and validation absent. | +| Four comparison scenarios | All remain `UNMET`; a positive fetch establishes no veto, stale/revoked, unreachable-authority or post-dispatch denial result. | +| EO01, target effects, latency | `not_measured`/unestablished; no operator-owned observer, target ingress log, commit-time recheck or revocation timing. | +| Execution custody | Proofable-operated run, local offline reader; independent live reproduction and independent comparison remain false. | + +The source publisher README is preserved byte-identically despite its inaccurate statement that the full marker is inside the prompt. The request contains only the unique URL and generic `target-ack` criterion. The separate eight-case served packet from revision `61724c9f...` is not imported or combined with this run. Existing historical checksum FAILs, old N/A appraisals, original raw inputs and native MintID histories retain their original meaning. The check command validates old source packets afresh without rewriting the four historical author JSON reports; those committed outputs remain byte-identical rather than importing unrelated field-state changes from later reader versions. No live service/target call or scenario/operator acceptance is established by these offline checks. diff --git a/integrations/alakris-authority-comparison/check_package.py b/integrations/alakris-authority-comparison/check_package.py index 3d21d320..51257832 100644 --- a/integrations/alakris-authority-comparison/check_package.py +++ b/integrations/alakris-authority-comparison/check_package.py @@ -6,6 +6,7 @@ import sys from runner import mintid, proofable +from proofable_fetch import appraise as proofable_fetch ROOT = Path(__file__).resolve().parent @@ -15,24 +16,25 @@ def main(): args = parser.parse_args() if args.fetch: subprocess.run([sys.executable, 'fetch_fixtures.py', '--out', 'fixtures'], cwd=ROOT, check=True) - for path in ('fixtures/download-lock.json', 'fixtures/proofable/manifest.json', 'fixtures/mintid'): + for path in ('fixtures/download-lock.json', 'fixtures/proofable/manifest.json', 'fixtures/mintid', 'fixtures/proofable-fetch/SHA256SUMS'): if not (ROOT / path).exists(): parser.error('fixtures are required; rerun with --fetch') results = ROOT / 'results' results.mkdir(exist_ok=True) with (results / 'tests.txt').open('w') as output: - subprocess.run([sys.executable, '-m', 'unittest', '-v'], cwd=ROOT, stdout=output, stderr=subprocess.STDOUT, check=True) + for flags in ([], ['-O']): + output.write('Command: python ' + ' '.join(flags + ['-m', 'unittest', '-v']) + '\n') + output.flush() + subprocess.run([sys.executable, *flags, '-m', 'unittest', '-v'], cwd=ROOT, stdout=output, stderr=subprocess.STDOUT, check=True) with (results / 'synthetic-reference.json').open('w') as output: subprocess.run([sys.executable, 'reference_adapter.py'], cwd=ROOT, stdout=output, check=True) records = [('revocation-trace-testnet-20261006T073712Z', 'mintid-author-testnet.json'), ('revocation-trace-local-20261005T094532Z', 'mintid-author-local.json')] for name, filename in records: report = mintid(ROOT / 'fixtures/mintid', name) - (results / filename).write_text(json.dumps(report, indent=2) + '\n') if report['integrity'] != 'PASS': raise RuntimeError('MintID publisher bytes changed: ' + name) report = proofable(ROOT / 'fixtures/proofable-historical') - (results / 'proofable-author.json').write_text(json.dumps(report, indent=2) + '\n') mismatches = [c for c in report['checks'] if not c['match']] # Both SHA256SUMS and manifest assert the same trace hash. Preserve both failures. expected = 'd749cf16fe8dd599f528e21e95863b930004a0b3f0a9882695e3d3b03e8c2183' @@ -41,12 +43,22 @@ def main(): if report['integrity'] != 'FAIL' or not known or not report['record_count_consistent']: raise RuntimeError('Pinned Proofable evidence changed; review rather than replace expected hashes') corrected = proofable(ROOT / 'fixtures/proofable') - (results / 'proofable-author-oct8.json').write_text(json.dumps(corrected, indent=2) + '\n') + # These existing author reports are historical records. Validate their source + # packets afresh, but do not replace past appraisals with today's field states. if corrected['integrity'] != 'PASS' or corrected['envelope_appraisal']['valid'] is not True: raise RuntimeError('Corrected Proofable appraisal failed') + fetch_report = proofable_fetch(ROOT / 'fixtures/proofable-fetch') + (results / 'proofable-fetch-author.json').write_text(json.dumps(fetch_report, indent=2) + '\n') + if fetch_report['integrity'] != 'PASS' or fetch_report['positive_dispatch_appraisal'] != 'PASS': + raise RuntimeError('Pinned Proofable fetch appraisal failed') + if fetch_report['scenario_conformance'] != 'UNMET' or fetch_report['independent_live_reproduction']: + raise RuntimeError('Positive fetch cannot establish independent scenario conformance') subprocess.run([sys.executable, 'create_references.py', '--lock', 'fixtures/download-lock.json', '--output', 'results/trace-reference-shapes.json'], cwd=ROOT, check=True) + with (results / 'python-packages.txt').open('w') as output: + subprocess.run([sys.executable, '-m', 'pip', 'freeze'], cwd=ROOT, stdout=output, check=True) print(json.dumps({'package_checks': 'PASS', 'mintid_author_bytes': 'PASS', 'proofable_author_bytes': 'FAIL', 'known_failure_preserved': True, 'proofable_oct8_bytes_and_envelopes': 'PASS', + 'proofable_fetch_positive_dispatch': 'PASS', 'proofable_fetch_four_scenarios': 'UNMET', 'independent_comparison_completed': False})) if __name__ == '__main__': diff --git a/integrations/alakris-authority-comparison/fetch_fixtures.py b/integrations/alakris-authority-comparison/fetch_fixtures.py index 7f1cf801..9e2a7a67 100644 --- a/integrations/alakris-authority-comparison/fetch_fixtures.py +++ b/integrations/alakris-authority-comparison/fetch_fixtures.py @@ -5,6 +5,8 @@ from pathlib import Path import urllib.request +from proofable_fetch import PINS as FETCH_PINS, PUBLICATION as FETCH_PUBLICATION, SOURCE_URL as FETCH_SOURCE_URL + PROOFABLE = '9851059900e29ba701bb0f4df2bf89a2107f430c' HISTORICAL_PROOFABLE = '3a45f026fd0c06b9d1d411c053597584b6b1c3dc' MINTID = 'b678e5cd6b5736e1795fd18d497fd8e501272dcd' @@ -22,10 +24,15 @@ def fetch(out): for record in RECORDS: for suffix in ['.jsonl', '.md', '.manifest.json']: files['mintid/' + record + suffix] = f'https://gitlab.com/mintid/mintid/-/raw/{MINTID}/test-harness/trace/records/{record}{suffix}' - lock = {'proofable_docs_commit': PROOFABLE, 'mintid_public_commit': MINTID, 'files': []} + for name in FETCH_PINS: + files['proofable-fetch/' + name] = FETCH_SOURCE_URL + '/' + name + lock = {'proofable_docs_commit': PROOFABLE, 'mintid_public_commit': MINTID, + 'proofable_fetch_docs_commit': FETCH_PUBLICATION, 'files': []} for name, url in files.items(): with urllib.request.urlopen(url, timeout=60) as response: content = response.read() + if name.startswith('proofable-fetch/') and hashlib.sha256(content).hexdigest() != FETCH_PINS[Path(name).name]: + raise ValueError('Pinned Proofable fetch bytes changed: ' + name) path = out / name path.parent.mkdir(parents=True, exist_ok=True) path.write_bytes(content) diff --git a/integrations/alakris-authority-comparison/proofable_fetch.py b/integrations/alakris-authority-comparison/proofable_fetch.py new file mode 100644 index 00000000..8534b621 --- /dev/null +++ b/integrations/alakris-authority-comparison/proofable_fetch.py @@ -0,0 +1,635 @@ +"""Bounded offline appraisal of one immutable, publisher-operated positive fetch. + +Packet integrity and signed statements are distinct from scenario conformance, +service-key attribution, independent execution and target-side effect evidence. +No downloaded code, packet URL, vendor API or target is invoked by this module. +""" + +import argparse +import hashlib +import json +import math +import os +from pathlib import Path +import re +import tempfile + +from portable_envelope import verify +from runner import CASES, REQUIRED_FIELDS, digest, field, validate_report + +PUBLICATION = "e6eb5b9307be24a5d82cc7490f647f0c98e2d24a" +SOURCE_PATH = "public/evidence/aaif/authority-at-dispatch/2026-10-24-fetch-route" +SOURCE_URL = ( + f"https://raw.githubusercontent.com/proofable/docs/{PUBLICATION}/{SOURCE_PATH}" +) +MAX_FILE_BYTES = 1_048_576 +# Independently retained pins, including the publisher's checksum inventory itself. +PINS = { + "1-invocation.json": "566b2cc841a1c4966bf56ca98f21c4a479476a97172e3287c1572a2f0a59c871", + "2-request-arguments.json": "8dd4cf11e11a29347ba1d73921a2b2fce0a8ac3ccac56aea860bbf6778d95da7", + "3-create-response.txt": "2db3795531b68a1f627d5e38d0a99783a2c789fcea0802629a6e4d85466fa1a8", + "4-dispatch-decision.json": "2970e1c6a1e7291d36bcb752e1b7b2c9416c24eb6e6c699c1dcfd19c2b2e6b5f", + "5-job-record.json": "40e0bb4a7d75396182b026884f83c350316fa3be6b00bff2ce9ea3cac146d415", + "6-terminal-receipt.json": "34688c283148a0524b550666fa74839b38410c26fd63425734a2753ba89f040c", + "README.md": "57dcce249c71c083f43f70f25bae4bf24d0904a201a664da3f4c181c43aeceed", + "SHA256SUMS": "0a47e2b25bdf5959a774df690098a0d22f73e7d31453099ae53b45c370858f5a", + "health-at-run.json": "b6849d19f65356cb3da0a1d0d4e840c1e1244830c9c2a637641f0cf7cf468616", + "verify.mjs": "d1919890e1d5ff3c5da0f552c90e09ef672e514c7f2dc3e35c91276358c926a6", +} +ADDRESS = re.compile(r"0x[0-9a-fA-F]{40}\Z") +HASH = re.compile(r"0x[0-9a-f]{64}\Z") +MODULE_ROOT = Path(__file__).resolve().parent + + +def _object(value, label): + if not isinstance(value, dict): + raise ValueError(label + " must be a JSON object") + return value + + +def _text(value, label): + if not isinstance(value, str) or not value: + raise ValueError(label + " must be a nonempty string") + return value + + +def _unique_object(pairs): + result = {} + for key, value in pairs: + if key in result: + raise ValueError("ambiguous JSON: duplicate key " + key) + result[key] = value + return result + + +def _invalid_constant(value): + raise ValueError("invalid JSON constant: " + value) + + +def _json(text, label): + try: + value = json.loads( + text, object_pairs_hook=_unique_object, parse_constant=_invalid_constant + ) + pending = [(value, 0)] + while pending: + item, depth = pending.pop() + if depth > 64: + raise ValueError("JSON nesting exceeds the bounded profile") + if isinstance(item, float) and not math.isfinite(item): + raise ValueError("nonfinite JSON number") + if isinstance(item, dict): + pending.extend((child, depth + 1) for child in item.values()) + elif isinstance(item, list): + pending.extend((child, depth + 1) for child in item) + return value + except (ValueError, RecursionError) as error: + raise ValueError(label + ": " + str(error)) from error + + +def _create_response(text): + lines = [line for line in text.splitlines() if line] + if ( + len(lines) != 2 + or lines[0] != "event: message" + or not lines[1].startswith("data: ") + ): + raise ValueError( + "create response must contain exactly one retained SSE message" + ) + return _object(_json(lines[1][6:], "create response"), "create response") + + +def _records(raw): + records = {} + for name, content in raw.items(): + try: + text = content.decode("utf-8") + except UnicodeDecodeError as error: + raise ValueError(name + ": invalid UTF-8") from error + if name.endswith(".json"): + records[name] = _object(_json(text, name), name) + elif name == "3-create-response.txt": + records[name] = _create_response(text) + return records + + +def _load(base): + base = Path(base) + if not base.is_dir() or base.is_symlink(): + raise ValueError("packet must be an existing, non-symlink directory") + root = base.resolve() + raw = {} + for name in PINS: + path = root / name + if ( + path.is_symlink() + or not path.is_file() + or not path.resolve().is_relative_to(root) + ): + raise ValueError("missing file or unsafe artifact path: " + name) + with path.open("rb") as source: + raw[name] = source.read(MAX_FILE_BYTES + 1) + if len(raw[name]) > MAX_FILE_BYTES: + raise ValueError("packet file exceeds the bounded size: " + name) + return raw + + +def _inventory(content): + result = {} + for line in content.decode("utf-8").splitlines(): + match = re.fullmatch(r"([0-9a-f]{64}) [ *]([^\s]+)", line) + if not match or match[2] not in PINS or match[2] == "SHA256SUMS": + raise ValueError("invalid or unsafe publisher checksum inventory") + if match[2] in result: + raise ValueError("duplicate publisher checksum entry: " + match[2]) + result[match[2]] = match[1] + return result + + +def _js_stringify(value): + """JSON.stringify for this packet's string/bool/null/safe-integer profile. + + No Unicode normalization is applied to the request digest. Floating-point + runtime inputs are outside this bounded profile and rejected, not rounded. + """ + if value is None or isinstance(value, (str, bool)): + text = json.dumps(value, ensure_ascii=False, separators=(",", ":")) + # JS JSON.stringify escapes lone UTF-16 surrogates (well-formed JSON). + return "".join( + f"\\u{ord(c):04x}" if 0xD800 <= ord(c) <= 0xDFFF else c for c in text + ) + if type(value) is int and abs(value) <= 2**53 - 1: + return str(value) + if isinstance(value, list): + return "[" + ",".join(_js_stringify(item) for item in value) + "]" + if isinstance(value, dict): + + def index(key): + return ( + key.isascii() + and key.isdecimal() + and str(int(key)) == key + and int(key) < 2**32 - 1 + ) + + numeric = sorted((key for key in value if index(key)), key=int) + keys = numeric + [key for key in value if not index(key)] + return ( + "{" + + ",".join( + _js_stringify(key) + ":" + _js_stringify(value[key]) for key in keys + ) + + "}" + ) + raise ValueError( + "request projection has a value outside the bounded JSON.stringify profile" + ) + + +def request_digest(fields): + """Recompute the published fixed projection, not a raw-file byte digest.""" + fields = _object(fields, "request fields") + wallet = _text(fields.get("agentWallet"), "agentWallet") + if not ADDRESS.fullmatch(wallet): + raise ValueError("agentWallet is not an EVM address") + acceptance = _object(fields.get("acceptance"), "acceptance") + approval = fields.get("requiresJobApproval", False) + if not isinstance(approval, bool): + raise ValueError("requiresJobApproval must be a boolean") + projection = { + "agentId": _text(fields.get("agentId"), "agentId"), + "agentWallet": wallet.lower(), + "prompt": _text(fields.get("prompt"), "prompt"), + "runtime": fields.get("runtime"), + "requiresJobApproval": approval, + "acceptance": { + "criterion": _text(acceptance.get("criterion"), "acceptance.criterion"), + "mode": _text(acceptance.get("mode"), "acceptance.mode"), + }, + } + return hashlib.sha256(_js_stringify(projection).encode("utf-8")).hexdigest() + + +def _envelope(document, label): + document = _object(document, label) + proofs = _object(document.get("data"), label + ".data").get("proofs") + if ( + document.get("success") is not True + or not isinstance(proofs, list) + or len(proofs) != 1 + ): + raise ValueError(label + " must contain exactly one successful proof") + envelope = _object( + _object(proofs[0], label + ".proof").get("portableProof"), label + ".envelope" + ) + for name in ("did", "walletAddress", "qHash", "signature"): + _text(envelope.get(name), label + "." + name) + if not ADDRESS.fullmatch(envelope["walletAddress"]) or not HASH.fullmatch( + envelope["qHash"] + ): + raise ValueError(label + " has an invalid wallet or qHash") + for name in ("chainId", "signedTimestamp"): + if type(envelope.get(name)) is not int or not 0 < envelope[name] <= 2**53 - 1: + raise ValueError(label + "." + name + " must be a positive safe integer") + verifiers = envelope.get("verifierIds") + if ( + not isinstance(verifiers, list) + or not verifiers + or not all(isinstance(v, str) and v for v in verifiers) + ): + raise ValueError(label + " has invalid verifierIds") + if envelope.get("signatureMethod") != "eip191": + raise ValueError(label + " is not the supported EIP-191 profile") + data = _object(envelope.get("data"), label + ".data") + content = _object( + _json( + _text(data.get("content"), label + ".content"), label + " signed content" + ), + label + " content", + ) + return envelope, content + + +def _semantic_checks(records): + """Diagnostic checks only; appraise() additionally requires all fixed byte pins. + + Keeping semantic checks separate lets generated-key controls test digest, + signature and correlation failures without relying on file-hash failures. + """ + invocation = records["1-invocation.json"] + request = records["2-request-arguments.json"] + response = records["3-create-response.txt"] + result = _object(response.get("result"), "create.result") + create = _object(result.get("structuredContent"), "create.structuredContent") + text_content = result.get("content") + if not isinstance(text_content, list) or len(text_content) != 1: + raise ValueError("create response must have one text content item") + text_item = _object(text_content[0], "create.content") + text_create = _object( + _json(_text(text_item.get("text"), "create.content.text"), "create text JSON"), + "create text", + ) + decision, statement = _envelope( + records["4-dispatch-decision.json"], "dispatch decision" + ) + receipt, terminal = _envelope( + records["6-terminal-receipt.json"], "terminal receipt" + ) + job = records["5-job-record.json"] + health = records["health-at-run.json"] + release = _object(health.get("release"), "health.release") + signed_result = _object(terminal.get("result"), "terminal.result") + zeus = _object(signed_result.get("zeus"), "terminal.result.zeus") + activity = zeus.get("toolActivity") + if not isinstance(activity, list) or len(activity) != 1: + raise ValueError("positive fetch profile requires one reported tool action") + action = _object(activity[0], "tool action") + action_args = _object(action.get("args"), "tool action.args") + prompt = _text(request.get("prompt"), "request.prompt") + url_match = re.fullmatch( + r"Fetch this exact URL once and reply with its visible text verbatim and nothing else: (https://[^\s]+)", + prompt, + ) + if not url_match: + raise ValueError("request is not the retained single-URL fetch profile") + url = url_match[1] + checks = [] + + def check(name, valid, scope): + checks.append({"check": name, "valid": bool(valid), "scope": scope}) + + verdicts = {"decision": verify(decision), "receipt": verify(receipt)} + for name, verdict in verdicts.items(): + check( + name + "_envelope", + verdict["valid"], + "EIP-191/qHash/DID-chain integrity, not service identity", + ) + check( + "same_declared_signer_context", + decision["walletAddress"].lower() + == receipt["walletAddress"].lower() + == str(invocation.get("serviceSigner", "")).lower() + and decision["chainId"] == receipt["chainId"] + and decision["verifierIds"] == receipt["verifierIds"], + "publisher-declared signer/context only", + ) + principal = invocation.get("principal") + for label, value in ( + ("principal", principal), + ("decision owner", decision["data"].get("owner")), + ("receipt owner", receipt["data"].get("owner")), + ("signed principal", statement.get("principal")), + ): + if not ADDRESS.fullmatch(_text(value, label)): + raise ValueError(label + " is not an EVM address") + check( + "same_principal_agent_context", + decision["data"].get("owner") + == receipt["data"].get("owner") + == statement.get("principal") + == principal + and statement.get("agentId") + == terminal.get("agentId") + == request.get("agentId") + == invocation.get("agentId") + == job.get("agentId") + and statement.get("agentWallet") + == terminal.get("agentWallet") + == request.get("agentWallet") + == invocation.get("agentWallet") + == job.get("agentWallet"), + "signed principal/agent fields versus unsigned request/invocation/job", + ) + check( + "dispatch_allow_profile", + statement.get("version") == 1 + and statement.get("kind") == "agent-job-authority-decision" + and statement.get("action") == "job.dispatch" + and statement.get("outcome") == "ALLOW" + and statement.get("jobId") is None + and statement.get("effect") == "none", + "positive dispatch statement, no job/effect attestation", + ) + check( + "decision_reference_context", + decision["data"].get("reference") + == { + "type": "agent-job-authority-decision", + "id": str(statement.get("agentId")) + + ":" + + str(statement.get("requestedAtMs")), + }, + "signed decision reference names this agent/request timestamp", + ) + check( + "referenced_authority_ids", + all( + HASH.fullmatch(str(statement.get(key, ""))) + for key in ("delegationQHash", "agentIdentityQHash") + ), + "references are identifiers only; grant/identity proof validation is unestablished", + ) + check( + "request_args_digest", + request_digest(request) == statement.get("argsDigest"), + "fixed request projection", + ) + check( + "receipt_request_digest", + request_digest(terminal) == statement.get("argsDigest"), + "same projected request in the signed receipt", + ) + check( + "receipt_request_fields", + terminal.get("prompt") == request.get("prompt") + and terminal.get("acceptance") == request.get("acceptance") + and terminal.get("kind") == "agent-job" + and terminal.get("status") == "completed", + "signed terminal report of this request, including unsigned-digest alias consistency", + ) + check( + "create_response_shape", + response.get("jsonrpc") == "2.0" + and type(response.get("id")) is int + and response["id"] == 1 + and result.get("isError") is False + and text_item.get("type") == "text" + and create == text_create + and create.get("success") is True, + "unsigned retained MCP response", + ) + authority_decision = _object( + create.get("authorityDecision"), "create.authorityDecision" + ) + check( + "create_decision_qhash_join", + create.get("authorityDecisionQHash") + == authority_decision.get("qHash") + == decision["qHash"] + and authority_decision.get("outcome") == statement.get("outcome"), + "unsigned create-response correlation, not a signed decision-to-job link", + ) + job_id = _text(terminal.get("jobId"), "terminal.jobId") + check( + "receipt_reference_context", + receipt["data"].get("reference") == {"type": "job", "id": job_id}, + "signed receipt reference names this terminal job", + ) + check( + "create_job_join", + create.get("jobId") == job_id == job.get("jobId") == job.get("runId"), + "unsigned create response/job versus signed terminal jobId", + ) + check( + "job_receipt_consistency", + job.get("qHash") == job.get("terminalQHash") == receipt["qHash"] + and job.get("prompt") == terminal.get("prompt") + and job.get("result") == signed_result + and job.get("status") == terminal.get("status") + and job.get("acceptance") == terminal.get("acceptance"), + "unsigned job record versus signed terminal statement", + ) + check( + "reported_fetch", + action.get("tool") == "proofable_url_fetch" + and action_args.get("url") == url + and action.get("ok") is True, + "service-reported execution, not an executor or target signature", + ) + marker = signed_result.get("response") + check( + "reported_marker", + isinstance(marker, str) + and re.fullmatch(r"target-ack [0-9a-f]{20}", marker) + and request.get("acceptance", {}).get("criterion") == "target-ack", + "full returned marker is signed only in the terminal statement", + ) + check( + "reported_release", + invocation.get("mcpVersion") + == release.get("mcpVersion") + == release.get("imagePackageVersion") + == "0.1.7" + and invocation.get("serviceRevision") + == release.get("deploySha") + == "18433a48d74af913550de7a018f02c7c724cc1c9" + and invocation.get("method") == "tools/call" + and invocation.get("tool") == "proofable_job_create" + and invocation.get("endpoint") == "https://mcp.proofable.me/mcp", + "unsigned publisher deployment metadata, not binary attestation", + ) + return { + "valid": all(check["valid"] for check in checks), + "checks": checks, + "envelopes": verdicts, + "job_id": job_id, + "target_url": url, + "returned_marker": marker, + "declared_service_signer": decision["walletAddress"], + } + + +def appraise(base): + """Appraise only the fixed publication; no trusted-wallet or hash-bypass flag.""" + raw = _load(base) + hash_checks = [ + { + "file": name, + "expected": expected, + "actual": hashlib.sha256(raw[name]).hexdigest(), + "match": hashlib.sha256(raw[name]).hexdigest() == expected, + } + for name, expected in PINS.items() + ] + inventory_valid = _inventory(raw["SHA256SUMS"]) == { + name: value for name, value in PINS.items() if name != "SHA256SUMS" + } + semantic = _semantic_checks(_records(raw)) + integrity = all(check["match"] for check in hash_checks) and inventory_valid + cases = [] + for case in CASES: + reason = ( + "positive author-operated fetch does not exercise the " + case + " scenario" + ) + cases.append( + { + "case_id": case, + "basis": "author_record", + "independent_live_reproduction": False, + "scenario_conformance": "UNMET", + "unmet_requirements": [reason], + "fields": { + name: field("not_measured", reason=reason) + for name in sorted(REQUIRED_FIELDS) + }, + } + ) + sources = ( + "proofable_fetch.py", + "test_proofable_fetch.py", + "portable_envelope.py", + "runner.py", + "fetch_fixtures.py", + "check_package.py", + "comparison-contract.json", + ) + report = { + "schema": "aaif-authority-comparison/v1", + "implementation": "Proofable hosted MCP fetch", + "basis": "author_record", + "integrity": "PASS" if integrity else "FAIL", + "publication": {"commit": PUBLICATION, "path": SOURCE_PATH}, + "publisher_deployment_claim": { + "mcp_version": "0.1.7", + "service_revision": "18433a48d74af913550de7a018f02c7c724cc1c9", + }, + "artifact_hashes": hash_checks, + "publisher_checksum_inventory_valid": inventory_valid, + "positive_dispatch_appraisal": "PASS" + if integrity and semantic["valid"] + else "FAIL", + "positive_dispatch": semantic, + "service_key_attribution": field( + "not_measured", + reason="no separately verified canonical service-key trust source; declared signer integrity is not attribution", + ), + "grant_and_agent_identity_validation": field( + "not_measured", + reason="decision names grant and identity qHashes but their proof records are absent", + ), + "request_binding": { + "projection": [ + "agentId", + "agentWallet (lowercased)", + "prompt", + "runtime (default null)", + "requiresJobApproval (default false)", + "acceptance.criterion", + "acceptance.mode", + ], + "outside_digest_projection": ["runNowFirst", "acceptance.criteria"], + "full_returned_marker_precommitted": False, + "reason": "prompt binds the unique URL, criterion is generic target-ack; full returned marker is signed only in terminal receipt", + }, + "correlation_only_links": [ + "decision jobId=null; unsigned create response joins decision qHash to jobId", + "identical requests share argsDigest; URL uniqueness is correlation, not replay prevention", + "no executor-signed GET; no retained target ingress log", + ], + "unmeasured": { + name: field("not_measured", reason=reason) + for name, reason in { + "target_commit": "signed service completion does not establish target-side committed effects", + "independent_target_witness": "public request-bin logs and an operator-owned observer are absent", + "revocation_latency": "no revocation timing measurement in this positive run", + "commit_time_authority": "dispatch-only; no authority recheck in flight or at target commit", + "EO01_unreachable_authority": "no separately unreachable authority experiment on this locally evaluated path", + }.items() + }, + "evidence_custody": { + "operator": "Proofable", + "observer": "Proofable", + "appraisal": "offline local reader", + "independent_target_observer": False, + }, + "independent_live_reproduction": False, + "independent_comparison_completed": False, + "scenario_conformance": "UNMET", + "cases": cases, + "appraiser_source_sha256": { + name: digest(MODULE_ROOT / name) for name in sources + }, + "trace_record_emitted": False, + } + return validate_report(report) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--input", + type=Path, + required=True, + help="directory containing the ten pinned files", + ) + parser.add_argument( + "--output", type=Path, help="separate appraisal JSON (stdout by default)" + ) + args = parser.parse_args() + try: + report = appraise(args.input) + except (OSError, ValueError, KeyError, TypeError) as error: + parser.error(str(error)) + rendered = json.dumps(report, indent=2, ensure_ascii=False) + "\n" + if args.output: + output = args.output.resolve() + packet = args.input.resolve() + if output.is_relative_to(packet): + parser.error("output must be outside the retained packet directory") + output.parent.mkdir(parents=True, exist_ok=True) + temporary = None + try: + with tempfile.NamedTemporaryFile( + mode="w", + encoding="utf-8", + dir=output.parent, + prefix=".proofable-fetch-", + delete=False, + ) as destination: + temporary = Path(destination.name) + destination.write(rendered) + # Replace the outside pathname, never write its existing inode: an + # output hard link to a packet file must not modify retained bytes. + os.replace(temporary, output) + finally: + if temporary is not None: + temporary.unlink(missing_ok=True) + else: + print(rendered, end="") + return 0 if report["positive_dispatch_appraisal"] == "PASS" else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/integrations/alakris-authority-comparison/results/proofable-fetch-author.json b/integrations/alakris-authority-comparison/results/proofable-fetch-author.json new file mode 100644 index 00000000..2f6eebc6 --- /dev/null +++ b/integrations/alakris-authority-comparison/results/proofable-fetch-author.json @@ -0,0 +1,593 @@ +{ + "schema": "aaif-authority-comparison/v1", + "implementation": "Proofable hosted MCP fetch", + "basis": "author_record", + "integrity": "PASS", + "publication": { + "commit": "e6eb5b9307be24a5d82cc7490f647f0c98e2d24a", + "path": "public/evidence/aaif/authority-at-dispatch/2026-10-24-fetch-route" + }, + "publisher_deployment_claim": { + "mcp_version": "0.1.7", + "service_revision": "18433a48d74af913550de7a018f02c7c724cc1c9" + }, + "artifact_hashes": [ + { + "file": "1-invocation.json", + "expected": "566b2cc841a1c4966bf56ca98f21c4a479476a97172e3287c1572a2f0a59c871", + "actual": "566b2cc841a1c4966bf56ca98f21c4a479476a97172e3287c1572a2f0a59c871", + "match": true + }, + { + "file": "2-request-arguments.json", + "expected": "8dd4cf11e11a29347ba1d73921a2b2fce0a8ac3ccac56aea860bbf6778d95da7", + "actual": "8dd4cf11e11a29347ba1d73921a2b2fce0a8ac3ccac56aea860bbf6778d95da7", + "match": true + }, + { + "file": "3-create-response.txt", + "expected": "2db3795531b68a1f627d5e38d0a99783a2c789fcea0802629a6e4d85466fa1a8", + "actual": "2db3795531b68a1f627d5e38d0a99783a2c789fcea0802629a6e4d85466fa1a8", + "match": true + }, + { + "file": "4-dispatch-decision.json", + "expected": "2970e1c6a1e7291d36bcb752e1b7b2c9416c24eb6e6c699c1dcfd19c2b2e6b5f", + "actual": "2970e1c6a1e7291d36bcb752e1b7b2c9416c24eb6e6c699c1dcfd19c2b2e6b5f", + "match": true + }, + { + "file": "5-job-record.json", + "expected": "40e0bb4a7d75396182b026884f83c350316fa3be6b00bff2ce9ea3cac146d415", + "actual": "40e0bb4a7d75396182b026884f83c350316fa3be6b00bff2ce9ea3cac146d415", + "match": true + }, + { + "file": "6-terminal-receipt.json", + "expected": "34688c283148a0524b550666fa74839b38410c26fd63425734a2753ba89f040c", + "actual": "34688c283148a0524b550666fa74839b38410c26fd63425734a2753ba89f040c", + "match": true + }, + { + "file": "README.md", + "expected": "57dcce249c71c083f43f70f25bae4bf24d0904a201a664da3f4c181c43aeceed", + "actual": "57dcce249c71c083f43f70f25bae4bf24d0904a201a664da3f4c181c43aeceed", + "match": true + }, + { + "file": "SHA256SUMS", + "expected": "0a47e2b25bdf5959a774df690098a0d22f73e7d31453099ae53b45c370858f5a", + "actual": "0a47e2b25bdf5959a774df690098a0d22f73e7d31453099ae53b45c370858f5a", + "match": true + }, + { + "file": "health-at-run.json", + "expected": "b6849d19f65356cb3da0a1d0d4e840c1e1244830c9c2a637641f0cf7cf468616", + "actual": "b6849d19f65356cb3da0a1d0d4e840c1e1244830c9c2a637641f0cf7cf468616", + "match": true + }, + { + "file": "verify.mjs", + "expected": "d1919890e1d5ff3c5da0f552c90e09ef672e514c7f2dc3e35c91276358c926a6", + "actual": "d1919890e1d5ff3c5da0f552c90e09ef672e514c7f2dc3e35c91276358c926a6", + "match": true + } + ], + "publisher_checksum_inventory_valid": true, + "positive_dispatch_appraisal": "PASS", + "positive_dispatch": { + "valid": true, + "checks": [ + { + "check": "decision_envelope", + "valid": true, + "scope": "EIP-191/qHash/DID-chain integrity, not service identity" + }, + { + "check": "receipt_envelope", + "valid": true, + "scope": "EIP-191/qHash/DID-chain integrity, not service identity" + }, + { + "check": "same_declared_signer_context", + "valid": true, + "scope": "publisher-declared signer/context only" + }, + { + "check": "same_principal_agent_context", + "valid": true, + "scope": "signed principal/agent fields versus unsigned request/invocation/job" + }, + { + "check": "dispatch_allow_profile", + "valid": true, + "scope": "positive dispatch statement, no job/effect attestation" + }, + { + "check": "decision_reference_context", + "valid": true, + "scope": "signed decision reference names this agent/request timestamp" + }, + { + "check": "referenced_authority_ids", + "valid": true, + "scope": "references are identifiers only; grant/identity proof validation is unestablished" + }, + { + "check": "request_args_digest", + "valid": true, + "scope": "fixed request projection" + }, + { + "check": "receipt_request_digest", + "valid": true, + "scope": "same projected request in the signed receipt" + }, + { + "check": "receipt_request_fields", + "valid": true, + "scope": "signed terminal report of this request, including unsigned-digest alias consistency" + }, + { + "check": "create_response_shape", + "valid": true, + "scope": "unsigned retained MCP response" + }, + { + "check": "create_decision_qhash_join", + "valid": true, + "scope": "unsigned create-response correlation, not a signed decision-to-job link" + }, + { + "check": "receipt_reference_context", + "valid": true, + "scope": "signed receipt reference names this terminal job" + }, + { + "check": "create_job_join", + "valid": true, + "scope": "unsigned create response/job versus signed terminal jobId" + }, + { + "check": "job_receipt_consistency", + "valid": true, + "scope": "unsigned job record versus signed terminal statement" + }, + { + "check": "reported_fetch", + "valid": true, + "scope": "service-reported execution, not an executor or target signature" + }, + { + "check": "reported_marker", + "valid": true, + "scope": "full returned marker is signed only in the terminal statement" + }, + { + "check": "reported_release", + "valid": true, + "scope": "unsigned publisher deployment metadata, not binary attestation" + } + ], + "envelopes": { + "decision": { + "valid": true, + "signer": "0xA41494e1E5F9E5Eaf786961e6ef84cd1a3071Eec", + "errors": [] + }, + "receipt": { + "valid": true, + "signer": "0xA41494e1E5F9E5Eaf786961e6ef84cd1a3071Eec", + "errors": [] + } + }, + "job_id": "d259c7cc-0701-4624-a148-77b149f84e06", + "target_url": "https://webhook.site/b008feae-131e-45bc-850b-c3a759865b88", + "returned_marker": "target-ack 6b86b1f0cd3adaced3f1", + "declared_service_signer": "0xa41494e1e5f9e5eaf786961e6ef84cd1a3071eec" + }, + "service_key_attribution": { + "state": "not_measured", + "value": null, + "reason": "no separately verified canonical service-key trust source; declared signer integrity is not attribution" + }, + "grant_and_agent_identity_validation": { + "state": "not_measured", + "value": null, + "reason": "decision names grant and identity qHashes but their proof records are absent" + }, + "request_binding": { + "projection": [ + "agentId", + "agentWallet (lowercased)", + "prompt", + "runtime (default null)", + "requiresJobApproval (default false)", + "acceptance.criterion", + "acceptance.mode" + ], + "outside_digest_projection": [ + "runNowFirst", + "acceptance.criteria" + ], + "full_returned_marker_precommitted": false, + "reason": "prompt binds the unique URL, criterion is generic target-ack; full returned marker is signed only in terminal receipt" + }, + "correlation_only_links": [ + "decision jobId=null; unsigned create response joins decision qHash to jobId", + "identical requests share argsDigest; URL uniqueness is correlation, not replay prevention", + "no executor-signed GET; no retained target ingress log" + ], + "unmeasured": { + "target_commit": { + "state": "not_measured", + "value": null, + "reason": "signed service completion does not establish target-side committed effects" + }, + "independent_target_witness": { + "state": "not_measured", + "value": null, + "reason": "public request-bin logs and an operator-owned observer are absent" + }, + "revocation_latency": { + "state": "not_measured", + "value": null, + "reason": "no revocation timing measurement in this positive run" + }, + "commit_time_authority": { + "state": "not_measured", + "value": null, + "reason": "dispatch-only; no authority recheck in flight or at target commit" + }, + "EO01_unreachable_authority": { + "state": "not_measured", + "value": null, + "reason": "no separately unreachable authority experiment on this locally evaluated path" + } + }, + "evidence_custody": { + "operator": "Proofable", + "observer": "Proofable", + "appraisal": "offline local reader", + "independent_target_observer": false + }, + "independent_live_reproduction": false, + "independent_comparison_completed": false, + "scenario_conformance": "UNMET", + "cases": [ + { + "case_id": "binding_veto", + "basis": "author_record", + "independent_live_reproduction": false, + "scenario_conformance": "UNMET", + "unmet_requirements": [ + "positive author-operated fetch does not exercise the binding_veto scenario" + ], + "fields": { + "action_digest": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the binding_veto scenario" + }, + "authority": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the binding_veto scenario" + }, + "committed_effect": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the binding_veto scenario" + }, + "decision": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the binding_veto scenario" + }, + "dispatch": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the binding_veto scenario" + }, + "duplicate_count": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the binding_veto scenario" + }, + "fallback_scope": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the binding_veto scenario" + }, + "independent_effect_witness": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the binding_veto scenario" + }, + "original_deadline": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the binding_veto scenario" + }, + "policy_digest": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the binding_veto scenario" + }, + "receipt": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the binding_veto scenario" + }, + "retry_count": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the binding_veto scenario" + }, + "target_identity": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the binding_veto scenario" + }, + "task_outcome": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the binding_veto scenario" + } + } + }, + { + "case_id": "revoked_stale", + "basis": "author_record", + "independent_live_reproduction": false, + "scenario_conformance": "UNMET", + "unmet_requirements": [ + "positive author-operated fetch does not exercise the revoked_stale scenario" + ], + "fields": { + "action_digest": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the revoked_stale scenario" + }, + "authority": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the revoked_stale scenario" + }, + "committed_effect": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the revoked_stale scenario" + }, + "decision": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the revoked_stale scenario" + }, + "dispatch": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the revoked_stale scenario" + }, + "duplicate_count": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the revoked_stale scenario" + }, + "fallback_scope": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the revoked_stale scenario" + }, + "independent_effect_witness": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the revoked_stale scenario" + }, + "original_deadline": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the revoked_stale scenario" + }, + "policy_digest": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the revoked_stale scenario" + }, + "receipt": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the revoked_stale scenario" + }, + "retry_count": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the revoked_stale scenario" + }, + "target_identity": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the revoked_stale scenario" + }, + "task_outcome": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the revoked_stale scenario" + } + } + }, + { + "case_id": "unreachable", + "basis": "author_record", + "independent_live_reproduction": false, + "scenario_conformance": "UNMET", + "unmet_requirements": [ + "positive author-operated fetch does not exercise the unreachable scenario" + ], + "fields": { + "action_digest": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the unreachable scenario" + }, + "authority": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the unreachable scenario" + }, + "committed_effect": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the unreachable scenario" + }, + "decision": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the unreachable scenario" + }, + "dispatch": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the unreachable scenario" + }, + "duplicate_count": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the unreachable scenario" + }, + "fallback_scope": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the unreachable scenario" + }, + "independent_effect_witness": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the unreachable scenario" + }, + "original_deadline": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the unreachable scenario" + }, + "policy_digest": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the unreachable scenario" + }, + "receipt": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the unreachable scenario" + }, + "retry_count": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the unreachable scenario" + }, + "target_identity": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the unreachable scenario" + }, + "task_outcome": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the unreachable scenario" + } + } + }, + { + "case_id": "post_dispatch_revoke", + "basis": "author_record", + "independent_live_reproduction": false, + "scenario_conformance": "UNMET", + "unmet_requirements": [ + "positive author-operated fetch does not exercise the post_dispatch_revoke scenario" + ], + "fields": { + "action_digest": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the post_dispatch_revoke scenario" + }, + "authority": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the post_dispatch_revoke scenario" + }, + "committed_effect": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the post_dispatch_revoke scenario" + }, + "decision": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the post_dispatch_revoke scenario" + }, + "dispatch": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the post_dispatch_revoke scenario" + }, + "duplicate_count": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the post_dispatch_revoke scenario" + }, + "fallback_scope": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the post_dispatch_revoke scenario" + }, + "independent_effect_witness": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the post_dispatch_revoke scenario" + }, + "original_deadline": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the post_dispatch_revoke scenario" + }, + "policy_digest": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the post_dispatch_revoke scenario" + }, + "receipt": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the post_dispatch_revoke scenario" + }, + "retry_count": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the post_dispatch_revoke scenario" + }, + "target_identity": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the post_dispatch_revoke scenario" + }, + "task_outcome": { + "state": "not_measured", + "value": null, + "reason": "positive author-operated fetch does not exercise the post_dispatch_revoke scenario" + } + } + } + ], + "appraiser_source_sha256": { + "proofable_fetch.py": "db6585702fb9bd820bbdad5321f0b5953a64cdead6c05db08a900e12645cf57c", + "test_proofable_fetch.py": "262d5cf59061d8f443df5274d53a9dcf1f99485b739c78ff8af6e02e4d8e10b0", + "portable_envelope.py": "5622942634508fb7daa02292b9ea4a39d155e1a9928fe1f51461a8caf74e7e42", + "runner.py": "171d0b555a236894561b86ce8d8e8763affd112c1bfe7b250c9e9cc84f4f636e", + "fetch_fixtures.py": "78a9609df497d6895dc8a91de8b87c759e4c330674a1da192c7644540b24c0b9", + "check_package.py": "543586401fd525c9408fd3f13101138654312b6198edf34a236ad64cb0416f1c", + "comparison-contract.json": "62615642a8dac18a44d48746abe8e98e049e144109db64bd14ef7a1c69fac277" + }, + "trace_record_emitted": false +} diff --git a/integrations/alakris-authority-comparison/results/python-packages.txt b/integrations/alakris-authority-comparison/results/python-packages.txt index 5a015d05..d80acff4 100644 --- a/integrations/alakris-authority-comparison/results/python-packages.txt +++ b/integrations/alakris-authority-comparison/results/python-packages.txt @@ -1,18 +1,36 @@ agentrust-trace==0.11.0 annotated-types==0.8.0 attrs==26.1.0 +bitarray==3.11.0 cffi==2.1.1 +ckzg==2.1.8 cryptography==50.0.2 +cytoolz==1.2.0 +eth-account==0.14.0 +eth-hash==0.8.0 +eth-keyfile==0.10.0 +eth-keys==0.8.0 +eth-rlp==3.0.0 +eth-typing==6.0.0 +eth-utils==6.0.0 +eth_abi==6.0.0 +hexbytes==2.0.0 jsonschema==4.26.0 jsonschema-specifications==2025.9.1 packaging==26.3 +parsimonious==0.10.0 +py-ecc==8.0.0 pycparser==3.0 +pycryptodome==3.24.0 pydantic==2.13.5 -pydantic-core==2.46.5 -pyyaml==6.0.3 +pydantic_core==2.46.5 +PyYAML==6.0.3 referencing==0.37.0 +regex==2026.9.29 rfc3986-validator==0.1.1 rfc8785==0.1.4 +rlp==5.0.0 rpds-py==2026.9.1 -typing-extensions==4.16.0 +toolz==1.2.0 typing-inspection==0.4.4 +typing_extensions==4.16.0 diff --git a/integrations/alakris-authority-comparison/results/tests.txt b/integrations/alakris-authority-comparison/results/tests.txt index c5f29336..237f1aba 100644 --- a/integrations/alakris-authority-comparison/results/tests.txt +++ b/integrations/alakris-authority-comparison/results/tests.txt @@ -1,3 +1,24 @@ +Command: python -m unittest -v +test_changed_unsigned_request_digest_fails_with_valid_envelopes (test_proofable_fetch.IndependentCryptoControls.test_changed_unsigned_request_digest_fails_with_valid_envelopes) ... ok +test_did_binding_is_required (test_proofable_fetch.IndependentCryptoControls.test_did_binding_is_required) ... ok +test_different_valid_signer_or_chain_fails_context (test_proofable_fetch.IndependentCryptoControls.test_different_valid_signer_or_chain_fails_context) ... ok +test_different_validly_signed_principal_fails_context (test_proofable_fetch.IndependentCryptoControls.test_different_validly_signed_principal_fails_context) ... ok +test_different_validly_signed_receipt_request_fails_digest (test_proofable_fetch.IndependentCryptoControls.test_different_validly_signed_receipt_request_fails_digest) ... ok +test_fresh_test_key_control_verifies_without_vendor_signatures (test_proofable_fetch.IndependentCryptoControls.test_fresh_test_key_control_verifies_without_vendor_signatures) ... ok +test_unsigned_create_join_mismatches_are_not_promoted_to_attestation (test_proofable_fetch.IndependentCryptoControls.test_unsigned_create_join_mismatches_are_not_promoted_to_attestation) ... ok +test_wrong_signature_and_qhash_fail_independently (test_proofable_fetch.IndependentCryptoControls.test_wrong_signature_and_qhash_fail_independently) ... ok +test_ambiguous_and_nonfinite_json_rejected_including_signed_content (test_proofable_fetch.InputAndProjectionChecks.test_ambiguous_and_nonfinite_json_rejected_including_signed_content) ... ok +test_checksum_inventory_rejects_escape_duplicate_and_omission (test_proofable_fetch.InputAndProjectionChecks.test_checksum_inventory_rejects_escape_duplicate_and_omission) ... ok +test_multiple_envelopes_and_malformed_data_rejected (test_proofable_fetch.InputAndProjectionChecks.test_multiple_envelopes_and_malformed_data_rejected) ... ok +test_multiple_sse_responses_and_ambiguous_nested_create_json_rejected (test_proofable_fetch.InputAndProjectionChecks.test_multiple_sse_responses_and_ambiguous_nested_create_json_rejected) ... ok +test_projection_matches_published_digest_and_ignores_only_declared_exclusions (test_proofable_fetch.InputAndProjectionChecks.test_projection_matches_published_digest_and_ignores_only_declared_exclusions) ... ok +test_stringify_preserves_unicode_and_js_key_order_without_rounding (test_proofable_fetch.InputAndProjectionChecks.test_stringify_preserves_unicode_and_js_key_order_without_rounding) ... ok +test_atomic_output_failure_cleans_temporary_and_preserves_inputs (test_proofable_fetch.PublishedFetchChecks.test_atomic_output_failure_cleans_temporary_and_preserves_inputs) ... ok +test_cli_keeps_output_outside_packet (test_proofable_fetch.PublishedFetchChecks.test_cli_keeps_output_outside_packet) ... ok +test_cli_replaces_hardlinked_output_without_modifying_any_input (test_proofable_fetch.PublishedFetchChecks.test_cli_replaces_hardlinked_output_without_modifying_any_input) ... ok +test_fixed_positive_packet_and_unmet_scenarios (test_proofable_fetch.PublishedFetchChecks.test_fixed_positive_packet_and_unmet_scenarios) ... ok +test_missing_unsafe_and_oversized_files_rejected (test_proofable_fetch.PublishedFetchChecks.test_missing_unsafe_and_oversized_files_rejected) ... ok +test_tampered_artifact_cannot_pass_even_if_semantics_match (test_proofable_fetch.PublishedFetchChecks.test_tampered_artifact_cannot_pass_even_if_semantics_match) ... ok test_emergency_bound_retains_blocks (test_runner.BoundaryChecks.test_emergency_bound_retains_blocks) ... ok test_forged_dispatch_rejected (test_runner.BoundaryChecks.test_forged_dispatch_rejected) ... ok test_late_veto_invalidates_prior_dispatch (test_runner.BoundaryChecks.test_late_veto_invalidates_prior_dispatch) ... ok @@ -22,11 +43,78 @@ test_veto_no_effect (test_runner.Checks.test_veto_no_effect) ... ok test_duplicate_cases_rejected (test_runner.CompletenessChecks.test_duplicate_cases_rejected) ... ok test_four_cases_required (test_runner.CompletenessChecks.test_four_cases_required) ... ok test_missing_proofable_row_is_missing_not_na (test_runner.CompletenessChecks.test_missing_proofable_row_is_missing_not_na) ... ok +test_corrected_packet_verifies (test_runner.CorrectedProofableChecks.test_corrected_packet_verifies) ... ok +test_semantic_envelope_mutations_fail (test_runner.CorrectedProofableChecks.test_semantic_envelope_mutations_fail) ... ok test_reference_rejects_empty_username_credentials (test_runner.ReferenceChecks.test_reference_rejects_empty_username_credentials) ... ok test_reference_requires_http_uri (test_runner.ReferenceChecks.test_reference_requires_http_uri) ... ok test_released_trace_reference_shape (test_runner.ReferenceChecks.test_released_trace_reference_shape) ... ok +test_authority_and_decision_omission_rejected (test_runner.RequiredFieldChecks.test_authority_and_decision_omission_rejected) ... ok +test_complete_measured_fields_accepted (test_runner.RequiredFieldChecks.test_complete_measured_fields_accepted) ... ok +test_each_required_field_omission_rejected_without_mutation (test_runner.RequiredFieldChecks.test_each_required_field_omission_rejected_without_mutation) ... ok +test_empty_fields_rejected (test_runner.RequiredFieldChecks.test_empty_fields_rejected) ... ok +test_explicit_unavailable_states_accepted_without_mutation (test_runner.RequiredFieldChecks.test_explicit_unavailable_states_accepted_without_mutation) ... ok ---------------------------------------------------------------------- -Ran 27 tests in 0.385s +Ran 54 tests in 1.548s + +OK +Command: python -O -m unittest -v +test_changed_unsigned_request_digest_fails_with_valid_envelopes (test_proofable_fetch.IndependentCryptoControls.test_changed_unsigned_request_digest_fails_with_valid_envelopes) ... ok +test_did_binding_is_required (test_proofable_fetch.IndependentCryptoControls.test_did_binding_is_required) ... ok +test_different_valid_signer_or_chain_fails_context (test_proofable_fetch.IndependentCryptoControls.test_different_valid_signer_or_chain_fails_context) ... ok +test_different_validly_signed_principal_fails_context (test_proofable_fetch.IndependentCryptoControls.test_different_validly_signed_principal_fails_context) ... ok +test_different_validly_signed_receipt_request_fails_digest (test_proofable_fetch.IndependentCryptoControls.test_different_validly_signed_receipt_request_fails_digest) ... ok +test_fresh_test_key_control_verifies_without_vendor_signatures (test_proofable_fetch.IndependentCryptoControls.test_fresh_test_key_control_verifies_without_vendor_signatures) ... ok +test_unsigned_create_join_mismatches_are_not_promoted_to_attestation (test_proofable_fetch.IndependentCryptoControls.test_unsigned_create_join_mismatches_are_not_promoted_to_attestation) ... ok +test_wrong_signature_and_qhash_fail_independently (test_proofable_fetch.IndependentCryptoControls.test_wrong_signature_and_qhash_fail_independently) ... ok +test_ambiguous_and_nonfinite_json_rejected_including_signed_content (test_proofable_fetch.InputAndProjectionChecks.test_ambiguous_and_nonfinite_json_rejected_including_signed_content) ... ok +test_checksum_inventory_rejects_escape_duplicate_and_omission (test_proofable_fetch.InputAndProjectionChecks.test_checksum_inventory_rejects_escape_duplicate_and_omission) ... ok +test_multiple_envelopes_and_malformed_data_rejected (test_proofable_fetch.InputAndProjectionChecks.test_multiple_envelopes_and_malformed_data_rejected) ... ok +test_multiple_sse_responses_and_ambiguous_nested_create_json_rejected (test_proofable_fetch.InputAndProjectionChecks.test_multiple_sse_responses_and_ambiguous_nested_create_json_rejected) ... ok +test_projection_matches_published_digest_and_ignores_only_declared_exclusions (test_proofable_fetch.InputAndProjectionChecks.test_projection_matches_published_digest_and_ignores_only_declared_exclusions) ... ok +test_stringify_preserves_unicode_and_js_key_order_without_rounding (test_proofable_fetch.InputAndProjectionChecks.test_stringify_preserves_unicode_and_js_key_order_without_rounding) ... ok +test_atomic_output_failure_cleans_temporary_and_preserves_inputs (test_proofable_fetch.PublishedFetchChecks.test_atomic_output_failure_cleans_temporary_and_preserves_inputs) ... ok +test_cli_keeps_output_outside_packet (test_proofable_fetch.PublishedFetchChecks.test_cli_keeps_output_outside_packet) ... ok +test_cli_replaces_hardlinked_output_without_modifying_any_input (test_proofable_fetch.PublishedFetchChecks.test_cli_replaces_hardlinked_output_without_modifying_any_input) ... ok +test_fixed_positive_packet_and_unmet_scenarios (test_proofable_fetch.PublishedFetchChecks.test_fixed_positive_packet_and_unmet_scenarios) ... ok +test_missing_unsafe_and_oversized_files_rejected (test_proofable_fetch.PublishedFetchChecks.test_missing_unsafe_and_oversized_files_rejected) ... ok +test_tampered_artifact_cannot_pass_even_if_semantics_match (test_proofable_fetch.PublishedFetchChecks.test_tampered_artifact_cannot_pass_even_if_semantics_match) ... ok +test_emergency_bound_retains_blocks (test_runner.BoundaryChecks.test_emergency_bound_retains_blocks) ... ok +test_forged_dispatch_rejected (test_runner.BoundaryChecks.test_forged_dispatch_rejected) ... ok +test_late_veto_invalidates_prior_dispatch (test_runner.BoundaryChecks.test_late_veto_invalidates_prior_dispatch) ... ok +test_manifest_cannot_omit_required_bytes (test_runner.BoundaryChecks.test_manifest_cannot_omit_required_bytes) ... ok +test_measured_null_rejected (test_runner.BoundaryChecks.test_measured_null_rejected) ... ok +test_root_age_is_seconds_not_root_identity (test_runner.BoundaryChecks.test_root_age_is_seconds_not_root_identity) ... ok +test_action_tampering_between_dispatch_commit (test_runner.Checks.test_action_tampering_between_dispatch_commit) ... ok +test_author_evidence_cannot_be_independent (test_runner.Checks.test_author_evidence_cannot_be_independent) ... ok +test_escape_rejected (test_runner.Checks.test_escape_rejected) ... ok +test_late_recovery_does_not_reopen (test_runner.Checks.test_late_recovery_does_not_reopen) ... ok +test_mintid_outage_keeps_old_revision (test_runner.Checks.test_mintid_outage_keeps_old_revision) ... ok +test_missing_field_cannot_be_measured_value (test_runner.Checks.test_missing_field_cannot_be_measured_value) ... ok +test_missing_states_not_collapsed (test_runner.Checks.test_missing_states_not_collapsed) ... ok +test_post_dispatch_recheck (test_runner.Checks.test_post_dispatch_recheck) ... ok +test_published_proofable_stays_integrity_failed (test_runner.Checks.test_published_proofable_stays_integrity_failed) ... ok +test_revoked_no_effect (test_runner.Checks.test_revoked_no_effect) ... ok +test_tampering_fails (test_runner.Checks.test_tampering_fails) ... ok +test_unknown_state_rejected (test_runner.Checks.test_unknown_state_rejected) ... ok +test_unreachable_no_effect (test_runner.Checks.test_unreachable_no_effect) ... ok +test_valid_control_and_duplicate (test_runner.Checks.test_valid_control_and_duplicate) ... ok +test_veto_no_effect (test_runner.Checks.test_veto_no_effect) ... ok +test_duplicate_cases_rejected (test_runner.CompletenessChecks.test_duplicate_cases_rejected) ... ok +test_four_cases_required (test_runner.CompletenessChecks.test_four_cases_required) ... ok +test_missing_proofable_row_is_missing_not_na (test_runner.CompletenessChecks.test_missing_proofable_row_is_missing_not_na) ... ok +test_corrected_packet_verifies (test_runner.CorrectedProofableChecks.test_corrected_packet_verifies) ... ok +test_semantic_envelope_mutations_fail (test_runner.CorrectedProofableChecks.test_semantic_envelope_mutations_fail) ... ok +test_reference_rejects_empty_username_credentials (test_runner.ReferenceChecks.test_reference_rejects_empty_username_credentials) ... ok +test_reference_requires_http_uri (test_runner.ReferenceChecks.test_reference_requires_http_uri) ... ok +test_released_trace_reference_shape (test_runner.ReferenceChecks.test_released_trace_reference_shape) ... ok +test_authority_and_decision_omission_rejected (test_runner.RequiredFieldChecks.test_authority_and_decision_omission_rejected) ... ok +test_complete_measured_fields_accepted (test_runner.RequiredFieldChecks.test_complete_measured_fields_accepted) ... ok +test_each_required_field_omission_rejected_without_mutation (test_runner.RequiredFieldChecks.test_each_required_field_omission_rejected_without_mutation) ... ok +test_empty_fields_rejected (test_runner.RequiredFieldChecks.test_empty_fields_rejected) ... ok +test_explicit_unavailable_states_accepted_without_mutation (test_runner.RequiredFieldChecks.test_explicit_unavailable_states_accepted_without_mutation) ... ok + +---------------------------------------------------------------------- +Ran 54 tests in 1.479s OK diff --git a/integrations/alakris-authority-comparison/results/trace-reference-shapes.json b/integrations/alakris-authority-comparison/results/trace-reference-shapes.json index 1c32779d..9d2876ec 100644 --- a/integrations/alakris-authority-comparison/results/trace-reference-shapes.json +++ b/integrations/alakris-authority-comparison/results/trace-reference-shapes.json @@ -1,6 +1,110 @@ [ { "file": "proofable/README.md", + "reference": { + "rel": "behavior-trace", + "id": "https://raw.githubusercontent.com/proofable/docs/9851059900e29ba701bb0f4df2bf89a2107f430c/public/evidence/aaif/authority-at-dispatch/2026-10-08/README.md", + "resolver": "https://raw.githubusercontent.com/proofable/docs/9851059900e29ba701bb0f4df2bf89a2107f430c/public/evidence/aaif/authority-at-dispatch/2026-10-08/README.md", + "digest": "sha256:e32b2580d2ff33d7379b3fc686855c389bcb113beeaf9c44a8e3e4db48e13271" + }, + "reference_shape_validated": true, + "signature_verification": "not_measured", + "assurance": "public_vendor_log", + "trace_record_emitted": false + }, + { + "file": "proofable/SHA256SUMS", + "reference": { + "rel": "behavior-trace", + "id": "https://raw.githubusercontent.com/proofable/docs/9851059900e29ba701bb0f4df2bf89a2107f430c/public/evidence/aaif/authority-at-dispatch/2026-10-08/SHA256SUMS", + "resolver": "https://raw.githubusercontent.com/proofable/docs/9851059900e29ba701bb0f4df2bf89a2107f430c/public/evidence/aaif/authority-at-dispatch/2026-10-08/SHA256SUMS", + "digest": "sha256:ec9222646d8edfa5954949cf0bdca0765e428f6c18d6ed34000ccfb325ddd001" + }, + "reference_shape_validated": true, + "signature_verification": "not_measured", + "assurance": "public_vendor_log", + "trace_record_emitted": false + }, + { + "file": "proofable/manifest.json", + "reference": { + "rel": "behavior-trace", + "id": "https://raw.githubusercontent.com/proofable/docs/9851059900e29ba701bb0f4df2bf89a2107f430c/public/evidence/aaif/authority-at-dispatch/2026-10-08/manifest.json", + "resolver": "https://raw.githubusercontent.com/proofable/docs/9851059900e29ba701bb0f4df2bf89a2107f430c/public/evidence/aaif/authority-at-dispatch/2026-10-08/manifest.json", + "digest": "sha256:90638a7e27b42ab784070ca6cea5dbc6add33671a3277744a9534cb7b51a424c" + }, + "reference_shape_validated": true, + "signature_verification": "not_measured", + "assurance": "public_vendor_log", + "trace_record_emitted": false + }, + { + "file": "proofable/trace.jsonl", + "reference": { + "rel": "behavior-trace", + "id": "https://raw.githubusercontent.com/proofable/docs/9851059900e29ba701bb0f4df2bf89a2107f430c/public/evidence/aaif/authority-at-dispatch/2026-10-08/trace.jsonl", + "resolver": "https://raw.githubusercontent.com/proofable/docs/9851059900e29ba701bb0f4df2bf89a2107f430c/public/evidence/aaif/authority-at-dispatch/2026-10-08/trace.jsonl", + "digest": "sha256:96241d8409c4661f8e60cd0904e7245434f48bf5711d9c377706f29ff721a0c8" + }, + "reference_shape_validated": true, + "signature_verification": "not_measured", + "assurance": "public_vendor_log", + "trace_record_emitted": false + }, + { + "file": "proofable/authority-effect-results.json", + "reference": { + "rel": "behavior-trace", + "id": "https://raw.githubusercontent.com/proofable/docs/9851059900e29ba701bb0f4df2bf89a2107f430c/public/evidence/aaif/authority-at-dispatch/2026-10-08/authority-effect-results.json", + "resolver": "https://raw.githubusercontent.com/proofable/docs/9851059900e29ba701bb0f4df2bf89a2107f430c/public/evidence/aaif/authority-at-dispatch/2026-10-08/authority-effect-results.json", + "digest": "sha256:45c953291efce44499cb995f5c7d271683399a04bc155aea1389b78468cbe7e0" + }, + "reference_shape_validated": true, + "signature_verification": "not_measured", + "assurance": "public_vendor_log", + "trace_record_emitted": false + }, + { + "file": "proofable/authority-effect-results.md", + "reference": { + "rel": "behavior-trace", + "id": "https://raw.githubusercontent.com/proofable/docs/9851059900e29ba701bb0f4df2bf89a2107f430c/public/evidence/aaif/authority-at-dispatch/2026-10-08/authority-effect-results.md", + "resolver": "https://raw.githubusercontent.com/proofable/docs/9851059900e29ba701bb0f4df2bf89a2107f430c/public/evidence/aaif/authority-at-dispatch/2026-10-08/authority-effect-results.md", + "digest": "sha256:60a6d77a1705c6e0152e0fb72131824715966cd3c2574f5a3b06820ecd8ab47b" + }, + "reference_shape_validated": true, + "signature_verification": "not_measured", + "assurance": "public_vendor_log", + "trace_record_emitted": false + }, + { + "file": "proofable/portable-proofs.json", + "reference": { + "rel": "behavior-trace", + "id": "https://raw.githubusercontent.com/proofable/docs/9851059900e29ba701bb0f4df2bf89a2107f430c/public/evidence/aaif/authority-at-dispatch/2026-10-08/portable-proofs.json", + "resolver": "https://raw.githubusercontent.com/proofable/docs/9851059900e29ba701bb0f4df2bf89a2107f430c/public/evidence/aaif/authority-at-dispatch/2026-10-08/portable-proofs.json", + "digest": "sha256:74ef851d4001a572d841bcfa27cfb1216dc2d5be063049196986d00c69b5e7cc" + }, + "reference_shape_validated": true, + "signature_verification": "not_measured", + "assurance": "public_vendor_log", + "trace_record_emitted": false + }, + { + "file": "proofable/verify-portable-proofs.mjs", + "reference": { + "rel": "behavior-trace", + "id": "https://raw.githubusercontent.com/proofable/docs/9851059900e29ba701bb0f4df2bf89a2107f430c/public/evidence/aaif/authority-at-dispatch/2026-10-08/verify-portable-proofs.mjs", + "resolver": "https://raw.githubusercontent.com/proofable/docs/9851059900e29ba701bb0f4df2bf89a2107f430c/public/evidence/aaif/authority-at-dispatch/2026-10-08/verify-portable-proofs.mjs", + "digest": "sha256:f7eb8f74885518b3ba0d945f60500e3236a25ad569fdcc6c8e44af462eaa3248" + }, + "reference_shape_validated": true, + "signature_verification": "not_measured", + "assurance": "public_vendor_log", + "trace_record_emitted": false + }, + { + "file": "proofable-historical/README.md", "reference": { "rel": "behavior-trace", "id": "https://raw.githubusercontent.com/proofable/docs/3a45f026fd0c06b9d1d411c053597584b6b1c3dc/public/evidence/aaif/authority-at-dispatch/2026-10-06/README.md", @@ -13,7 +117,7 @@ "trace_record_emitted": false }, { - "file": "proofable/SHA256SUMS", + "file": "proofable-historical/SHA256SUMS", "reference": { "rel": "behavior-trace", "id": "https://raw.githubusercontent.com/proofable/docs/3a45f026fd0c06b9d1d411c053597584b6b1c3dc/public/evidence/aaif/authority-at-dispatch/2026-10-06/SHA256SUMS", @@ -26,7 +130,7 @@ "trace_record_emitted": false }, { - "file": "proofable/manifest.json", + "file": "proofable-historical/manifest.json", "reference": { "rel": "behavior-trace", "id": "https://raw.githubusercontent.com/proofable/docs/3a45f026fd0c06b9d1d411c053597584b6b1c3dc/public/evidence/aaif/authority-at-dispatch/2026-10-06/manifest.json", @@ -39,7 +143,7 @@ "trace_record_emitted": false }, { - "file": "proofable/trace.jsonl", + "file": "proofable-historical/trace.jsonl", "reference": { "rel": "behavior-trace", "id": "https://raw.githubusercontent.com/proofable/docs/3a45f026fd0c06b9d1d411c053597584b6b1c3dc/public/evidence/aaif/authority-at-dispatch/2026-10-06/trace.jsonl", @@ -52,7 +156,7 @@ "trace_record_emitted": false }, { - "file": "proofable/authority-effect-results.json", + "file": "proofable-historical/authority-effect-results.json", "reference": { "rel": "behavior-trace", "id": "https://raw.githubusercontent.com/proofable/docs/3a45f026fd0c06b9d1d411c053597584b6b1c3dc/public/evidence/aaif/authority-at-dispatch/2026-10-06/authority-effect-results.json", @@ -65,7 +169,7 @@ "trace_record_emitted": false }, { - "file": "proofable/authority-effect-results.md", + "file": "proofable-historical/authority-effect-results.md", "reference": { "rel": "behavior-trace", "id": "https://raw.githubusercontent.com/proofable/docs/3a45f026fd0c06b9d1d411c053597584b6b1c3dc/public/evidence/aaif/authority-at-dispatch/2026-10-06/authority-effect-results.md", @@ -154,5 +258,135 @@ "signature_verification": "not_measured", "assurance": "public_vendor_log", "trace_record_emitted": false + }, + { + "file": "proofable-fetch/1-invocation.json", + "reference": { + "rel": "behavior-trace", + "id": "https://raw.githubusercontent.com/proofable/docs/e6eb5b9307be24a5d82cc7490f647f0c98e2d24a/public/evidence/aaif/authority-at-dispatch/2026-10-24-fetch-route/1-invocation.json", + "resolver": "https://raw.githubusercontent.com/proofable/docs/e6eb5b9307be24a5d82cc7490f647f0c98e2d24a/public/evidence/aaif/authority-at-dispatch/2026-10-24-fetch-route/1-invocation.json", + "digest": "sha256:566b2cc841a1c4966bf56ca98f21c4a479476a97172e3287c1572a2f0a59c871" + }, + "reference_shape_validated": true, + "signature_verification": "not_measured", + "assurance": "public_vendor_log", + "trace_record_emitted": false + }, + { + "file": "proofable-fetch/2-request-arguments.json", + "reference": { + "rel": "behavior-trace", + "id": "https://raw.githubusercontent.com/proofable/docs/e6eb5b9307be24a5d82cc7490f647f0c98e2d24a/public/evidence/aaif/authority-at-dispatch/2026-10-24-fetch-route/2-request-arguments.json", + "resolver": "https://raw.githubusercontent.com/proofable/docs/e6eb5b9307be24a5d82cc7490f647f0c98e2d24a/public/evidence/aaif/authority-at-dispatch/2026-10-24-fetch-route/2-request-arguments.json", + "digest": "sha256:8dd4cf11e11a29347ba1d73921a2b2fce0a8ac3ccac56aea860bbf6778d95da7" + }, + "reference_shape_validated": true, + "signature_verification": "not_measured", + "assurance": "public_vendor_log", + "trace_record_emitted": false + }, + { + "file": "proofable-fetch/3-create-response.txt", + "reference": { + "rel": "behavior-trace", + "id": "https://raw.githubusercontent.com/proofable/docs/e6eb5b9307be24a5d82cc7490f647f0c98e2d24a/public/evidence/aaif/authority-at-dispatch/2026-10-24-fetch-route/3-create-response.txt", + "resolver": "https://raw.githubusercontent.com/proofable/docs/e6eb5b9307be24a5d82cc7490f647f0c98e2d24a/public/evidence/aaif/authority-at-dispatch/2026-10-24-fetch-route/3-create-response.txt", + "digest": "sha256:2db3795531b68a1f627d5e38d0a99783a2c789fcea0802629a6e4d85466fa1a8" + }, + "reference_shape_validated": true, + "signature_verification": "not_measured", + "assurance": "public_vendor_log", + "trace_record_emitted": false + }, + { + "file": "proofable-fetch/4-dispatch-decision.json", + "reference": { + "rel": "behavior-trace", + "id": "https://raw.githubusercontent.com/proofable/docs/e6eb5b9307be24a5d82cc7490f647f0c98e2d24a/public/evidence/aaif/authority-at-dispatch/2026-10-24-fetch-route/4-dispatch-decision.json", + "resolver": "https://raw.githubusercontent.com/proofable/docs/e6eb5b9307be24a5d82cc7490f647f0c98e2d24a/public/evidence/aaif/authority-at-dispatch/2026-10-24-fetch-route/4-dispatch-decision.json", + "digest": "sha256:2970e1c6a1e7291d36bcb752e1b7b2c9416c24eb6e6c699c1dcfd19c2b2e6b5f" + }, + "reference_shape_validated": true, + "signature_verification": "not_measured", + "assurance": "public_vendor_log", + "trace_record_emitted": false + }, + { + "file": "proofable-fetch/5-job-record.json", + "reference": { + "rel": "behavior-trace", + "id": "https://raw.githubusercontent.com/proofable/docs/e6eb5b9307be24a5d82cc7490f647f0c98e2d24a/public/evidence/aaif/authority-at-dispatch/2026-10-24-fetch-route/5-job-record.json", + "resolver": "https://raw.githubusercontent.com/proofable/docs/e6eb5b9307be24a5d82cc7490f647f0c98e2d24a/public/evidence/aaif/authority-at-dispatch/2026-10-24-fetch-route/5-job-record.json", + "digest": "sha256:40e0bb4a7d75396182b026884f83c350316fa3be6b00bff2ce9ea3cac146d415" + }, + "reference_shape_validated": true, + "signature_verification": "not_measured", + "assurance": "public_vendor_log", + "trace_record_emitted": false + }, + { + "file": "proofable-fetch/6-terminal-receipt.json", + "reference": { + "rel": "behavior-trace", + "id": "https://raw.githubusercontent.com/proofable/docs/e6eb5b9307be24a5d82cc7490f647f0c98e2d24a/public/evidence/aaif/authority-at-dispatch/2026-10-24-fetch-route/6-terminal-receipt.json", + "resolver": "https://raw.githubusercontent.com/proofable/docs/e6eb5b9307be24a5d82cc7490f647f0c98e2d24a/public/evidence/aaif/authority-at-dispatch/2026-10-24-fetch-route/6-terminal-receipt.json", + "digest": "sha256:34688c283148a0524b550666fa74839b38410c26fd63425734a2753ba89f040c" + }, + "reference_shape_validated": true, + "signature_verification": "not_measured", + "assurance": "public_vendor_log", + "trace_record_emitted": false + }, + { + "file": "proofable-fetch/README.md", + "reference": { + "rel": "behavior-trace", + "id": "https://raw.githubusercontent.com/proofable/docs/e6eb5b9307be24a5d82cc7490f647f0c98e2d24a/public/evidence/aaif/authority-at-dispatch/2026-10-24-fetch-route/README.md", + "resolver": "https://raw.githubusercontent.com/proofable/docs/e6eb5b9307be24a5d82cc7490f647f0c98e2d24a/public/evidence/aaif/authority-at-dispatch/2026-10-24-fetch-route/README.md", + "digest": "sha256:57dcce249c71c083f43f70f25bae4bf24d0904a201a664da3f4c181c43aeceed" + }, + "reference_shape_validated": true, + "signature_verification": "not_measured", + "assurance": "public_vendor_log", + "trace_record_emitted": false + }, + { + "file": "proofable-fetch/SHA256SUMS", + "reference": { + "rel": "behavior-trace", + "id": "https://raw.githubusercontent.com/proofable/docs/e6eb5b9307be24a5d82cc7490f647f0c98e2d24a/public/evidence/aaif/authority-at-dispatch/2026-10-24-fetch-route/SHA256SUMS", + "resolver": "https://raw.githubusercontent.com/proofable/docs/e6eb5b9307be24a5d82cc7490f647f0c98e2d24a/public/evidence/aaif/authority-at-dispatch/2026-10-24-fetch-route/SHA256SUMS", + "digest": "sha256:0a47e2b25bdf5959a774df690098a0d22f73e7d31453099ae53b45c370858f5a" + }, + "reference_shape_validated": true, + "signature_verification": "not_measured", + "assurance": "public_vendor_log", + "trace_record_emitted": false + }, + { + "file": "proofable-fetch/health-at-run.json", + "reference": { + "rel": "behavior-trace", + "id": "https://raw.githubusercontent.com/proofable/docs/e6eb5b9307be24a5d82cc7490f647f0c98e2d24a/public/evidence/aaif/authority-at-dispatch/2026-10-24-fetch-route/health-at-run.json", + "resolver": "https://raw.githubusercontent.com/proofable/docs/e6eb5b9307be24a5d82cc7490f647f0c98e2d24a/public/evidence/aaif/authority-at-dispatch/2026-10-24-fetch-route/health-at-run.json", + "digest": "sha256:b6849d19f65356cb3da0a1d0d4e840c1e1244830c9c2a637641f0cf7cf468616" + }, + "reference_shape_validated": true, + "signature_verification": "not_measured", + "assurance": "public_vendor_log", + "trace_record_emitted": false + }, + { + "file": "proofable-fetch/verify.mjs", + "reference": { + "rel": "behavior-trace", + "id": "https://raw.githubusercontent.com/proofable/docs/e6eb5b9307be24a5d82cc7490f647f0c98e2d24a/public/evidence/aaif/authority-at-dispatch/2026-10-24-fetch-route/verify.mjs", + "resolver": "https://raw.githubusercontent.com/proofable/docs/e6eb5b9307be24a5d82cc7490f647f0c98e2d24a/public/evidence/aaif/authority-at-dispatch/2026-10-24-fetch-route/verify.mjs", + "digest": "sha256:d1919890e1d5ff3c5da0f552c90e09ef672e514c7f2dc3e35c91276358c926a6" + }, + "reference_shape_validated": true, + "signature_verification": "not_measured", + "assurance": "public_vendor_log", + "trace_record_emitted": false } ] diff --git a/integrations/alakris-authority-comparison/test_proofable_fetch.py b/integrations/alakris-authority-comparison/test_proofable_fetch.py new file mode 100644 index 00000000..5535e9cf --- /dev/null +++ b/integrations/alakris-authority-comparison/test_proofable_fetch.py @@ -0,0 +1,427 @@ +"""Fixed-publication checks and independent test-key cryptographic controls.""" + +import copy +import hashlib +import json +import os +from pathlib import Path +import shutil +import subprocess +import sys +import tempfile +import unittest +from unittest.mock import patch + +from eth_account import Account +from eth_account.messages import encode_defunct + +from portable_envelope import message, qhash +from proofable_fetch import ( + MAX_FILE_BYTES, + PINS, + _create_response, + _inventory, + _json, + _js_stringify, + _load, + _records, + _semantic_checks, + appraise, + main, + request_digest, +) +from runner import CASES, REQUIRED_FIELDS + +ROOT = Path(__file__).resolve().parent +PACKET = ROOT / "fixtures/proofable-fetch" +TEST_KEY = "0x" + "01".zfill(64) +SECOND_KEY = "0x" + "02".zfill(64) + + +def envelope(records, filename): + return records[filename]["data"]["proofs"][0]["portableProof"] + + +def sign(records, filename, key=TEST_KEY): + """Create signatures locally using test keys, not vendor signatures.""" + item = envelope(records, filename) + item["walletAddress"] = Account.from_key(key).address.lower() + item["did"] = f"did:pkh:eip155:{item['chainId']}:{item['walletAddress']}" + item["qHash"] = qhash(item) + item["signature"] = "0x" + Account.sign_message( + encode_defunct(text=message(item)), private_key=key + ).signature.hex().removeprefix("0x") + + +def create_payload(records): + return records["3-create-response.txt"]["result"]["structuredContent"] + + +def sync_create_text(records): + records["3-create-response.txt"]["result"]["content"][0]["text"] = json.dumps( + create_payload(records) + ) + + +def sync_joins(records): + decision = envelope(records, "4-dispatch-decision.json") + receipt = envelope(records, "6-terminal-receipt.json") + create = create_payload(records) + create["authorityDecisionQHash"] = create["authorityDecision"]["qHash"] = decision[ + "qHash" + ] + records["5-job-record.json"]["qHash"] = records["5-job-record.json"][ + "terminalQHash" + ] = receipt["qHash"] + sync_create_text(records) + + +def control_records(): + records = _records(_load(PACKET)) + records["1-invocation.json"]["serviceSigner"] = Account.from_key( + TEST_KEY + ).address.lower() + for name in ("4-dispatch-decision.json", "6-terminal-receipt.json"): + sign(records, name) + sync_joins(records) + return records + + +def checks(records): + return {row["check"]: row["valid"] for row in _semantic_checks(records)["checks"]} + + +class PublishedFetchChecks(unittest.TestCase): + def test_fixed_positive_packet_and_unmet_scenarios(self): + self.assertTrue( + PACKET.is_dir(), + "run fetch_fixtures.py first; no fixture test may be skipped", + ) + before = { + p.name: hashlib.sha256(p.read_bytes()).hexdigest() + for p in PACKET.iterdir() + if p.is_file() + } + with patch("urllib.request.urlopen", side_effect=AssertionError("no network")): + report = appraise(PACKET) + self.assertEqual(report["integrity"], "PASS") + self.assertEqual(report["positive_dispatch_appraisal"], "PASS") + self.assertTrue(report["positive_dispatch"]["valid"]) + self.assertEqual(len(report["artifact_hashes"]), 10) + self.assertTrue(report["publisher_checksum_inventory_valid"]) + self.assertEqual(report["scenario_conformance"], "UNMET") + self.assertEqual(report["service_key_attribution"]["state"], "not_measured") + self.assertEqual( + report["grant_and_agent_identity_validation"]["state"], "not_measured" + ) + self.assertFalse(report["independent_live_reproduction"]) + self.assertFalse(report["independent_comparison_completed"]) + self.assertFalse(report["request_binding"]["full_returned_marker_precommitted"]) + self.assertEqual({case["case_id"] for case in report["cases"]}, set(CASES)) + for case in report["cases"]: + self.assertEqual(set(case["fields"]), REQUIRED_FIELDS) + self.assertEqual(case["scenario_conformance"], "UNMET") + self.assertTrue(case["unmet_requirements"]) + self.assertTrue( + all( + item["state"] == "not_measured" and item["reason"] + for item in case["fields"].values() + ) + ) + self.assertEqual( + report["unmeasured"]["EO01_unreachable_authority"]["state"], "not_measured" + ) + self.assertFalse(report["trace_record_emitted"]) + self.assertEqual( + before, + { + p.name: hashlib.sha256(p.read_bytes()).hexdigest() + for p in PACKET.iterdir() + if p.is_file() + }, + ) + + def test_tampered_artifact_cannot_pass_even_if_semantics_match(self): + with tempfile.TemporaryDirectory() as tmp: + packet = Path(tmp) / "packet" + shutil.copytree(PACKET, packet) + (packet / "README.md").write_text("changed publisher description\n") + report = appraise(packet) + self.assertEqual(report["integrity"], "FAIL") + self.assertEqual(report["positive_dispatch_appraisal"], "FAIL") + self.assertTrue(report["positive_dispatch"]["valid"]) + + def test_missing_unsafe_and_oversized_files_rejected(self): + for kind in ("missing", "symlink", "oversize"): + with self.subTest(kind=kind), tempfile.TemporaryDirectory() as tmp: + packet = Path(tmp) / "packet" + shutil.copytree(PACKET, packet) + path = packet / "README.md" + path.unlink() + if kind == "symlink": + outside = Path(tmp) / "outside" + outside.write_text("outside") + path.symlink_to(outside) + elif kind == "oversize": + path.write_bytes(b"x" * (MAX_FILE_BYTES + 1)) + with self.assertRaises(ValueError): + appraise(packet) + + def test_cli_keeps_output_outside_packet(self): + result = subprocess.run( + [ + sys.executable, + "proofable_fetch.py", + "--input", + str(PACKET), + "--output", + str(PACKET / "forbidden.json"), + ], + cwd=ROOT, + text=True, + capture_output=True, + ) + self.assertNotEqual(result.returncode, 0) + self.assertIn("output must be outside", result.stderr) + self.assertFalse((PACKET / "forbidden.json").exists()) + + def test_cli_replaces_hardlinked_output_without_modifying_any_input(self): + with tempfile.TemporaryDirectory() as tmp: + packet = Path(tmp) / "packet" + shutil.copytree(PACKET, packet) + original = packet / "README.md" + output = Path(tmp) / "outside-output.json" + os.link(original, output) + self.assertEqual(original.stat().st_ino, output.stat().st_ino) + before = { + name: hashlib.sha256((packet / name).read_bytes()).hexdigest() + for name in PINS + } + result = subprocess.run( + [ + sys.executable, + "proofable_fetch.py", + "--input", + str(packet), + "--output", + str(output), + ], + cwd=ROOT, + text=True, + capture_output=True, + ) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual( + json.loads(output.read_text())["positive_dispatch_appraisal"], "PASS" + ) + self.assertNotEqual(original.stat().st_ino, output.stat().st_ino) + self.assertEqual( + before, + { + name: hashlib.sha256((packet / name).read_bytes()).hexdigest() + for name in PINS + }, + ) + self.assertFalse(list(Path(tmp).glob(".proofable-fetch-*"))) + + def test_atomic_output_failure_cleans_temporary_and_preserves_inputs(self): + with tempfile.TemporaryDirectory() as tmp: + output = Path(tmp) / "output.json" + before = { + name: hashlib.sha256((PACKET / name).read_bytes()).hexdigest() + for name in PINS + } + with patch( + "sys.argv", + ["proofable_fetch.py", "--input", str(PACKET), "--output", str(output)], + ), patch( + "proofable_fetch.os.replace", side_effect=OSError("replace failed") + ): + with self.assertRaisesRegex(OSError, "replace failed"): + main() + self.assertFalse(output.exists()) + self.assertFalse(list(Path(tmp).glob(".proofable-fetch-*"))) + self.assertEqual( + before, + { + name: hashlib.sha256((PACKET / name).read_bytes()).hexdigest() + for name in PINS + }, + ) + + +class IndependentCryptoControls(unittest.TestCase): + def test_fresh_test_key_control_verifies_without_vendor_signatures(self): + records = control_records() + report = _semantic_checks(records) + self.assertTrue(report["valid"]) + self.assertNotEqual( + report["declared_service_signer"], + "0xa41494e1e5f9e5eaf786961e6ef84cd1a3071eec", + ) + self.assertTrue(all(item["valid"] for item in report["envelopes"].values())) + + def test_changed_unsigned_request_digest_fails_with_valid_envelopes(self): + records = control_records() + records["2-request-arguments.json"]["prompt"] = records[ + "2-request-arguments.json" + ]["prompt"].replace("b008feae", "a008feae") + result = checks(records) + self.assertFalse(result["request_args_digest"]) + self.assertTrue(result["decision_envelope"]) + self.assertTrue(result["receipt_envelope"]) + + def test_different_validly_signed_receipt_request_fails_digest(self): + records = control_records() + item = envelope(records, "6-terminal-receipt.json") + content = json.loads(item["data"]["content"]) + content["prompt"] = content["prompt"].replace("b008feae", "a008feae") + item["data"]["content"] = json.dumps(content) + sign(records, "6-terminal-receipt.json") + sync_joins(records) + result = checks(records) + self.assertTrue(result["receipt_envelope"]) + self.assertFalse(result["receipt_request_digest"]) + self.assertFalse(result["receipt_request_fields"]) + + def test_wrong_signature_and_qhash_fail_independently(self): + for key, value in (("signature", "0x" + "00" * 65), ("qHash", "0x" + "0" * 64)): + with self.subTest(key=key): + records = control_records() + envelope(records, "4-dispatch-decision.json")[key] = value + self.assertFalse(checks(records)["decision_envelope"]) + + def test_different_valid_signer_or_chain_fails_context(self): + for kind in ("signer", "chain"): + with self.subTest(kind=kind): + records = control_records() + if kind == "chain": + envelope(records, "6-terminal-receipt.json")["chainId"] = 1 + sign( + records, + "6-terminal-receipt.json", + SECOND_KEY if kind == "signer" else TEST_KEY, + ) + sync_joins(records) + result = checks(records) + self.assertTrue(result["receipt_envelope"]) + self.assertFalse(result["same_declared_signer_context"]) + + def test_did_binding_is_required(self): + records = control_records() + item = envelope(records, "4-dispatch-decision.json") + item["did"] = item["did"].replace(":8453:", ":1:") + item["qHash"] = qhash(item) + self.assertFalse(checks(records)["decision_envelope"]) + + def test_unsigned_create_join_mismatches_are_not_promoted_to_attestation(self): + for kind in ("decision", "job"): + with self.subTest(kind=kind): + records = control_records() + create = create_payload(records) + if kind == "decision": + create["authorityDecisionQHash"] = create["authorityDecision"][ + "qHash" + ] = "0x" + "1" * 64 + else: + create["jobId"] = "different-job" + sync_create_text(records) + result = checks(records) + self.assertTrue(result["create_response_shape"]) + self.assertFalse( + result[ + "create_decision_qhash_join" + if kind == "decision" + else "create_job_join" + ] + ) + self.assertTrue(result["decision_envelope"]) + self.assertTrue(result["receipt_envelope"]) + + def test_different_validly_signed_principal_fails_context(self): + records = control_records() + item = envelope(records, "6-terminal-receipt.json") + item["data"]["owner"] = "0x" + "1" * 40 + sign(records, "6-terminal-receipt.json") + sync_joins(records) + result = checks(records) + self.assertTrue(result["receipt_envelope"]) + self.assertFalse(result["same_principal_agent_context"]) + + +class InputAndProjectionChecks(unittest.TestCase): + def test_ambiguous_and_nonfinite_json_rejected_including_signed_content(self): + for value in ( + '{"x":1,"x":2}', + '{"x":NaN}', + '{"x":Infinity}', + '{"x":1e999}', + "{", + "[" * 66 + "0" + "]" * 66, + ): + with self.subTest(value=value), self.assertRaises(ValueError): + _json(value, "control") + records = control_records() + envelope(records, "6-terminal-receipt.json")["data"]["content"] = ( + '{"jobId":"a","jobId":"b"}' + ) + with self.assertRaisesRegex(ValueError, "duplicate key"): + _semantic_checks(records) + + def test_multiple_sse_responses_and_ambiguous_nested_create_json_rejected(self): + with self.assertRaises(ValueError): + _create_response("event: message\ndata: {}\n\nevent: message\ndata: {}\n") + records = control_records() + records["3-create-response.txt"]["result"]["content"][0]["text"] = ( + '{"jobId":"a","jobId":"b"}' + ) + with self.assertRaisesRegex(ValueError, "duplicate key"): + _semantic_checks(records) + + def test_checksum_inventory_rejects_escape_duplicate_and_omission(self): + for content in ( + ("0" * 64 + " ../outside\n").encode(), + ("0" * 64 + " README.md\n").encode() * 2, + ): + with self.assertRaises(ValueError): + _inventory(content) + self.assertNotEqual( + _inventory(b""), + {name: h for name, h in PINS.items() if name != "SHA256SUMS"}, + ) + + def test_projection_matches_published_digest_and_ignores_only_declared_exclusions( + self, + ): + request = _records(_load(PACKET))["2-request-arguments.json"] + self.assertEqual( + request_digest(request), + "245cd3cefe248e532ba867b8735484485c26d3809ad909ef47348f449b0b7b07", + ) + altered = copy.deepcopy(request) + altered["runNowFirst"] = False + altered["acceptance"]["criteria"] = ["different unsigned alias"] + altered["agentWallet"] = altered["agentWallet"].upper().replace("0X", "0x") + self.assertEqual(request_digest(altered), request_digest(request)) + altered["acceptance"]["criterion"] = "different criterion" + self.assertNotEqual(request_digest(altered), request_digest(request)) + + def test_stringify_preserves_unicode_and_js_key_order_without_rounding(self): + self.assertEqual( + _js_stringify({"z": "e\u0301", "2": False, "1": None}), + '{"1":null,"2":false,"z":"e\u0301"}', + ) + self.assertEqual(_js_stringify("\ud800"), '"\\ud800"') + for value in (1.5, float("nan"), 2**53): + with self.assertRaises(ValueError): + _js_stringify(value) + + def test_multiple_envelopes_and_malformed_data_rejected(self): + records = control_records() + records["4-dispatch-decision.json"]["data"]["proofs"] *= 2 + with self.assertRaisesRegex(ValueError, "exactly one"): + _semantic_checks(records) + + +if __name__ == "__main__": + unittest.main()