Skip to content

Repository files navigation

gate-rust

Rust companion crate for GATE v1.4 (Governed Agent Trust Environment).

Per GATE v1.4 paper-update 10 line 18, gate-rust is the high-throughput Tool Gateway companion to gate-python. It ships four surfaces: canonical JSON, envelopes, ledger, and signing.

Hash compatibility with gate-python

gate-rust v1.0.0 pairs with gate-python v1.2.0. The canonical-JSON test vectors at gate-python/gate/test_vectors/canonical_json_vectors.json are the cross-language compatibility source of truth. gate-rust's tests/canonical_json_vectors.rs drift guard reads these vectors at every test run; the build script reads them at every cargo build; both fail loudly on byte-level drift.

Forward compatibility: gate-rust v1.0.0 also accepts vectors from any later gate-python minor version whose canonical-JSON algorithm has not changed. If a future gate-python release breaks byte equivalence, gate-rust v1.1 ships with the new algorithm and the cross-version pairing table is updated.

Canonical JSON: pragmatic subset of RFC 8785

gate-rust's canonical_json replicates gate-python's gate.hashing.canonical_json behaviour exactly, which is a pragmatic subset of RFC 8785 (sort_keys=True, separators=(",", ":"), ensure_ascii=False). It is NOT a full RFC 8785 implementation; consumers needing strict RFC 8785 elsewhere in their stack should not assume equivalence.

The thirteen reference vectors are the byte-level contract. Empirical verification at v1.0.0 cut confirmed all thirteen round-trip byte-for-byte with serde_json defaults plus recursive key sort. No custom number-formatting layer is needed for gate-python compatibility.

Bundled schema set

gate-rust v1.0.0 bundles 15 JSON Schema files matching gate-python v1.2.0's pinned schema set. The bundled src/schemas/MANIFEST.yaml structurally includes 6 additional entries marked PENDING_AT_RELEASE, deferred to gate-rust v1.1 alongside gate-python v1.3.0; these are not listable via list_schemas() or loadable via load_schema() in v1.0.0. The total of 21 manifest entries matches gate-python's manifest entry-for-entry: each entry's schema name and the sha256 hash recorded against it are identical across the two manifests. The MANIFEST.yaml files themselves are not byte-identical (the gate-python and gate-rust generators write YAML with slightly different field ordering and indentation), but the hash content the manifests certify matches across the two languages.

The build script verifies bundled schemas hash to their MANIFEST sha256s at every cargo build. The CI drift guard at tests/schema_manifest_drift.rs separately verifies the bundled set against the gate-contracts source-of-truth when monorepo-adjacent.

MSRV

Minimum supported Rust version: 1.78. MSRV upgrades happen in minor releases only, never in patch releases.

Feature flags

Feature Default Purpose
canonical-json on Canonical-JSON serialisation + SHA-256 hashing. Foundation; always available.
schemas on Bundled JSON Schema files via gate_rust::schemas::load_schema / list_schemas.
envelopes on ToolRequestEnvelope + ToolResponseEnvelope builders. Pulls serde, time, uuid.
ledger on LedgerEvent + LedgerChain + verify_chain. Pulls serde, time, uuid.
signing on ES256 sign + verify + KeyRegistry. Pulls base64, ring.

Downstream consumers wanting canonical-JSON only can opt out via default-features = false, features = ["canonical-json"]. The minimal feature set has been verified to compile and test cleanly (15 tests, no serde / time / uuid / ring dependencies pulled).

A xtask feature gates the maintainer-only regenerate_schema_manifest binary; downstream consumers do not enable it.

Compatibility-preserving deviations from idiomatic Rust

Two signing-module behaviours match gate-python's contract rather than Rust's typical error-handling style. Both are deliberate; cross-language compatibility takes precedence over idiom here.

  • verify_signature(payload, record, public_key) -> Result<bool> returns Ok(false) for an invalid signature OR malformed base64. Err is reserved for unsupported algorithm strings only. Mirrors gate-python signing.py:204-271 ("never raises on invalid signature").
  • KeyRegistry::verify(payload, record) -> Result<bool> returns Ok(false) when the signing_key_id is not registered. Err is not returned for missing keys. Mirrors gate-python KeyRegistry.verify.

Consumers expecting an Err on missing key or invalid signature should wrap with their own adapter.

PEM serialisation

gate-rust v1.0.0 returns PKCS8 DER bytes from SigningKey::generate and consumes PKCS8 DER in SigningKey::from_pkcs8. PEM wrapping is mechanical: base64-encode the DER + add -----BEGIN PRIVATE KEY----- / -----END PRIVATE KEY----- headers + wrap at 64 chars. PEM helpers are a v1.1 candidate if downstream demand surfaces.

For public keys, gate-rust v1.0.0 returns SEC1 uncompressed bytes (0x04 || X || Y, 65 bytes for P-256) from SigningKey::public_key_bytes. SPKI / PEM wrapping is similarly mechanical for now.

Crypto stack

ES256 signing uses ring 0.17 with ECDSA_P256_SHA256_ASN1_SIGNING for signing and ECDSA_P256_SHA256_ASN1 for verification. Signatures are ASN.1 DER encoded, matching gate-python's cryptography library default. The fixed-width 64-byte format (ring's _FIXED variant) is NOT used and would break cross-language signature interpretation.

