Call Signature Reference

The call signature is a 256-bit BLAKE3 hash that uniquely identifies one tool call invocation. It provides exactly-once execution semantics across all SDKs and services.


Algorithm

Input byte stream

[session_id: 16 bytes]
[step_index: 4 bytes LE]
[tool_name_len: 4 bytes LE][tool_name: N bytes UTF-8]
[canonical_args_len: 4 bytes LE][canonical_args: M bytes UTF-8]

Output

64-character lowercase hex string (BLAKE3-256 hash of the above byte stream).

Implementation

Rust (undolog-types/src/effect.rs):

let mut hasher = blake3::Hasher::new();
hasher.update(session_id.as_uuid().as_bytes());
hasher.update(&step_index.to_le_bytes());
let name_bytes = tool_name.as_bytes();
hasher.update(&(name_bytes.len() as u32).to_le_bytes());
hasher.update(name_bytes);
let canon = canonical_json(canonical_args);
let args_bytes = canon.as_bytes();
hasher.update(&(args_bytes.len() as u32).to_le_bytes());
hasher.update(args_bytes);
Self(hasher.finalize().to_hex().to_string())

Python (undolog_sdk/signature.py):

sid = uuid.UUID(session_id)
hasher = blake3.blake3()
hasher.update(sid.bytes)
hasher.update(struct.pack("<I", step_index))
hasher.update(struct.pack("<I", len(name_bytes)))
hasher.update(name_bytes)
hasher.update(struct.pack("<I", len(args_bytes)))
hasher.update(args_bytes)
return hasher.hexdigest()

Canonical JSON

Deterministic, sorted-key JSON string suitable for hashing. Produces byte-for-byte identical output across Rust, Python, and Go.

Rules

Rule Description
Key sorting Object keys are sorted lexicographically ascending. Recursively applied for nested objects.
No whitespace Compact representation with no spaces, tabs, or newlines.
Leaf values serde_json / json.dumps default serialisation for strings, numbers, booleans, and null.
Arrays Preserved in insertion order. Elements recursively canonicalised.

Examples

Input Canonical JSON
{"b": 1, "a": 2} {"a":2,"b":1}
{"z": {"b": 2, "a": 1}} {"z":{"a":1,"b":2}}
[3, 1, 2] [3,1,2]
{"name": "alice", "age": 30} {"age":30,"name":"alice"}

Implementation references

Language Location
Rust undolog-types/src/effect.rs: pub fn canonical_json(v: &serde_json::Value) -> String
Python undolog_sdk/signature.py: def canonical_json(value: Any) -> str
Go services/undolog-proxy/internal/proxy/signature.go: func writeCanonicalJSON(buf *bytes.Buffer, v any) error

BLAKE3 Details

Property Value
Algorithm BLAKE3 (default mode)
Output length 256 bits (32 bytes)
Hex length 64 characters
Character set [0-9a-f] (lowercase)
Language blake3 Rust crate / blake3 Python PyPI package

Cross-language invariant

Same inputs → same 64-char hex signature, regardless of language or platform.


Collision Properties

Property Value
Preimage resistance 256 bits
Collision resistance 128 bits (BLAKE3 birthday bound)
Unique constraint undolog_effect_log.call_signature has UNIQUE index
Conflict strategy INSERT ... ON CONFLICT (call_signature) DO NOTHING
Collision probability Less than 2^-128 for distinct inputs. Sufficient for the application use case.

Input uniqueness

The length-prefixed encoding format prevents boundary attacks where two different (tool_name, args) pairs could produce the same byte sequence without delimiters. For example:

step=1, tool="ab", args="c"     → 01|00:00:00|02:00:00:00|ab|01:00:00:00|c
step=1, tool="a",  args="bc"    → 01|00:00:00|01:00:00:00|a |02:00:00:00|bc

The length prefixes ensure these produce different hashes even though the concatenated raw bytes would overlap.


FNV-1a Advisory Lock Derivation

The call signature is used to derive PostgreSQL advisory lock keys for concurrent write protection.

Algorithm

FNV-1a 64-bit hash of the 64-char hex signature string
→ int64 (signed, for pg_try_advisory_xact_lock)

Implementation references

Language Location
Rust undolog-engine/src/engine.rs internal logic
Go services/undolog-proxy/internal/lock/advisory.go: func AdvisoryLockKey(signature string) int64

Lock strategy

1. Compute AdvisoryLockKey(call_signature)
2. SELECT pg_try_advisory_xact_lock(key)
3. If false: retry up to UNDOLOG_LOCK_MAX_ATTEMPTS with UNDOLOG_LOCK_RETRY_MS backoff
4. If still false: return AdvisoryLockTimeout error
5. INSERT INTO undolog_effect_log ... ON CONFLICT (call_signature) DO NOTHING

The advisory lock is a performance optimisation (avoids write conflict rollbacks under high concurrency). The UNIQUE constraint on call_signature is the last-resort safety net.


Database Column

Column Type Constraint
call_signature char(64) NOT NULL, UNIQUE across all partitions

Testing properties

Property Test
Deterministic Same inputs → same output every time
Length Always 64 lowercase hex characters
Step sensitivity Different step_index → different signature
Args sensitivity Different args → different signature
Key order invariance {"b":1,"a":2} and {"a":2,"b":1} produce the same signature
Cross-language Python, Rust, and Go produce identical output for identical inputs