open-standards/jouleclaw/jouleclaw-rs/crates/jouleclaw-hydra-client/clients/python/jclaw_hydra/hydra.py
Transaction Science 2032b91125 jouleclaw-hydra-client: Python + Go reference clients, byte-equal conformance (wave-23 #2)
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.
2026-06-02 13:54:42 -04:00

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)