ring stagnation or fork is a known ecosystem risk. Documented swap candidates for a future minor release: aws-lc-rs (drop-in for ring's API surface), p256 + ecdsa (pure-Rust RustCrypto suite). Neither is shipped in v1.0.0; revisit at v1.1 if downstream demand surfaces.

Drift guard discipline

The CI workflow (.github/workflows/ci.yml) runs cargo test --locked --all-features, cargo clippy --locked --all-features --all-targets -- -D warnings, and a non-skippable vector-file digest assertion. cargo test and cargo build in turn exercise the drift guards below.

Build-time (cargo build fails on drift):

  1. Bundled canonical_json_vectors.json matches the gate-python source-of-truth (skip-on-missing when the monorepo-adjacent source is absent).
  2. Bundled schemas hash to their MANIFEST.yaml entries (always active; no skip path).

Test-time (cargo test fails on drift):

  1. All 14 positive canonical-JSON vectors round-trip byte + sha256 equal (skip-on-missing when the vector file is absent, which cannot happen in this repo because the copy is bundled).
  2. The negative vectors (exponent-range floats) normalise via round4 to the cross-language-agreed canonical bytes and hash, and each raw serialisation matches the recorded Rust output.
  3. Bundled schemas hash-equivalent to gate-contracts source for overlapping schemas (skip-on-missing when standalone).

The skip-on-missing pattern (guards 1, 3, 5) follows the W5 precedent (test_reason_codes_present_in_rego_policy): these guards fail loudly when their cross-workstream input is present and diverges, and print a skip notice when it is absent (standalone consumer install).

Because both repos publish standalone, the vector file exists as a committed copy in each. The non-skippable cross-repo identity check lives in CI: both gate-rust and gate-python assert the bundled canonical_json_vectors.json matches a checked-in canonical_json_vectors.sha256. The two repos carry an identical digest; when the vectors change, update the .sha256 file in both repos in the same change.

Quick start

use gate_rust::canonical_json::gate_hash;
use gate_rust::envelopes::{
    Decision, Environment, RiskTier, Status, ToolCategory,
    ToolRequestBuilder, ToolResponseBuilder,
};
use gate_rust::ledger::{
    build_event, verify_chain, ActionType, BuildEventParams,
    RetentionClass, GENESIS,
};
use gate_rust::signing::{sign_event_hash, KeyRegistry, SigningKey};
use serde_json::json;

# fn main() -> Result<(), Box<dyn std::error::Error>> {
let request = ToolRequestBuilder::new()
    .run_id("11111111-1111-1111-1111-111111111111")
    .trace_id("trace-1")
    .tenant_id("acme")
    .environment(Environment::Prod)
    .agent("spiffe://org/agent/treasury#run-1",
           "treasury-agent", "2.1.0", true)
    .tool("transfer_funds", ToolCategory::Financial, RiskTier::High)
    .payload(json!({"amount": 500, "to": "acct-2"}))
    .policy_bundle_hash(format!("sha256:{}", "a".repeat(64)))
    .tool_schema_hash(format!("sha256:{}", "b".repeat(64)))
    .build()?;

let response = ToolResponseBuilder::new()
    .request_envelope(request.clone())
    .tool_output(json!({"transfer_id": "tx-1"}))
    .status(Status::Success)
    .duration_ms(217)
    .decision_id("dec-1")
    .decision(Decision::Allow)
    .obligations(vec!["audit_log".to_string()])
    .policy_bundle_hash(request.bundles.policy_bundle_hash.clone())
    .ledger_event_id("led-1")
    .build()?;

let event = build_event(BuildEventParams {
    run_id: request.run_id.clone(),
    tenant_id: request.tenant_id.clone(),
    environment: "prod".to_string(),
    action_type: ActionType::ToolInvoke,
    policy_decision_id: response.policy.decision_id.clone(),
    tool_request_hash: request.hashes.request_hash.clone(),
    tool_response_hash: response.hashes.response_hash.clone(),
    prev_event_hash: GENESIS.to_string(),
    sink_uri: "worm://acme/audit/prod/".to_string(),
    retention_class: RetentionClass::ProdHot365d,
    trace_id: Some(request.trace_id.clone()),
    replay_trace_step_id: None,
    hitl_approval_id: None,
    invariant_bundle_hash: None,
    tool_name: Some(request.tool.name.clone()),
    agent_instance_id: Some(request.agent.agent_instance_id.clone()),
    sequence_number: Some(1),
});

let (key, _pkcs8) = SigningKey::generate()?;
let mut registry = KeyRegistry::new();
registry.register_private("kid-treasury-2026", &key);

let signature = sign_event_hash(
    &event.hash_chain.event_hash,
    &key,
    "kid-treasury-2026",
)?;

assert!(verify_chain(&[event.clone()], GENESIS).passed);
let public_key = key.public_key_bytes();
assert!(gate_rust::signing::verify_event_hash_signature(
    &event.hash_chain.event_hash,
    &signature,
    &public_key,
)?);
# Ok(())
# }

Installation

v1.4 deliberately defers crates.io publication. Operators install from a tagged git ref:

cargo add gate-rust --git https://github.com/deterministic-agents/gate-rust --tag v1.0.0

Status

gate-rust v1.0.0 is released as part of the coordinated GATE v1.4 framework release (paper-update 10). It pairs with gate-python v1.2.0 and gate-contracts v1.2.0. See CHANGELOG-v1.0.0.md for the full release notes and BUNDLE-REVIEW.md for the bundle review summary.

License

MIT. See LICENSE.

About

Rust companion crate for GATE: canonical JSON, envelopes, hash-chained ledger, ES256 signing

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages