Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ text output to print only the `PASS:` summary on successful verification.

### Verify receipts from other producers

The frozen [receipt v1 specification](docs/spec/RECEIPT-v1.md) defines the signed bytes and verification rules.

The receipt protocol lives in a public, standard-library-only Go package:
[`pkg/receiptspec`](pkg/receiptspec). It holds the receipt type, canonical
hashing, Ed25519 signing, chain and export verification, and JSONL encoding.
Expand Down
663 changes: 663 additions & 0 deletions docs/spec/RECEIPT-v1.md

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions pkg/receiptspec/DECISION.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Decision records from non-gateway producers

See the frozen [receipt v1 specification](../../docs/spec/RECEIPT-v1.md) for bytes, signatures, and verification limits.

A producer that is not the AgentGate gateway (for example the Clawdlinux
operator) can record an arbitrary decision as a normal v1 receipt. The wire
format does not change. Any existing `agentgate-verify` checks the chain.
Expand Down
2 changes: 2 additions & 0 deletions pkg/receiptspec/receipt.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Licensed under the Apache License, Version 2.0.
// canonical hashing, Ed25519 signing, hash-chain verification, JSONL encoding,
// signed export manifests, and decision-record binding.
//
// The frozen wire contract is documented in docs/spec/RECEIPT-v1.md.
//
// Any producer (the AgentGate gateway, an operator, a batch job) can build a
// chain with Chain and any verifier can check it with VerifyChain or
// VerifyBundle. The on-the-wire format is the same for every producer.
Expand Down
6 changes: 6 additions & 0 deletions pkg/receiptspec/testdata/v1/bad-prev-hash.jsonl
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{"type":"manifest","format_version":1,"requested_from":1,"resolved_to":3,"count":3,"anchor_seq":0,"anchor_hash":"0000000000000000000000000000000000000000000000000000000000000000","first_entry_hash":"b9647a242fa29e0bbb32d5d3e1157165b4a5ebc24a1b04c71d6f4b4d03cba138","last_entry_hash":"559b87ae267e480b80e34aacf5111f94d200c94ef8fa111296965cad3d43b695","head_seq":3,"head_hash":"559b87ae267e480b80e34aacf5111f94d200c94ef8fa111296965cad3d43b695","keyset_digest":"9877fcf6988d2245cabc51a00b26fa546ef0c9209e17a5fd66c6cda80fe075eb","signer_kid":"ed25519:24f6ed6acbfe1009","signature":"38a522420dd49b670936d9c003c596bbec6c7ea036856e1e6430f320da92de8225b45cd43af5b863e6b6889620bebbc42b8cb450660a7e2136654cd61c23a20e"}
{"type":"key","kid":"ed25519:56475aa75463474c","public_key_hex":"03a107bff3ce10be1d70dd18e74bc09967e4d6309ba50d5f1ddc8664125531b8","valid_from_seq":1,"valid_until_seq":2}
{"type":"key","kid":"ed25519:24f6ed6acbfe1009","public_key_hex":"29acbae141bccaf0b22e1a94d34d0bc7361e526d0bfe12c89794bc9322966dd7","valid_from_seq":3,"valid_until_seq":null}
{"type":"receipt","format_version":1,"seq":1,"timestamp_unix_ns":1,"human_principal":"h","agent_key_id":"a","delegation_chain":null,"service":"s","action":"x","params_sha256":"44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","policy_decision":"allow","status_code":100,"latency_ms":0,"error":"","prev_hash":"0000000000000000000000000000000000000000000000000000000000000000","entry_hash":"b9647a242fa29e0bbb32d5d3e1157165b4a5ebc24a1b04c71d6f4b4d03cba138","signer_kid":"ed25519:56475aa75463474c","signature":"9b0b99df7d812a725ed1f810bbf913bf86b15de3152808cf97dc9ecdc45eb6e2f0481c80490d77d140cc48152c2d337871ebffa343dc69c5f18ee4d294ea1705"}
{"type":"receipt","format_version":1,"seq":2,"timestamp_unix_ns":1700000000000000001,"human_principal":"human-é/é/😀","agent_key_id":"agent-2","delegation_chain":["d2d8e9aea4f592e1ca94318065976dbe9147e57eaf6eb56e14ca9ae6004c6bae","482d488bd94eee647aa10471442b576f78c4646767b16616929cdef236d9eeb6"],"service":"operator","action":"decision:scale","params_sha256":"d110a9af75f1fb6bdecaf9590b2fa2b99df60f007e5ed4b37c72d57edbf11186","policy_decision":"deny","status_code":403,"latency_ms":17,"error":"scope_denied","prev_hash":"b8647a242fa29e0bbb32d5d3e1157165b4a5ebc24a1b04c71d6f4b4d03cba138","entry_hash":"b2a27707d518092a9fa6b069a0ef1907489ed66ad9ae3196d4bffafe3affcfe2","signer_kid":"ed25519:56475aa75463474c","signature":"d070daaf2edf8115f713d5749e4d8894ed4dfd3191d74d0d0f1d13082247bdb54055fe1dd818607edd9f8b26f86f4eed16cbcac784f509c68b1dcf3f672fe60e"}
{"type":"receipt","format_version":1,"seq":3,"timestamp_unix_ns":18446744073709551615,"human_principal":"h","agent_key_id":"a","delegation_chain":null,"service":"s","action":"x","params_sha256":"44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","policy_decision":"rate_limited","status_code":599,"latency_ms":9223372036854775807,"error":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","prev_hash":"b2a27707d518092a9fa6b069a0ef1907489ed66ad9ae3196d4bffafe3affcfe2","entry_hash":"559b87ae267e480b80e34aacf5111f94d200c94ef8fa111296965cad3d43b695","signer_kid":"ed25519:24f6ed6acbfe1009","signature":"628208d560ad848a6635343e2d0c0ab1b8f2bc07f2e6ff69c8f9f8bb11a54f5a70f8a26e42dafb797146ac252f6df58fa2d976d306fd17a9ce336bf84692f600"}
2 changes: 2 additions & 0 deletions pkg/receiptspec/testdata/v1/empty-chain.jsonl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{"type":"key","kid":"ed25519:56475aa75463474c","public_key_hex":"03a107bff3ce10be1d70dd18e74bc09967e4d6309ba50d5f1ddc8664125531b8","valid_from_seq":1,"valid_until_seq":2}
{"type":"key","kid":"ed25519:24f6ed6acbfe1009","public_key_hex":"29acbae141bccaf0b22e1a94d34d0bc7361e526d0bfe12c89794bc9322966dd7","valid_from_seq":3,"valid_until_seq":null}
42 changes: 42 additions & 0 deletions pkg/receiptspec/testdata/v1/encoding-lock.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"valid-boundary": {
"receipts": [
"393f5d597c088647185b507af322f38730372ae5f722581f98996a1d69c65ade"
],
"manifest": "9f159bca2f5d724207008e5e997a4931b68d8fd4f3077358034f24dab74a50f5"
},
"valid-chain": {
"receipts": [
"b9647a242fa29e0bbb32d5d3e1157165b4a5ebc24a1b04c71d6f4b4d03cba138",
"b2a27707d518092a9fa6b069a0ef1907489ed66ad9ae3196d4bffafe3affcfe2",
"559b87ae267e480b80e34aacf5111f94d200c94ef8fa111296965cad3d43b695"
],
"manifest": "8e8fb7b9943680a29e9ed35e93ad243d59e2b6ddf3c95c003e2c678d99810155"
},
"valid-genesis": {
"receipts": [
"b9647a242fa29e0bbb32d5d3e1157165b4a5ebc24a1b04c71d6f4b4d03cba138"
]
},
"valid-partial": {
"receipts": [
"b2a27707d518092a9fa6b069a0ef1907489ed66ad9ae3196d4bffafe3affcfe2",
"559b87ae267e480b80e34aacf5111f94d200c94ef8fa111296965cad3d43b695"
],
"manifest": "74a034547f23baccf6b021c8e44bbcc72bd16c032ae649393bbbbc6ef1c3dc40"
},
"valid-unchecked-manifest-count": {
"receipts": [
"b9647a242fa29e0bbb32d5d3e1157165b4a5ebc24a1b04c71d6f4b4d03cba138",
"b2a27707d518092a9fa6b069a0ef1907489ed66ad9ae3196d4bffafe3affcfe2",
"559b87ae267e480b80e34aacf5111f94d200c94ef8fa111296965cad3d43b695"
],
"manifest": "965801b7579b6d543114d768ea871ced95867776f73f612bdb3133855b9597e8"
},
"valid-unpinned-prefix": {
"receipts": [
"b9647a242fa29e0bbb32d5d3e1157165b4a5ebc24a1b04c71d6f4b4d03cba138",
"b2a27707d518092a9fa6b069a0ef1907489ed66ad9ae3196d4bffafe3affcfe2"
]
}
}
9 changes: 9 additions & 0 deletions pkg/receiptspec/testdata/v1/example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"seed_hex": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
"public_key_hex": "03a107bff3ce10be1d70dd18e74bc09967e4d6309ba50d5f1ddc8664125531b8",
"kid": "ed25519:56475aa75463474c",
"preimage_hex": "6167656e74676174652e726563656970742e686173682e7631000100000000000000010000000000000001000000680100000061000000000100000073010000007844136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a05000000616c6c6f7764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018000000656432353531393a35363437356161373534363334373463",
"preimage_bytes": 183,
"entry_hash": "b9647a242fa29e0bbb32d5d3e1157165b4a5ebc24a1b04c71d6f4b4d03cba138",
"signature": "9b0b99df7d812a725ed1f810bbf913bf86b15de3152808cf97dc9ecdc45eb6e2f0481c80490d77d140cc48152c2d337871ebffa343dc69c5f18ee4d294ea1705"
}
262 changes: 262 additions & 0 deletions pkg/receiptspec/testdata/v1/gen/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
/*
Copyright 2026 Clawdlinux.
Licensed under the Apache License, Version 2.0.
*/

