MandateClaw-Registry
The signing, storage, and audit layer for MandateClaw contracts.
MandateClaw-Registry is a Rails engine that receives signed MandateClaw contracts, stores them in an append-only ledger, links them to transition logs, and records breach events — even when the action was blocked.
What it does
| Responsibility | How |
|---|---|
| Store signed contracts | mandate_claw_signed_contracts — immutable after creation |
| Sign with Ed25519 | MandateClaw::Registry::Signing::Ed25519Signer |
| Sign with HMAC-SHA256 | MandateClaw::Registry::Signing::HmacSigner (v0 deployments) |
| Record breach attempts | mandate_claw_breach_events — append-only, even blocked actions |
| Link transitions to contracts | mandate_claw_transition_links — FOSM transition ↔ contract |
| Verify contract validity | POST /api/v1/contracts/:id/verify |
| Revoke contracts | POST /api/v1/contracts/:id/revoke |
Installation
# Gemfile
gem "mandateclaw-registry"
Run the migrations:
bundle exec rails mandate_claw_registry:install:migrations
bundle exec rails db:migrate
Mount the engine in your config/routes.rb:
mount MandateClaw::Registry::Engine => "/mandate_claw"
Signing and Anchoring a Contract
require "mandate_claw/registry"
# 1. Instantiate a task contract from a DSL template
tc = MandateClaw::Registry::TaskContract.new(
template: InvoiceContract, # a MandateClaw::DSL::Contract subclass
scope: invoice, # the Rails object this contract governs
parties: {
buyer: current_user,
seller: merchant,
ai_agent: agent
},
validity: 24.hours
)
# 2. Each party signs the canonical contract digest
tc.sign_as(:buyer,
key: current_user.signing_key_hex, # Ed25519 signing key
algorithm: :ed25519)
tc.sign_as(:ai_agent,
key: agent.capability_key_hex,
attests: agent.capability_manifest, # what the agent claims it can do
algorithm: :ed25519)
tc.sign_as(:seller,
key: merchant.signing_key_hex)
# 3. Anchor — persists to the registry database
record = tc.anchor!
# record.id is now the contract_id to attach to all subsequent transitions
puts record.contract_digest # SHA-256 of the canonical payload
Recording a Breach Event
When the runtime blocks a prohibited action, record the attempt:
contract = MandateClaw::SignedContract.find(contract_id)
contract.breach_events.create!(
breach_type: :prohibition_attempted,
party: "ai_agent",
action: "modify_amount",
occurred_at: Time.current,
context_json: { invoice_id: invoice.id, attempted_value: 9999 }.to_json
)
The breach is stored even though the action was blocked. This is the audit evidence.
JSON API
Create a contract
POST /mandate_claw/api/v1/contracts
Content-Type: application/json
{
"contract": {
"contract_digest": "abc123...",
"template_name": "invoice",
"scope_type": "Invoice",
"scope_id": "42",
"parties_json": "{...}",
"signatures_json": "{...}",
"rendered_markdown": "# Contract: Invoice\n...",
"expires_at": "2026-05-22T09:00:00Z"
}
}
Verify a contract
POST /mandate_claw/api/v1/contracts/:id/verify
→ { "valid": true, "status": "active", "expires_at": "..." }
List breach events
GET /mandate_claw/api/v1/contracts/:id/breach_events
Database Schema
mandate_claw_signed_contracts
id, contract_digest (unique), template_name, scope_type, scope_id,
parties_json, signatures_json, rendered_markdown,
status (active/expired/revoked), expires_at, revoked_at, revocation_reason,
created_at, updated_at
mandate_claw_breach_events
id, signed_contract_id (nullable), breach_type, party, action,
context_json, occurred_at, created_at, updated_at
mandate_claw_transition_links
id, signed_contract_id, transition_log_id (unique),
transition_model, actor_party, event_name, transitioned_at,
created_at, updated_at
Architecture
mandateclaw-dsl → defines the contract
mandateclaw-registry → signs, stores, audits (this gem)
MandateClaw Cloud → Merkle ledger, regulator webhooks, dashboards
The registry is intentionally minimal — a single Postgres schema and a JSON API. All advanced features (Merkle anchoring, multi-tenant, regulator integrations, compliance dashboards) are MandateClaw Cloud.
License
FSL-1.1-Apache-2.0 — converts to Apache 2.0 four years after publication. Commercial use before the Change Date requires a license from abhishek@parolkar.com.
Copyright (c) 2026 Abhishek Parolkar