Three independent implementations of the third-generation Hydra
client — Rust + Python + Go — all produce byte-for-byte identical
BLAKE3 input/output hashes against the same 9-vector conformance
suite. Cross-language conformance is what makes the Hydra-client
surface a real open standard rather than a Rust house format.
What ships:
conformance/call-reports.json — 5 dispatch + 4 SHACL-validation
vectors. The Rust producer is
the oracle (regenerated via
examples/gen_conformance_vectors.rs).
clients/python/jclaw_hydra/ — Python reference client. Package
layout for `from jclaw_hydra
import HydraClient`. Uses the
blake3 PyPI package for BLAKE3.
Conformance test at
clients/python/tests/test_conformance.py:
9/9 PASS.
clients/go/ — Go reference client. Importable
as `jclawhydra`. Uses
lukechampine.com/blake3.
Conformance via `go test ./...`:
9/9 PASS across TestCallReportsConformance
+ TestValidationConformance.
examples/gen_conformance_vectors.rs — Rust regenerator. Produces
call-reports.json from the
canonical Rust client; the
other implementations
verify against this output.
conformance/README.md — vector description, how to run
the three implementations, scope.
The clients implement the same surface verbatim:
- parse Hydra ApiDocumentation from JSON-LD
- find operation by jc_tool_id / @id / title
- validate input against the linked SHACL shape (open-world
default if no shape registered; #Nothing-expects skips)
- dispatch via Transport — echo-mode MockTransport ships as the
test reference; transport returns { "echo": <input> } verbatim
(matches Rust core; no "op" sidecar like the WASM demo wrapper)
- canonical-JSON serialise the input + body (lexically-sorted
keys, no whitespace) and BLAKE3-hash → hex
- return CallReport with tool_id, status, input_hash, output_hash,
status_matches_contract, body, touch{tool_id, joules_uj,
energy_provenance}
The hashes match because the canonical-JSON form matches — that's
the spec primitive. The body match proves the MockTransport
agrees too. The status_matches_contract bit verifies the three
agree on how to walk an Operation's statusCodes menu.
Validation reports verify the same conforms-bit across the four
SHACL cases the conformance file declares: well-shaped input,
missing required field, bad enum, and open-world skip for an op
with no expects-shape.
Workspace: 107 native crates (+0; clients/go and clients/python
live outside Cargo), 2,635 lib tests pass, 0 failures.
Next wave-23 step: #3 — carry the vocab + SHACL pattern into the
four sister standards (ARL, OpenPay, Smart Byte, EOC, WAI) so the
whole TX-Science quintet speaks Gen-3 with stable IRIs.
91 lines
2.7 KiB
Python
91 lines
2.7 KiB
Python
"""Hydra ApiDocumentation surface — parse + operation lookup."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from dataclasses import dataclass, field
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
|
|
@dataclass
|
|
class Operation:
|
|
"""One Hydra operation — typically one tool on a JouleClaw service."""
|
|
|
|
id: str
|
|
types: List[str]
|
|
title: str
|
|
description: str
|
|
method: str
|
|
expects: str
|
|
returns: str
|
|
status_codes: List[Dict[str, Any]] = field(default_factory=list)
|
|
jc_tool_id: Optional[str] = None
|
|
jc_mutating: Optional[bool] = None
|
|
|
|
@classmethod
|
|
def from_dict(cls, d: Dict[str, Any]) -> "Operation":
|
|
return cls(
|
|
id=d["@id"],
|
|
types=d.get("@type", ["Operation"]),
|
|
title=d.get("title", ""),
|
|
description=d.get("description", ""),
|
|
method=d.get("method", "GET"),
|
|
expects=d.get("expects", "http://www.w3.org/2002/07/owl#Nothing"),
|
|
returns=d.get("returns", ""),
|
|
status_codes=d.get("statusCodes", []),
|
|
jc_tool_id=d.get("jc_tool_id"),
|
|
jc_mutating=d.get("jc_mutating"),
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class Class:
|
|
"""One hydra:Class — groups operations."""
|
|
|
|
id: str
|
|
title: str
|
|
description: str
|
|
supported_operation: List[Operation] = field(default_factory=list)
|
|
|
|
@classmethod
|
|
def from_dict(cls, d: Dict[str, Any]) -> "Class":
|
|
return cls(
|
|
id=d["@id"],
|
|
title=d.get("title", ""),
|
|
description=d.get("description", ""),
|
|
supported_operation=[Operation.from_dict(o) for o in d.get("supportedOperation", [])],
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class ApiDocumentation:
|
|
"""Root Hydra document — the contract a service publishes."""
|
|
|
|
id: str
|
|
title: str
|
|
description: str
|
|
supported_class: List[Class] = field(default_factory=list)
|
|
entrypoint: Optional[str] = None
|
|
context: Optional[Any] = None
|
|
|
|
@classmethod
|
|
def from_dict(cls, d: Dict[str, Any]) -> "ApiDocumentation":
|
|
return cls(
|
|
id=d.get("@id", ""),
|
|
title=d.get("title", ""),
|
|
description=d.get("description", ""),
|
|
supported_class=[Class.from_dict(c) for c in d.get("supportedClass", [])],
|
|
entrypoint=d.get("entrypoint"),
|
|
context=d.get("@context"),
|
|
)
|
|
|
|
|
|
def parse_api(source: Any) -> ApiDocumentation:
|
|
"""Parse a Hydra ApiDocumentation. Accepts a dict, bytes, or str."""
|
|
if isinstance(source, (bytes, bytearray)):
|
|
source = source.decode("utf-8")
|
|
if isinstance(source, str):
|
|
source = json.loads(source)
|
|
if not isinstance(source, dict):
|
|
raise TypeError(f"unsupported source type: {type(source)}")
|
|
return ApiDocumentation.from_dict(source)
|