// Command gen writes deterministic v1 receipt fixtures. Keys are test-only.
package main

import (
"bytes"
"crypto/ed25519"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"flag"
"fmt"
"os"
"path/filepath"
"strings"

"github.com/Clawdlinux/agentgate/pkg/receiptspec"
)

type outcome struct {
Class string `json:"class"`
VerifiedCount int `json:"verified_count"`
FailedAtSeq uint64 `json:"failed_at_seq"`
Complete bool `json:"complete"`
}

type vector struct {
Name string `json:"name"`
JSONL string `json:"jsonl"`
Keys string `json:"keys"`
Expected outcome `json:"expected"`
}

type catalog struct {
FormatVersion int `json:"format_version"`
Vectors []vector `json:"vectors"`
}

func main() {
out := flag.String("out", "", "output directory (required)")
flag.Parse()
if *out == "" {
fatal(fmt.Errorf("-out is required"))
}
if err := os.MkdirAll(*out, 0o755); err != nil {
fatal(err)
}
generate(*out)
}

func generate(out string) {
var seedA, seedB [ed25519.SeedSize]byte
for i := range seedA {
seedA[i], seedB[i] = byte(i), byte(i+32)
}
privA := ed25519.NewKeyFromSeed(seedA[:])
privB := ed25519.NewKeyFromSeed(seedB[:])
signerA := receiptspec.NewEd25519Signer(privA)
signerB := receiptspec.NewEd25519Signer(privB)
until := uint64(2)
keys := []receiptspec.TrustedKey{
{KID: signerA.KID(), PublicKey: privA.Public().(ed25519.PublicKey), ValidFromSeq: 1, ValidUntilSeq: &until},
{KID: signerB.KID(), PublicKey: privB.Public().(ed25519.PublicKey), ValidFromSeq: 3},
}
writeTrust(out, "public-keys.json", keys)

base := receiptspec.Fields{
TimestampUnixNS: 1, HumanPrincipal: "h", AgentKeyID: "a",
Service: "s", Action: "x", ParamsSHA256: sha256.Sum256([]byte("{}")),
PolicyDecision: receiptspec.DecisionAllow, StatusCode: 100,
}
chainA := receiptspec.NewChain(signerA, 0, [32]byte{})
first, err := chainA.Next(base)
check(err)
secondFields := base
secondFields.TimestampUnixNS = 1_700_000_000_000_000_001
secondFields.HumanPrincipal = "human-é/e\u0301/😀"
secondFields.AgentKeyID = "agent-2"
secondFields.Service, secondFields.Action = "operator", "decision:scale"
secondFields.PolicyDecision, secondFields.StatusCode = receiptspec.DecisionDeny, 403
secondFields.Error, secondFields.LatencyMS = "scope_denied", 17
for _, code := range []string{`check if true`, `check if "github" == "github"`} {
digest := sha256.Sum256([]byte(code))
secondFields.DelegationChain = append(secondFields.DelegationChain, hex.EncodeToString(digest[:]))
}
secondFields.ParamsSHA256, err = receiptspec.HashRecord(json.RawMessage(`{"replicas":3,"note":"é/e\u0301/😀","confidence":"0.875"}`))
check(err)
second, err := chainA.Next(secondFields)
check(err)
chainB := receiptspec.NewChain(signerB, second.Seq, second.EntryHash)
thirdFields := base
thirdFields.TimestampUnixNS = ^uint64(0)
thirdFields.LatencyMS = int64(^uint64(0) >> 1)
thirdFields.StatusCode, thirdFields.PolicyDecision = 599, receiptspec.DecisionRateLimited
thirdFields.Error = strings.Repeat("e", 64)
third, err := chainB.Next(thirdFields)
check(err)
receipts := []receiptspec.Receipt{first, second, third}

manifest := func(rs []receiptspec.Receipt, anchor receiptspec.Anchor, head receiptspec.Receipt) *receiptspec.ExportManifest {
m := receiptspec.ExportManifest{
FormatVersion: 1, RequestedFrom: rs[0].Seq, ResolvedTo: rs[len(rs)-1].Seq,
Count: len(rs), AnchorSeq: anchor.Seq, AnchorHash: anchor.EntryHash,
FirstEntryHash: rs[0].EntryHash, LastEntryHash: rs[len(rs)-1].EntryHash,
HeadSeq: head.Seq, HeadHash: head.EntryHash, KeysetDigest: receiptspec.ComputeKeysetDigest(keys),
}
m = receiptspec.SignManifest(m, signerB.KID(), privB)
return &m
}
full := manifest(receipts, receiptspec.Anchor{}, third)
c := catalog{FormatVersion: 1}
add := func(name string, rs []receiptspec.Receipt, embedded []receiptspec.TrustedKey, m *receiptspec.ExportManifest, trust string, want outcome) {
var buf bytes.Buffer
if m != nil {
line, err := receiptspec.MarshalManifestLine(*m)
check(err)
buf.Write(line)
buf.WriteByte('\n')
}
for _, key := range embedded {
line, err := receiptspec.MarshalKeyLine(key)
check(err)
buf.Write(line)
buf.WriteByte('\n')
}
check(receiptspec.WriteJSONL(&buf, rs))
write(out, name+".jsonl", buf.Bytes())
c.Vectors = append(c.Vectors, vector{Name: name, JSONL: name + ".jsonl", Keys: trust, Expected: want})
}
addCommon := func(name string, rs []receiptspec.Receipt, m *receiptspec.ExportManifest, want outcome) {
add(name, rs, keys, m, "public-keys.json", want)
}
addCommon("valid-genesis", receipts[:1], nil, outcome{Class: "ok", VerifiedCount: 1})
addCommon("valid-chain", receipts, full, outcome{Class: "ok", VerifiedCount: 3, Complete: true})
addCommon("valid-partial", receipts[1:], manifest(receipts[1:], receiptspec.Anchor{Seq: 1, EntryHash: first.EntryHash}, third),
outcome{Class: "ok", VerifiedCount: 2, Complete: true})
addCommon("valid-unpinned-prefix", receipts[:2], nil, outcome{Class: "ok", VerifiedCount: 2})

maxFields := thirdFields
maxFields.HumanPrincipal = strings.Repeat("é", 128)
maxFields.AgentKeyID, maxFields.Service, maxFields.Action = strings.Repeat("a", 128), strings.Repeat("s", 64), strings.Repeat("x", 128)
maxFields.DelegationChain = make([]string, 32)
for i := range maxFields.DelegationChain {
digest := sha256.Sum256([]byte(fmt.Sprintf("block-%d", i)))
maxFields.DelegationChain[i] = hex.EncodeToString(digest[:])
}
maxAnchor := receiptspec.Anchor{Seq: ^uint64(0) - 1, EntryHash: sha256.Sum256([]byte("boundary anchor"))}
maxChain := receiptspec.NewChain(signerB, maxAnchor.Seq, maxAnchor.EntryHash)
maxReceipt, err := maxChain.Next(maxFields)
check(err)
maxReceipts := []receiptspec.Receipt{maxReceipt}
addCommon("valid-boundary", maxReceipts, manifest(maxReceipts, maxAnchor, maxReceipt),
outcome{Class: "ok", VerifiedCount: 1, Complete: true})

modified := func(change func(*receiptspec.Receipt)) []receiptspec.Receipt {
copyOfReceipts := append([]receiptspec.Receipt(nil), receipts...)
change(&copyOfReceipts[1])
return copyOfReceipts
}
addCommon("tampered-body", modified(func(r *receiptspec.Receipt) { r.Action = "decision:delete" }), full,
outcome{Class: receiptspec.ReasonEntryHashMismatch, VerifiedCount: 1, FailedAtSeq: 2})
addCommon("reordered-chain", []receiptspec.Receipt{second, first, third}, full,
outcome{Class: receiptspec.ReasonSequenceGap, FailedAtSeq: 2})
addCommon("missing-middle", []receiptspec.Receipt{first, third}, full,
outcome{Class: receiptspec.ReasonSequenceGap, VerifiedCount: 1, FailedAtSeq: 3})
addCommon("missing-tail-with-manifest", receipts[:2], full,
outcome{Class: receiptspec.ReasonExpectedHeadMismatch, VerifiedCount: 2, FailedAtSeq: 3})
addCommon("wrong-key-signature", modified(func(r *receiptspec.Receipt) {
copy(r.Signature[:], ed25519.Sign(privB, r.EntryHash[:]))
}), full, outcome{Class: receiptspec.ReasonSignatureInvalid, VerifiedCount: 1, FailedAtSeq: 2})
addCommon("bad-prev-hash", modified(func(r *receiptspec.Receipt) { r.PrevHash[0] ^= 1 }), full,
outcome{Class: receiptspec.ReasonPrevHashMismatch, VerifiedCount: 1, FailedAtSeq: 2})
addCommon("invalid-fields", modified(func(r *receiptspec.Receipt) { r.StatusCode = 99 }), full,
outcome{Class: receiptspec.ReasonInvalidReceiptFields, VerifiedCount: 1, FailedAtSeq: 2})
addCommon("unknown-signer", modified(func(r *receiptspec.Receipt) { r.SignerKID = "unknown" }), full,
outcome{Class: "ErrUnknownSignerKID"})
addCommon("empty-chain", nil, nil, outcome{Class: "ErrEmptyChain"})

inactive := append([]receiptspec.TrustedKey(nil), keys...)
inactive[0].ValidFromSeq = 2
writeTrust(out, "inactive-keys.json", inactive)
add("inactive-signer", receipts[:1], inactive, nil, "inactive-keys.json",
outcome{Class: receiptspec.ReasonSignerInactiveAtSeq, FailedAtSeq: 1})

badManifest := *full
badManifest.HeadHash[0] ^= 1
addCommon("manifest-mismatch", receipts, &badManifest, outcome{Class: "ManifestError/ErrManifestSignatureInvalid"})
add("manifest-keyset-mismatch", receipts, keys[1:], full, "public-keys.json",
outcome{Class: "ManifestError/ErrKeysetDigestMismatch"})
badManifest = *full
badManifest.SignerKID = "unknown"
addCommon("manifest-unknown-signer", receipts, &badManifest, outcome{Class: "ManifestError/ErrUnknownSignerKID"})

badManifest = *full
badManifest.ResolvedTo++
badManifest = receiptspec.SignManifest(badManifest, signerB.KID(), privB)
addCommon("manifest-endpoint-mismatch", receipts, &badManifest,
outcome{Class: receiptspec.ReasonExpectedHeadMismatch, VerifiedCount: 3, FailedAtSeq: 4})
badManifest = *full
badManifest.Count = 99
badManifest = receiptspec.SignManifest(badManifest, signerB.KID(), privB)
addCommon("valid-unchecked-manifest-count", receipts, &badManifest,
outcome{Class: "ok", VerifiedCount: 3, Complete: true})

preimage, err := receiptspec.CanonicalHashInput(first)
check(err)
writeJSON(out, "example.json", struct {
SeedHex string `json:"seed_hex"`
PublicKeyHex string `json:"public_key_hex"`
KID string `json:"kid"`
PreimageHex string `json:"preimage_hex"`
PreimageBytes int `json:"preimage_bytes"`
EntryHash string `json:"entry_hash"`
Signature string `json:"signature"`
}{
hex.EncodeToString(seedA[:]), hex.EncodeToString(keys[0].PublicKey), signerA.KID(),
hex.EncodeToString(preimage), len(preimage), hex.EncodeToString(first.EntryHash[:]), hex.EncodeToString(first.Signature[:]),
})
writeJSON(out, "vectors.json", c)
}

func writeTrust(out, name string, keys []receiptspec.TrustedKey) {
var lines []json.RawMessage
for _, key := range keys {
line, err := receiptspec.MarshalKeyLine(key)
check(err)
var fields map[string]json.RawMessage
check(json.Unmarshal(line, &fields))
delete(fields, "type")
line, err = json.Marshal(fields)
check(err)
lines = append(lines, line)
}
writeJSON(out, name, struct {
Keys []json.RawMessage `json:"keys"`
}{lines})
}

func writeJSON(out, name string, value any) {
data, err := json.MarshalIndent(value, "", " ")
check(err)
write(out, name, append(data, '\n'))
}

func write(out, name string, contents []byte) {
check(os.WriteFile(filepath.Join(out, name), contents, 0o644))
}

func check(err error) {
if err != nil {
fatal(err)
}
}

func fatal(err error) {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
16 changes: 16 additions & 0 deletions pkg/receiptspec/testdata/v1/inactive-keys.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"keys": [
{
"kid": "ed25519:56475aa75463474c",
"public_key_hex": "03a107bff3ce10be1d70dd18e74bc09967e4d6309ba50d5f1ddc8664125531b8",
"valid_from_seq": 2,
"valid_until_seq": 2
},
{
"kid": "ed25519:24f6ed6acbfe1009",
"public_key_hex": "29acbae141bccaf0b22e1a94d34d0bc7361e526d0bfe12c89794bc9322966dd7",
"valid_from_seq": 3,
"valid_until_seq": null
}
]
}
3 changes: 3 additions & 0 deletions pkg/receiptspec/testdata/v1/inactive-signer.jsonl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{"type":"key","kid":"ed25519:56475aa75463474c","public_key_hex":"03a107bff3ce10be1d70dd18e74bc09967e4d6309ba50d5f1ddc8664125531b8","valid_from_seq":2,"valid_until_seq":2}
{"type":"key","kid":"ed25519:24f6ed6acbfe1009","public_key_hex":"29acbae141bccaf0b22e1a94d34d0bc7361e526d0bfe12c89794bc9322966dd7","valid_from_seq":3,"valid_until_seq":null}
{"type":"receipt","format_version":1,"seq":1,"timestamp_unix_ns":1,"human_principal":"h","agent_key_id":"a","delegation_chain":null,"service":"s","action":"x","params_sha256":"44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","policy_decision":"allow","status_code":100,"latency_ms":0,"error":"","prev_hash":"0000000000000000000000000000000000000000000000000000000000000000","entry_hash":"b9647a242fa29e0bbb32d5d3e1157165b4a5ebc24a1b04c71d6f4b4d03cba138","signer_kid":"ed25519:56475aa75463474c","signature":"9b0b99df7d812a725ed1f810bbf913bf86b15de3152808cf97dc9ecdc45eb6e2f0481c80490d77d140cc48152c2d337871ebffa343dc69c5f18ee4d294ea1705"}
Loading
Loading