Help us shape the future of Go! Take the 2026 Go Developer Survey

audit

package
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 29, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package audit turns an agent's durable journal into a verifiable, tamper-evident record. The journal already captures every step of a run (model turns, tool calls and results, approvals, saga outcomes); this package commits to that history with a hash chain and lets you sign/verify the commitment. Stdlib-only (crypto/sha256, ed25519).

Security model — read this before relying on it for compliance:

  • Head computes a SHA-256 hash chain over the run's journal in its persisted order. Any modification, insertion, deletion, or reordering of a record changes the head.
  • That gives INTEGRITY (detect corruption / partial writes) unconditionally, and TAMPER-EVIDENCE only if you ANCHOR the head out-of-band — sign it with a key the app tier doesn't fully control, and/or publish it to a separate trust domain. A hash chain stored in the same database an attacker controls can be rewritten and rehashed; the guarantee is "you committed the head elsewhere, so divergence is provable." This package returns the head and provides signing; anchoring policy is yours.

Index

Constants

View Source
const (
	KindCall          = "call"           // the model turn that requested the gated call (its name and arguments)
	KindApproval      = "approval"       // one approver decision record
	KindApprovalTally = "approval-tally" // the gate's journaled terminal tally
	KindTool          = "tool"           // the call's result: the action, or the denial the model received
)

Evidence kinds for an m-of-n approval, in the order ApprovalEvidence emits them.

View Source
const (
	AlgEd25519 = "ed25519"           // classical Ed25519 (small, fast, FIPS-approved)
	AlgMLDSA65 = "ml-dsa-65"         // post-quantum ML-DSA-65 (FIPS 204)
	AlgHybrid  = "ed25519+ml-dsa-65" // both schemes; valid only if both verify
)

Algorithm identifiers stored in SignedTreeHead.Alg.

View Source
const EvidenceFormat = "bide.audit.evidence.v1"

EvidenceFormat is the format/version tag written into every EvidencePackage. It is domain-separated and versioned so a verifier can reject a package produced by an incompatible future layout rather than misreading it.

Variables

This section is empty.

Functions

func AbsenceRoot

func AbsenceRoot(records []agent.Record, keyFn KeyFunc) []byte

AbsenceRoot is the RFC 6962 Merkle root over the run's sorted, distinct keys under keyFn. Sign it (SignTreeHead) and anchor it like any root; anyone holding the journal recomputes it to confirm the key set faithfully reflects the run.

func AttenuatingSubAgent

func AttenuatingSubAgent(name, description string, sub *agent.Agent, store agent.Durable, narrow AttenuateFunc) agent.Tool

AttenuatingSubAgent wraps a sub-agent as a delegation Tool that attenuates authority by default. On each call, if a signed grant is bound to ctx (WithGrant), it mints a narrower child grant (ParentRef hash-linked to the parent, signed by the same signer), records it as a durable leaf in the sub-run so the chain is anchored, rebinds the sub-run's identity to the child (Actor = this sub-agent, OnBehalfOf = the parent's Subject, AuthorityRef = the child's digest), and propagates the child grant so a deeper delegation narrows again. With no grant on ctx it is a plain sub-agent (it inherits the identity), so it is safe to use either way.

The effect is that capabilities only ever shrink down a delegation tree, by default rather than by remembering to wire it, and the whole chain is provable end to end via VerifyDelegationChain. It is a composition of the grant machinery into the sub-agent seam, not a new agent type: the wrapped `sub` still runs its own full agent loop and reasons autonomously within the narrower authority.

func GrantFrom

func GrantFrom(ctx context.Context) (SignedGrant, Signer, bool)

GrantFrom returns the acting signed grant and signer bound to ctx, and whether one was set.

func Head(ctx context.Context, store agent.Durable, runID string) ([]byte, error)

Head returns the hash-chain commitment to runID's journal: head_0 = H(domain), and head_i = H(head_{i-1} || canonical(record_i)) over the records in persisted order. Two runs produce the same head iff their journals are byte-identical in the same order, so the head is a deterministic fingerprint of the entire execution history.

func PersistEvent

func PersistEvent(ctx context.Context, evStore EventStore, runID string, seq int, e agent.AgentEvent) error

PersistEvent appends one event's canonical leaf to evStore at position seq. The low-level primitive behind PersistJournal, for callers streaming events into a store directly; prefer PersistJournal for the crash-safe, idempotent path.

func PersistJournal

func PersistJournal(ctx context.Context, evStore EventStore, jStore agent.Durable, runID string) error

PersistJournal mirrors runID's durable event trail into evStore by projecting the journal (agent.ReplayEvents) and appending each canonical event leaf at its position. It is IDEMPOTENT: call it after each turn, once at run end, or on a schedule — re-runs append only what is new and never fork the trail, because the projection is deterministic and resume-stable. After it returns, LoadEventLog(evStore, runID) reconstructs the committed trail for anchoring/proofs even if the journal is later deleted.

func PoliciesUsed

func PoliciesUsed(records []agent.Record) []string

PoliciesUsed returns the sorted, distinct policy digests exercised by governed actions in the run. An auditor compares this against the approved set; for any disallowed digest it then obtains an absence proof (ProveAbsentBundle with PolicyUsedKey) showing no action ran under it.

func PolicyUsedKey

func PolicyUsedKey(r agent.Record) (string, bool)

PolicyUsedKey is a KeyFunc (see ProveAbsent / ProveAbsentBundle) over governed-action leaves: a completed tool call whose result carries a policy_digest, as govern.AttestedEventTool records. It keys by that digest, so the absence machinery commits the set of policies actually exercised in a run. Proving a digest ABSENT under this KeyFunc shows that no governed action ran under that policy; recomputing AbsenceRoot lists exactly which policies were used, so an auditor can confirm every one is in the approved set.

Scope: this is a policy-level negative ("no action ran under a disallowed policy"), which, combined with the approved policies being oracle-certified convergent and invariant-preserving, supports "no violation was admitted". It is not a per-action state-validity proof (a per-transition state digest is roadmap), and it does not close the runtime refinement gap: the runtime is differentially tested against the verified reference, not proven equal to it.

func PolicyUsedKeyFor

func PolicyUsedKeyFor(digest string) string

PolicyUsedKeyFor is the absence key for a specific policy digest: pass it to ProveAbsent / ProveAbsentBundle with PolicyUsedKey to prove no governed action ran under that policy.

func Record

func Record(log *EventLog, stream *agent.AgentStream, onEvent func(agent.AgentEvent)) (agent.Message, error)

Record drains stream through log — committing every event — while forwarding each event to onEvent (nil to skip), then returns the run's terminal Message and error (including *PendingApproval / *ResumeHalt, exactly as AgentStream.Final does). One pass gives you both the live UI feed and a committed, provable audit trail. A canonicalization failure is surfaced only if the run itself did not already fail.

func RecordConvergence

func RecordConvergence(ctx context.Context, store agent.Durable, runID string, cert []byte, digest string) (agent.Record, error)

RecordConvergence commits the serialized convergence certificate as a dedicated journal leaf (idempotent per (runID, digest)), so it is covered by the same STH and inclusion proofs as the policy leaf with the same digest and the actions taken under it. Pass the certificate bytes from govern.ConfluenceCertificate.Marshal and the same digest used for the policy leaf.

func RecordGrant

func RecordGrant(ctx context.Context, store agent.Durable, runID string, sg SignedGrant) (agent.Record, error)

RecordGrant commits a signed grant as a dedicated journal leaf keyed by its digest (idempotent per (runID, digest)), so it is covered by the same signed tree head and inclusion proofs as the actions taken under it. A governed action whose Identity.AuthorityRef equals the grant's digest then links to an anchored, issuer-signed authority.

func RecordPolicy

func RecordPolicy(ctx context.Context, store agent.Durable, runID string, policy []byte, digest string) (agent.Record, error)

RecordPolicy commits the serialized policy as a dedicated journal leaf (idempotent per (runID, digest)), so the policy is covered by the same STH and inclusion proofs as the actions taken under it. The digest is the policy owner's stable identifier (e.g. gsm.Registry.PolicyDigest); audit treats it as opaque and does not recompute it from the bytes. A verifier recomputes the digest from the disclosed bytes and runs the external oracle (bide-audit verify-governance), so a leaf that lies about its digest is caught there.

func RecordRunCertificate

func RecordRunCertificate(ctx context.Context, store agent.Durable, runID string, cert RunCertificate) (agent.Record, error)

RecordRunCertificate anchors a RunCertificate as a dedicated journal leaf (idempotent per runID), so the certificate is itself covered by a signed tree head and inclusion proofs, mirroring RecordPolicy / RecordConvergence. Anchoring the certificate lets a later verifier prove the certificate was committed in the run, not produced after the fact out of band.

Note the ordering: the certificate commits to a run STH taken at some size; anchoring it grows the journal past that size. That is intended: the run STH inside the certificate is the tree the policy/convergence bundles are proven against, and a fresh STH taken after RecordRunCertificate commits the certificate leaf itself (ProveRunCertificate proves it against that later STH).

func Root

func Root(ctx context.Context, store agent.Durable, runID string) ([]byte, error)

Root returns the RFC 6962 Merkle root committing to runID's journal (records in persisted order, each leaf = the canonical record bytes). Anchor it like Head; unlike Head it supports per-record inclusion proofs (Prove / VerifyInclusion).

func Sign

func Sign(head []byte, priv ed25519.PrivateKey) []byte

Sign returns an Ed25519 signature over a head commitment — anchor this (store it in a separate trust domain) to make the journal tamper-evident against later rewrites.

func ToolUseKey

func ToolUseKey(r agent.Record) (string, bool)

ToolUseKey is the default KeyFunc: completed tool calls, keyed by their ToolUseID. It lets you prove "no tool call with this ID happened in the run."

func ToolUseKeyFor

func ToolUseKeyFor(toolUseID string) string

ToolUseKeyFor is the absence key for a tool-use id: pass it to ProveAbsent / ProveAbsentBundle with ToolUseKey to prove no tool call with that id happened in the run. It mirrors PolicyUsedKeyFor for the ToolUseKey key set.

func VerifyAbsence

func VerifyAbsence(root []byte, proof Absence) (bool, error)

VerifyAbsence reports whether proof shows Key is absent from the key set committed by root: each named neighbor is included at its index, they bracket Key in sort order, and they are ADJACENT (or Key sits before the first / after the last / the set is empty). Adjacency is the load-bearing check: without it, bracketing alone does not preclude Key being present between two non-consecutive neighbors.

func VerifyAnchorInclusion

func VerifyAnchorInclusion(root []byte, entry AnchorEntry, proof Inclusion) (bool, error)

VerifyAnchorInclusion reports whether entry is the leaf at proof.Index in an anchor log of proof.Size entries committed by root — from the entry + proof alone.

func VerifyConsistency

func VerifyConsistency(firstRoot, laterRoot []byte, proof Consistency) bool

VerifyConsistency reports whether firstRoot (an earlier Root over First records) is an append-only prefix of laterRoot (a Root over Size records), given the proof — checked from the two roots + proof alone. A rewrite or reorder of any early record fails.

func VerifyDelegationChain

func VerifyDelegationChain(chain []SignedGrant, issuerVerifier func(issuer string) (Verifier, bool), atten Attenuator) (bool, error)

VerifyDelegationChain verifies a delegation chain ordered root-first (index 0) to leaf-last:

  1. each grant's signature under its issuer's verifier (resolved via issuerVerifier),
  2. the root has no ParentRef; every other grant's ParentRef equals the previous grant's Digest,
  3. every non-root grant is an attenuation of its parent (if atten is non-nil).

It establishes that the leaf's authority descends, unbroken and never widened, from a root grant each hop's issuer signed. Expiry is left to the caller (grant.Expired) since it needs a clock.

func VerifyEventInclusion

func VerifyEventInclusion(root []byte, event agent.AgentEvent, proof Inclusion) (bool, error)

VerifyEventInclusion reports whether event is the leaf at proof.Index in a log of proof.Size events committed by root — from the event + proof alone, no other events needed. The event must canonicalize identically to when it was Add-ed.

func VerifyInclusion

func VerifyInclusion(root []byte, record agent.Record, proof Inclusion) (bool, error)

VerifyInclusion reports whether record is the leaf at proof.Index in a run of proof.Size records committed by root — checked from record + proof alone, no other records needed.

func VerifySignature

func VerifySignature(head, sig []byte, pub ed25519.PublicKey) bool

VerifySignature reports whether sig is a valid signature of head under pub.

func WithGrant

func WithGrant(ctx context.Context, sg SignedGrant, signer Signer) context.Context

WithGrant binds the acting principal's signed grant and its signer to ctx. AttenuatingSubAgent reads this to mint a narrower, signed child grant on each delegation and rebind the sub-run's identity to it. Set it once at the root (the top principal's grant and key); each delegation then propagates the narrowed child grant automatically, so authority only ever shrinks down the tree.

Types

type Absence

type Absence struct {
	Key   string    `json:"key"`             // the key claimed absent from the committed set
	Size  int       `json:"size"`            // number of committed keys the proof is against
	Left  *Neighbor `json:"left,omitempty"`  // committed key just below Key in sort order; nil if Key sorts before all
	Right *Neighbor `json:"right,omitempty"` // committed key just above Key in sort order; nil if Key sorts after all
}

Absence is a non-membership proof: Key is not among the Size committed keys, shown by the adjacent committed keys that bracket it. Left is nil when Key sorts before all keys; Right is nil when it sorts after all; both nil only for the empty key set.

func ProveAbsent

func ProveAbsent(records []agent.Record, keyFn KeyFunc, key string) (Absence, error)

ProveAbsent builds an absence proof for key over records under keyFn, or errors if the key is actually present (you cannot prove absence of something that happened).

type AbsenceBundle

type AbsenceBundle struct {
	RunID   string         `json:"run_id"`  // the run whose key set the proof is against
	Absence Absence        `json:"absence"` // the non-membership proof
	STH     SignedTreeHead `json:"sth"`     // the signed commitment to the key set the proof is proven against
}

AbsenceBundle is the portable, anchorable form of an absence proof, mirroring ProofBundle: the proof plus the signed tree head committing the key set it is proven against. Verify with an out-of-band public key; it checks the STH signature, binds the proof to the signed size, and verifies non-membership against the signed root.

func ProveAbsentBundle

func ProveAbsentBundle(records []agent.Record, keyFn KeyFunc, key string, runID string, sth SignedTreeHead) (AbsenceBundle, error)

ProveAbsentBundle builds an anchorable absence proof bound to sth, whose Root must be the AbsenceRoot of records under keyFn (the fidelity guard: the STH must commit to THIS run's key set, recomputed here, so a stale or foreign STH is rejected).

func (AbsenceBundle) Verify

func (b AbsenceBundle) Verify(pub ed25519.PublicKey) (bool, error)

Verify reports whether the bundle authentically proves absence under pub.

func (AbsenceBundle) VerifyWith

func (b AbsenceBundle) VerifyWith(v Verifier) (bool, error)

VerifyWith is the scheme-agnostic form of AbsenceBundle.Verify.

type Anchor

type Anchor interface {
	Publish(ctx context.Context, runID string, sth SignedTreeHead) error
}

Anchor is the bring-your-own port for out-of-band anchoring: publish a SignedTreeHead to a trust domain separate from the app (a Certificate-Transparency-style log, a notary / timestamping service, another account's WORM store, a public ledger). Bide ships MemAnchorLog as a reference; you implement Publish against the external log you trust.

type AnchorEntry

type AnchorEntry struct {
	Seq   int            `json:"seq"`    // the entry's position in the anchor log
	RunID string         `json:"run_id"` // the run whose commitment was published
	STH   SignedTreeHead `json:"sth"`    // the published signed tree head
}

AnchorEntry is one published commitment recorded by a transparency log: the run it commits to, its signed tree head, and the entry's position in the anchor log.

type ApprovalVerdict added in v0.4.0

type ApprovalVerdict struct {
	// Need is the expected policy's k.
	Need int `json:"need"`
	// ToolName and Args are the call the approvers decided on, from the proven request.
	ToolName string          `json:"tool_name"`
	Args     json.RawMessage `json:"args,omitempty"`
	// Counted lists the approvers whose approval counted in the recount, in journal order.
	Counted []string `json:"counted"`
	// DeniedBy lists the approvers whose counted decision was a denial.
	DeniedBy []string `json:"denied_by,omitempty"`
	// Ignored lists every other disclosed decision, with the reason it did not count.
	Ignored []IgnoredDecision `json:"ignored,omitempty"`
	// Problems lists every way the evidence is inconsistent with itself or with the expected
	// policy: an omitted decision the gate read, a recount that disagrees with the gate's
	// journaled tally, a different enforced policy, or records out of order. Any problem fails
	// the verdict.
	Problems []string `json:"problems,omitempty"`
	// OK reports no Problems and at least Need approvals counted.
	OK bool `json:"ok"`
}

ApprovalVerdict is the offline result of checking an m-of-n approval gate from evidence.

func VerifyApprovals added in v0.4.0

func VerifyApprovals(actions []EvidenceAction, toolUseID string, policy agent.ApprovalPolicy, verifierFor agent.ApproverVerifierFor, logPub ed25519.PublicKey) (ApprovalVerdict, error)

VerifyApprovals checks offline that the m-of-n gate on toolUseID held, trusting only its inputs: actions (ApprovalEvidence's output, alone or inside an EvidencePackage), the policy the auditor expects, verifierFor (each approver's key, the same resolver shape the gate uses at run time), and logPub (the log operator's key, obtained out of band).

It verifies the proofs of the request, the gate's journaled tally, and the call's result, all under one signed tree head and in that journal order, then recounts the disclosed decisions with agent.TallyApprovals, the exact rule the gate ran, against the proven call's name and arguments. It reports a Problem if the evidence omits a decision the gate read, if the recount disagrees with the journaled tally, or if the gate enforced a different policy than expected. A decision signed for other arguments, another run, or another call does not verify and is listed in Ignored.

It returns an error, not a verdict, when the evidence cannot be evaluated: an invalid policy, a missing request, tally, or result, or one of those whose proof does not verify. It certifies that the gate held as recorded, not that the approvers' judgment was right.

type AttenuateFunc

type AttenuateFunc func(parent Grant, subAgent string) Grant

AttenuateFunc derives a child grant from the parent grant and the delegating sub-agent's name. It sets the narrower Scope (and may set Subject); AttenuatingSubAgent fills in ParentRef and, if empty, Issuer (the parent's Subject) and Subject (the sub-agent name), then signs it. The func must produce a STRICTLY narrower grant: a widening delegation is a caller bug and is rejected by VerifyDelegationChain, so a broadened chain cannot pass verification even if one is minted.

type Attenuator

type Attenuator func(parent, child Grant) bool

Attenuator reports whether child's authority is within parent's (domain-specific "narrower").

func AttenuatesNumericScope

func AttenuatesNumericScope(key string) Attenuator

AttenuatesNumericScope requires child.Scope[key] <= parent.Scope[key] parsed as integers, the common "lower the limit" narrowing. Missing or unparseable values fail closed (not an attenuation), so a sub-grant cannot widen authority by dropping or corrupting a bound.

type AuditedStore

type AuditedStore struct {
	// contains filtered or unexported fields
}

AuditedStore wraps a Durable so every durable step is continuously anchored: whenever a run's journal grows, it commits an RFC 6962 Merkle root over the run, signs a Signed Tree Head, and publishes it to an Anchor (an external transparency log). The caller writes the agent loop exactly as before — audit.NewAuditedStore(store, priv, anchor) is a drop-in Durable — and gets a stream of signed, out-of-band commitments for free.

Anchoring is a SIDE CHANNEL: a publish failure NEVER fails the durable step. Failing Do because an STH could not be published would be dangerous — the underlying write already succeeded, and the caller could wrongly retry a non-idempotent step. Publish errors go to the optional OnError hook instead; the journal remains the source of truth.

func NewAuditedStore

func NewAuditedStore(inner agent.Durable, priv ed25519.PrivateKey, anchor Anchor) *AuditedStore

NewAuditedStore wraps inner so each journal growth is signed with priv and published to anchor. Timestamps default to time.Now().UnixNano(); override with WithClock for tests.

func (*AuditedStore) Do

func (a *AuditedStore) Do(ctx context.Context, runID, name string, fn func(context.Context) (agent.Record, error)) (agent.Record, error)

Do runs the step on the inner store, then (if the journal grew) anchors the new tree head.

func (*AuditedStore) History

func (a *AuditedStore) History(ctx context.Context, runID string) ([]agent.Record, error)

History delegates unchanged.

func (*AuditedStore) OnError

func (a *AuditedStore) OnError(fn func(runID string, err error)) *AuditedStore

OnError sets a hook called when anchoring a step fails (Publish, or committing the root). Without it, anchor errors are swallowed — the durable step still succeeds. Returns the store.

func (*AuditedStore) WithClock

func (a *AuditedStore) WithClock(now func() int64) *AuditedStore

WithClock sets a deterministic timestamp source (for tests). Returns the store for chaining.

type Consistency

type Consistency struct {
	First int      // m — the earlier tree size
	Size  int      // n — the current tree size
	Path  [][]byte // proof hashes
}

Consistency is an RFC 6962 consistency proof: the first First records are an append-only prefix of the Size-record tree.

func ProveConsistency

func ProveConsistency(ctx context.Context, store agent.Durable, runID string, first int) (Consistency, error)

ProveConsistency proves that runID's first `first` records are an append-only prefix of its current journal — i.e. nothing before `first` was changed or reordered, only appended.

type ConvergenceContent

type ConvergenceContent struct {
	Digest      string          `json:"digest"`      // the policy digest this certificate certifies (treated as opaque here)
	Certificate json.RawMessage `json:"certificate"` // the opaque, serialized convergence certificate bytes
}

ConvergenceContent is the payload of a convergence leaf: an opaque, serialized convergence certificate and the policy digest it certifies. Committing it as a journal leaf puts the convergence evidence under the same signed tree head and inclusion proofs as the policy and the actions taken under it, so an auditor can prove not merely that a policy was anchored but that the anchored policy was certified convergent in the same committed tree.

audit treats the certificate as opaque bytes and the digest as an opaque identifier, exactly as it does for a policy leaf: it does not import gsm and does not recompute either. The producer of the certificate is govern.CertifyConvergence; a rigorous verifier re-derives the certificate from the disclosed policy bytes (by rebuilding the gsm machine) and compares, so a leaf that overstates convergence is caught outside audit, not trusted here.

type EarnedAuthority

type EarnedAuthority struct {
	// contains filtered or unexported fields
}

EarnedAuthority is a control loop that adjusts an agent's delegated limit from its provable track record. Authority starts at the ladder's baseline rung and is promoted one rung after promoteEvery compliant actions, capped at the top rung; an anomaly resets it to baseline at once. Each change re-issues a signed grant that is a CHILD of the root grant (ParentRef to root, scope limit = the current rung), so the earned limit provably never exceeds the root ceiling: every earned grant passes VerifyDelegationChain against the root under AttenuatesNumericScope, even a buggy or compromised controller cannot widen past what the root principal authorized.

It is deliberately a sequential controller, not a convergent gsm machine. Earning is temporal: how many compliant actions preceded a promotion, and whether an anomaly interleaved, is order-dependent, so it belongs to the durable/saga tier, not the order-independent governance tier (a promote event and a record event do not commute, so no convergent machine expresses it). The ENFORCEMENT of the current limit stays a convergent gsm invariant on the work machine (see examples/earned-authority); the controller only decides the limit and re-issues the grant.

The asymmetry is the safety property: promotion is slow, capped, and evidence-gated; attenuation is immediate and needs no gate, because shrinking authority is always safe.

func NewEarnedAuthority

func NewEarnedAuthority(ladder []int, promoteEvery int, root SignedGrant, signer Signer, subject string) (*EarnedAuthority, error)

NewEarnedAuthority builds a controller for subject, granting the limits in ladder (rung 0 is the baseline) and promoting one rung every promoteEvery compliant actions. root is the ceiling: the top rung must not exceed root's numeric "limit" scope, so no earned grant can widen past what the root principal authorized. It issues the baseline grant immediately.

func (*EarnedAuthority) FlagAnomaly

func (e *EarnedAuthority) FlagAnomaly() (changed bool, err error)

FlagAnomaly resets authority to the baseline rung immediately, returning whether it changed.

func (*EarnedAuthority) Grant

func (e *EarnedAuthority) Grant() SignedGrant

Grant returns the current signed earned grant (a child of root, scope limit = the current rung).

func (*EarnedAuthority) Limit

func (e *EarnedAuthority) Limit() int

Limit returns the currently earned limit.

func (*EarnedAuthority) RecordCompliant

func (e *EarnedAuthority) RecordCompliant() (promoted bool, err error)

RecordCompliant notes one clean governed action; it promotes one rung when the streak reaches the threshold (unless already at the top), returning whether a promotion happened.

func (*EarnedAuthority) Tier

func (e *EarnedAuthority) Tier() int

Tier returns the current rung (0 is baseline).

type Ed25519Signer

type Ed25519Signer struct {
	Priv ed25519.PrivateKey // the Ed25519 private key that signs
}

Ed25519Signer signs with an Ed25519 private key.

func (Ed25519Signer) Alg

func (Ed25519Signer) Alg() string

Alg reports the signature scheme (AlgEd25519).

func (Ed25519Signer) Sign

func (s Ed25519Signer) Sign(m []byte) ([]byte, error)

Sign returns the Ed25519 signature over m.

type Ed25519Verifier

type Ed25519Verifier struct {
	Pub ed25519.PublicKey // the Ed25519 public key that verifies
}

Ed25519Verifier verifies with an Ed25519 public key.

func (Ed25519Verifier) Alg

func (Ed25519Verifier) Alg() string

Alg reports the signature scheme (AlgEd25519).

func (Ed25519Verifier) Verify

func (v Ed25519Verifier) Verify(m, sig []byte) bool

Verify reports whether sig is a valid Ed25519 signature over m.

type EventLog

type EventLog struct {
	// contains filtered or unexported fields
}

EventLog is an append-only, tamper-evident log of one run's AgentEvents. Build it by Add-ing events in emission order (Agent.Stream emits them ordered from a single goroutine); then Root/Head to commit, Prove for selective disclosure, and audit.Sign to anchor. Not safe for concurrent Add — feed it from the one goroutine ranging Events.

func EventLogFromJournal

func EventLogFromJournal(ctx context.Context, store agent.Durable, runID string) (*EventLog, error)

EventLogFromJournal builds an EventLog from runID's DURABLE journal by projecting it to the semantic events Agent.Stream re-emits on resume (agent.ReplayEvents). This is the crash-durable, resume-stable counterpart to filling an EventLog from the live stream: it is a deterministic function of the persisted journal, so its Root / STH are byte-identical before and after a crash. THIS is the artifact to anchor for a durable audit trail; the live-stream EventLog is a real-time view (and its Root shifts between a fresh run and its replay because live-only events differ). Root / Head / TreeHead / Prove / ProveConsistency then work exactly as they do on any EventLog.

func LoadEventLog

func LoadEventLog(ctx context.Context, evStore EventStore, runID string) (*EventLog, error)

LoadEventLog rebuilds an EventLog from evStore's persisted leaves for runID, ready for Root / Head / TreeHead / Prove / ProveConsistency — from the store alone, no journal needed. The leaves were canonicalized when stored, so inclusion proofs verify against the original events exactly as if the log had been built live.

func NewEventLog

func NewEventLog() *EventLog

NewEventLog returns an empty log.

func (*EventLog) Add

func (l *EventLog) Add(e agent.AgentEvent) error

Add appends one event as a canonical, kind-tagged leaf.

func (*EventLog) Head

func (l *EventLog) Head() []byte

Head is the linear SHA-256 hash-chain commitment over the events (domain-separated), the event-stream analogue of audit.Head: head_0 = H(eventDomain), head_i = H(head_{i-1} || canonical(event_i)). Cheaper than Root, no selective disclosure.

func (*EventLog) Len

func (l *EventLog) Len() int

Len is the number of events committed so far.

func (*EventLog) Prove

func (l *EventLog) Prove(index int) (Inclusion, error)

Prove returns an inclusion proof for the event at index against Root — enough to verify that one event WITHOUT revealing any other (selective disclosure over the event stream). The returned Inclusion is the same type journal proofs use, so audit.Sign over Root and this proof compose exactly as they do for the journal.

func (*EventLog) ProveConsistency

func (l *EventLog) ProveConsistency(first int) (Consistency, error)

ProveConsistency proves the first `first` events are an append-only PREFIX of the current log — nothing observed earlier was rewritten or reordered, only appended. Verify with the shared VerifyConsistency against two published event-log roots (e.g. two STH Roots taken at different points in the run). This is the transparency-log guarantee over the event stream, the same one ProveConsistency gives over the journal.

func (*EventLog) Root

func (l *EventLog) Root() []byte

Root is the RFC 6962 Merkle root over every event added so far (empty-log root = SHA-256(); identical shape to audit.Root over the journal). Supports Prove.

func (*EventLog) TreeHead

func (l *EventLog) TreeHead(timestamp int64) TreeHead

TreeHead returns a commitment to the events added so far at the given timestamp — the EventLog analogue of NewTreeHead over the journal. Sign it with SignTreeHead and verify with SignedTreeHead.Verify; the signature binds Root ↔ Size ↔ Timestamp, so an event trail gets the same anchored root↔size↔time guarantee a journal STH gives. Inclusion proofs (Prove / VerifyEventInclusion) check against the resulting Root; Size counts events.

type EventStore

type EventStore interface {
	// Append durably records leaf at position seq (0-based, contiguous) for runID. It MUST be
	// append-only and idempotent on (runID, seq): re-appending an already-stored seq with the
	// SAME bytes is a no-op (so a retried mirror is safe), while a DIFFERENT leaf at an
	// existing seq must error (a fork / tamper attempt). A seq beyond the next position is a
	// gap and must error.
	Append(ctx context.Context, runID string, seq int, leaf []byte) error
	// Load returns every leaf for runID in seq order (0..n-1), or nil if the run is unknown.
	Load(ctx context.Context, runID string) ([][]byte, error)
}

EventStore is the bring-your-own port: append canonical event leaves to your append-only backend (a Postgres table with UNIQUE(run_id, seq) and insert-only grants, object storage with object-lock/WORM, or a log) and read them back. Bide ships MemEventStore as the in-memory default; you implement Append/Load against infrastructure you already run.

type EvidenceAction

type EvidenceAction struct {
	// Label is a short human description of the action (e.g. the tool name, or a step name).
	Label string `json:"label"`
	// Kind is the action category, one of "tool", "step", "grant", "call", "approval", or "approval-tally", used only to phrase the report.
	Kind string `json:"kind"`
	// Ref is the identifier the action was selected by (the tool-use id, step name, or grant digest),
	// surfaced in the report so a reader can trace it back.
	Ref string `json:"ref,omitempty"`
	// Bundle is the inclusion proof that this action is a committed leaf under the package's STH.
	Bundle ProofBundle `json:"bundle"`
}

EvidenceAction is one proven action inside an EvidencePackage: a ProofBundle plus a human-readable label and kind, so the verification report can name what was proven ("tool \"charge\"") without re-deriving it from the record.

func ApprovalEvidence added in v0.4.0

func ApprovalEvidence(ctx context.Context, store agent.Durable, runID, toolUseID string, sth SignedTreeHead) ([]EvidenceAction, error)

ApprovalEvidence builds the complete evidence for an m-of-n gated call under one STH, in this order: the model turn that requested the call (Kind "call"), every decision record the gate read (Kind "approval", from the journaled tally's Records, in journal order), the gate's terminal tally (Kind "approval-tally"), and the call's result (Kind "tool"). The result appends to EvidencePackage.Actions; drop the trailing "tool" entry if the package already carries the call.

It discloses everything the gate read, valid or not, so VerifyApprovals can recount from the same inputs and detect an omission. It errors if the call has no journaled tally (it was not m-of-n gated, or its gate has not reached an outcome), no recorded request, or no result, or if a proof cannot be built against sth.

type EvidenceGrants

type EvidenceGrants struct {
	// Chain is the delegation chain, root-first to leaf-last, as signed grants (may be a single grant).
	Chain []SignedGrant `json:"chain"`
	// Anchored is one ProofBundle per grant in Chain (same order), proving the grant leaf is committed
	// in the signed log. It may be shorter than Chain if some grants were not anchored in this run.
	Anchored []EvidenceAction `json:"anchored,omitempty"`
}

EvidenceGrants carries the run's authority: the delegation chain as signed grants (root-first) and, per grant, a ProofBundle proving the grant leaf is anchored in the same signed log as the actions.

Note the trust boundary: the anchoring bundles verify under the package's log key, but a grant's own issuer signature is made with the ISSUER's key, which is distinct from the log key and is not carried here. EvidencePackage.Verify therefore checks that each grant is anchored in the signed log; verifying the issuer signatures and the attenuation chain (VerifyDelegationChain) requires the issuers' public keys, which a caller supplies from their own PKI.

type EvidenceItem

type EvidenceItem struct {
	Kind     string `json:"kind"`  // "tool", "step", "grant", "call", "approval", "approval-tally", "run-certificate", or "consistency"
	Label    string `json:"label"` // the human label for the item
	Ref      string `json:"ref,omitempty"`
	Verified bool   `json:"verified"` // whether this item verified under the key
	Note     string `json:"note"`     // plain-English detail, on success or failure
}

EvidenceItem is one line of an EvidenceReport: what was checked, whether it verified, and a short human note (the reason on failure, or a one-line description on success).

type EvidenceOption

type EvidenceOption func(*evidenceOptions)

An EvidenceOption selects what an EvidencePackage includes. With no action option (WithToolCall, WithStep, or WithAllToolCalls), Evidence defaults to proving every completed tool call, so the zero-configuration package is "prove every action this run took".

func WithAllToolCalls

func WithAllToolCalls() EvidenceOption

WithAllToolCalls proves every completed tool call in the run, enumerated from its history. It is the default when no other action option is given.

func WithConsistency

func WithConsistency(first int) EvidenceOption

WithConsistency includes an append-only consistency proof that the first `first` records are an unmodified prefix of the tree the package's STH commits to (nothing before `first` was rewritten or reordered, only appended).

func WithGrants

func WithGrants() EvidenceOption

WithGrants includes the run's authority: every anchored grant leaf, proven included in the signed log, plus the signed grants themselves as the delegation chain (in journal order, root-first for a well-formed attenuation chain).

func WithLabel

func WithLabel(label string) EvidenceOption

WithLabel sets a human-readable description of what the evidence establishes.

func WithRunCertificate

func WithRunCertificate(spec RunCertSpec) EvidenceOption

WithRunCertificate includes the proof-carrying run certificate for the whole run, certified against the given approved-policy allowlist. It fails assembly if the run used a policy outside the allowlist (CertifyRun refuses), so a package with a certificate attests the run stayed within approved, anchored, convergence-certified policies.

func WithStep

func WithStep(name string) EvidenceOption

WithStep adds a proof that the durable step with this name (a Step / Parallel task) happened.

func WithToolCall

func WithToolCall(toolUseID string) EvidenceOption

WithToolCall adds a proof that the completed tool call with this tool-use id happened in the run.

type EvidencePackage

type EvidencePackage struct {
	// Format is the domain-separated format/version tag (EvidenceFormat). A verifier that does not
	// recognize it should refuse to interpret the rest rather than guess.
	Format string `json:"format"`
	// RunID is the run this evidence is about.
	RunID string `json:"run_id"`
	// Label is an optional caller-supplied description of what this evidence establishes (e.g.
	// "Q3 refund approvals"). It is metadata for a human reader and is not part of any proof.
	Label string `json:"label,omitempty"`
	// ProducedAt is a caller-supplied unix timestamp (seconds) recording when the package was
	// assembled. It is informational; the authoritative time commitment is inside the STH.
	ProducedAt int64 `json:"produced_at"`
	// PublicKeyHex is the hex-encoded ed25519 public key that produced the STH, carried so a reader
	// knows which key to obtain out of band. Verify does not trust it: it takes the key separately.
	PublicKeyHex string `json:"public_key_hex"`

	// STH is the run's signed tree head: the {Size, Root, Timestamp} commitment every action proof
	// below is checked against. One STH is built for the whole package so all proofs share one root.
	STH SignedTreeHead `json:"sth"`

	// Actions are the material tool calls / steps proven included in the signed log. Each carries a
	// human label and the ProofBundle proving that one action is a leaf under the STH's root.
	Actions []EvidenceAction `json:"actions"`

	// Grants optionally carries the authority behind the run: the delegation chain (root-first) as
	// signed grants, plus a ProofBundle per grant proving the grant leaf is anchored in the signed
	// log. It is nil when the caller did not request grants (WithGrants).
	Grants *EvidenceGrants `json:"grants,omitempty"`

	// RunCertificate optionally carries the proof-carrying run certificate (only-approved-policies +
	// policies-convergence-certified) for the whole run. It is nil unless WithRunCertificate was set.
	RunCertificate *RunCertificate `json:"run_certificate,omitempty"`

	// Consistency optionally carries an append-only consistency proof showing the first N records are
	// an unmodified prefix of the tree the STH commits to. It is nil unless WithConsistency was set.
	Consistency *Consistency `json:"consistency,omitempty"`
}

An EvidencePackage is the portable, offline-verifiable evidence artifact for one run: a single self-contained bundle that PACKAGES a run's already-produced cryptographic proofs (a signed tree head, per-action inclusion proofs, and optionally the run certificate, the authority grant chain, and an append-only consistency proof) into one JSON document you can store, email, or hand to an auditor. It introduces no new cryptography: every field is an existing audit primitive, and Verify re-checks each one against the out-of-band public key.

It is deliberately PURE DATA: no funcs, no ed25519 keys, no unexported state, so it round-trips losslessly through JSON. The PublicKeyHex it carries is a convenience for a reader to know which key produced it; it is NOT the trust root. As with a bare ProofBundle, the verifying key must be obtained out of band (the anchor / transparency log), and Verify takes that key as an argument rather than trusting the one embedded here.

func Evidence

func Evidence(ctx context.Context, store agent.Durable, runID string, priv ed25519.PrivateKey, timestamp int64, opts ...EvidenceOption) (EvidencePackage, error)

Evidence assembles the portable, offline-verifiable EvidencePackage for runID. It builds ONE signed tree head over the run's journal (NewTreeHead + SignTreeHead with priv at timestamp), then, per the options, gathers inclusion proofs for the material tool calls / steps and, optionally, the run certificate, the anchored authority grants, and an append-only consistency proof, all bound to that one STH. With no action option it proves every completed tool call in the run.

It mints no new cryptography: every proof is an existing audit primitive, packaged into one document a verifier re-checks offline with EvidencePackage.Verify against the out-of-band public key. The public key is derived from priv and recorded as metadata only (Verify takes the trusted key as an argument; it does not trust the embedded one).

func (EvidencePackage) Verify

Verify re-checks every proof in the package against pub, an ed25519 public key obtained OUT OF BAND (the anchor / transparency log), entirely offline. It does not trust the package's embedded key. It verifies: (1) the STH's signature, so the {Size, Root, Timestamp} commitment is authentic; (2) each action ProofBundle, confirming the action is a leaf under that signed root and is bound to the same tree; (3) each anchored grant bundle, if present, the same way; (4) the run certificate, if present, via VerifyRun against the certificate's own approved allowlist; and (5) the consistency proof, if present, that the prefix is append-only-contained in the signed root.

It returns a structured EvidenceReport with an overall bool and a per-item verdict. A false OK with per-item notes is a well-formed-but-invalid package; an error means a bundle could not be canonicalized (a malformed artifact). Grant ISSUER signatures and the attenuation chain are NOT checked here (they need the issuers' keys, which are not in the package); a caller verifies those with VerifyDelegationChain and its own PKI.

type EvidenceReport

type EvidenceReport struct {
	// OK is the overall verdict: true only when the STH is authentic and every item verified.
	OK bool `json:"ok"`
	// RunID echoes the package's run id for a self-describing report.
	RunID string `json:"run_id"`
	// Label echoes the package's optional label.
	Label string `json:"label,omitempty"`
	// STHVerified is true when the signed tree head's signature is authentic under the verifying key.
	// Every action proof is checked against this same root, so a false STH fails the whole package.
	STHVerified bool `json:"sth_verified"`
	// Items is one verdict per proven item (each action, each anchored grant, the run certificate, and
	// the consistency proof), in report order.
	Items []EvidenceItem `json:"items"`
}

EvidenceReport is the structured outcome of EvidencePackage.Verify: an overall verdict plus a per-item verdict list, so a caller (or the CLI) can print exactly which proven item held and which did not. It is the machine-readable form behind the plain-English CLI report.

type Grant

type Grant struct {
	ID        string            `json:"id"`                   // unique identifier for this grant
	Issuer    string            `json:"issuer"`               // principal that authorized the grant (resolves to the signing key)
	Subject   string            `json:"subject"`              // actor the grant authorizes to act
	Scope     map[string]string `json:"scope,omitempty"`      // domain-defined authority bounds (e.g. limits) the chain verifier attenuates over
	NotAfter  int64             `json:"not_after,omitempty"`  // unix-seconds expiry; zero never expires
	ParentRef string            `json:"parent_ref,omitempty"` // Digest of the grant this was attenuated from; empty for a root grant
}

Grant is a signed statement by a principal (Issuer) authorizing an actor (Subject) to act within Scope until NotAfter. It is the non-repudiable authority behind a governed action: the action's agent.Identity.AuthorityRef is a grant's Digest, and the grant is signed by the issuer's own key (distinct from the log's tree-head key), so an auditor attributes the authorization to the principal, not merely to the operator that runs the log.

ParentRef links a grant to the one it was attenuated from (empty for a root grant), forming a hash-linked delegation chain: a holder of a grant may mint a strictly narrower sub-grant for a sub-agent without returning to the root issuer (capability attenuation), and the chain back to the root is provable. Scope semantics are domain-defined; the chain verifier takes an Attenuator that decides what "narrower" means (a helper for numeric limits is provided).

func (Grant) Bytes

func (g Grant) Bytes() []byte

Bytes is the canonical serialization that is signed and digested. json.Marshal sorts map keys and emits struct fields in declaration order, so it is deterministic in-ecosystem.

func (Grant) Digest

func (g Grant) Digest() string

Digest is a stable, domain-separated SHA-256 over the grant, used as agent.Identity.AuthorityRef and as the anchoring key. It is recomputable by any verifier from the disclosed grant.

func (Grant) Expired

func (g Grant) Expired(now int64) bool

Expired reports whether the grant is past NotAfter at time now (unix seconds). A zero NotAfter never expires. The caller supplies the clock, so verification stays deterministic.

type HybridSigner

type HybridSigner struct {
	Ed Ed25519Signer // the Ed25519 component signer
	ML MLDSASigner   // the ML-DSA-65 component signer
}

HybridSigner signs with both schemes; the signature is only accepted if BOTH verify, so the anchor is safe as long as either scheme remains unbroken.

func (HybridSigner) Alg

func (HybridSigner) Alg() string

Alg reports the signature scheme (AlgHybrid).

func (HybridSigner) Sign

func (s HybridSigner) Sign(m []byte) ([]byte, error)

Sign returns the packed pair of Ed25519 and ML-DSA-65 signatures over m.

type HybridVerifier

type HybridVerifier struct {
	Ed Ed25519Verifier // the Ed25519 component verifier
	ML MLDSAVerifier   // the ML-DSA-65 component verifier
}

HybridVerifier requires both component signatures to verify.

func (HybridVerifier) Alg

func (HybridVerifier) Alg() string

Alg reports the signature scheme (AlgHybrid).

func (HybridVerifier) Verify

func (v HybridVerifier) Verify(m, sig []byte) bool

Verify reports whether both component signatures over m are valid.

type IgnoredDecision added in v0.4.0

type IgnoredDecision struct {
	Approver string `json:"approver"`
	Step     string `json:"step"`
	Reason   string `json:"reason"`
}

IgnoredDecision is one disclosed approver decision that did not count toward the gate.

type Inclusion

type Inclusion struct {
	Index int      // the record's position in the journal
	Size  int      // the journal length the root committed to
	Path  [][]byte // sibling hashes, leaf-to-root
}

Inclusion is an RFC 6962 audit path proving one record's membership in a committed run.

func Prove

func Prove(ctx context.Context, store agent.Durable, runID string, index int) (Inclusion, error)

Prove returns an inclusion proof for the record at index in runID's journal — enough to verify that record against Root WITHOUT revealing any other record (selective disclosure).

type KeyFunc

type KeyFunc func(agent.Record) (string, bool)

KeyFunc extracts an absence key from a record, returning false to exclude the record from the key set. Absence is proven over the sorted set of distinct keys the KeyFunc yields.

type MLDSASigner

type MLDSASigner struct {
	Priv *mldsa.PrivateKey // the ML-DSA-65 private key that signs
}

MLDSASigner signs with an ML-DSA-65 private key. Signing is deterministic, so replaying the same tree head reproduces the same signature.

func (MLDSASigner) Alg

func (MLDSASigner) Alg() string

Alg reports the signature scheme (AlgMLDSA65).

func (MLDSASigner) Sign

func (s MLDSASigner) Sign(m []byte) ([]byte, error)

Sign returns the deterministic ML-DSA-65 signature over m.

type MLDSAVerifier

type MLDSAVerifier struct {
	Pub *mldsa.PublicKey // the ML-DSA-65 public key that verifies
}

MLDSAVerifier verifies with an ML-DSA-65 public key.

func (MLDSAVerifier) Alg

func (MLDSAVerifier) Alg() string

Alg reports the signature scheme (AlgMLDSA65).

func (MLDSAVerifier) Verify

func (v MLDSAVerifier) Verify(m, sig []byte) bool

Verify reports whether sig is a valid ML-DSA-65 signature over m.

type MemAnchorLog

type MemAnchorLog struct {
	// contains filtered or unexported fields
}

MemAnchorLog is a reference external transparency log: an append-only, independently Merkle-committed record of published STHs. In a real deployment the anchor lives in a DIFFERENT trust domain than the journal (a separate service / account / public log); this in-memory version is for tests and local dev. Because it keeps its OWN RFC 6962 tree over the entries, a third party can verify that the anchor log itself only grew (ProveConsistency) and that a specific STH was anchored (Prove) — the properties that make anchoring meaningful.

func NewMemAnchorLog

func NewMemAnchorLog() *MemAnchorLog

NewMemAnchorLog returns an empty transparency log.

func (*MemAnchorLog) Entries

func (l *MemAnchorLog) Entries() []AnchorEntry

Entries returns a copy of the anchored entries in order.

func (*MemAnchorLog) Len

func (l *MemAnchorLog) Len() int

Len is the number of anchored entries.

func (*MemAnchorLog) Prove

func (l *MemAnchorLog) Prove(seq int) (Inclusion, error)

Prove returns an inclusion proof that the entry at seq is in the anchor log committed by Root — i.e. that a given STH was anchored, disclosing no other entry.

func (*MemAnchorLog) ProveConsistency

func (l *MemAnchorLog) ProveConsistency(first int) (Consistency, error)

ProveConsistency proves the first `first` anchored entries are an append-only prefix of the current log — the transparency-log guarantee that no earlier anchor was rewritten.

func (*MemAnchorLog) Publish

func (l *MemAnchorLog) Publish(_ context.Context, runID string, sth SignedTreeHead) error

Publish appends an STH for runID at the next sequence. Append-only: entries are never modified or removed.

func (*MemAnchorLog) Root

func (l *MemAnchorLog) Root() ([]byte, error)

Root is the RFC 6962 Merkle root over all anchored entries — the anchor log's own commitment. Publish/sign IT in turn (or expose it) so a monitor can check the log's growth.

type MemEventStore

type MemEventStore struct {
	// contains filtered or unexported fields
}

MemEventStore is the in-memory reference EventStore for tests and local dev. A real backend (Postgres, object storage) enforces the same append-only/idempotent contract with a unique constraint and insert-only permissions.

func NewMemEventStore

func NewMemEventStore() *MemEventStore

NewMemEventStore returns an empty in-memory EventStore.

func (*MemEventStore) Append

func (s *MemEventStore) Append(_ context.Context, runID string, seq int, leaf []byte) error

func (*MemEventStore) Load

func (s *MemEventStore) Load(_ context.Context, runID string) ([][]byte, error)

type Neighbor

type Neighbor struct {
	Key   string    `json:"key"`   // the committed key adjacent to the absent key
	Proof Inclusion `json:"proof"` // inclusion proof placing Key at its index in the committed key set
}

Neighbor is one committed key adjacent to an absent key, with its inclusion proof.

type PolicyContent

type PolicyContent struct {
	Digest string `json:"digest"` // the policy owner's stable identifier (treated as opaque here)
	Policy string `json:"policy"` // the serialized governed policy bytes
}

PolicyContent is the payload of a policy leaf: the serialized governed policy and its digest. Committing it as a journal leaf puts the policy itself under the same signed tree head and inclusion proofs as the actions taken under it, so an auditor can prove not merely that an action happened but that the exact policy admitting it was anchored in the same committed tree.

type PolicyConvergence

type PolicyConvergence struct {
	Digest      string      `json:"digest"`      // the used policy digest this evidence is for
	PolicyLeaf  ProofBundle `json:"policy_leaf"` // proof the policy leaf is anchored in the run STH
	Certificate ProofBundle `json:"certificate"` // proof the convergence-certificate leaf is anchored in the same tree
}

PolicyConvergence bundles the anchored evidence for one used policy: its anchored policy leaf and its anchored convergence-certificate leaf, both proven against the run STH. The verifier confirms both bundles are authentic and in the same tree, the convergence certificate certifies the policy leaf's digest, and the policy leaf's bytes hash to that digest (done in the CLI, independent of gsm), then optionally runs the oracle on the disclosed policy bytes.

type ProofBundle

type ProofBundle struct {
	RunID     string         `json:"run_id"`    // the run whose journal the record and STH belong to
	Record    agent.Record   `json:"record"`    // the single disclosed action
	Inclusion Inclusion      `json:"inclusion"` // its RFC 6962 audit path
	STH       SignedTreeHead `json:"sth"`       // the signed commitment it is proven against
}

A ProofBundle is a single, portable, self-describing artifact that proves ONE action happened inside a signed, committed run, disclosing nothing else. It is what "produce a proof" returns: hand it (plus the signer's public key, obtained out-of-band) to an auditor and they verify it offline. This turns the audit package's separate pieces (a record, its inclusion path, a signed tree head) into one thing you can marshal to JSON, store, email, or publish.

The trust model, stated precisely: the public key must come from OUT OF BAND (the anchor / transparency log), NOT from the bundle. Given a trusted key, Verify checks (1) the STH's signature, so the {Size, Root, Timestamp} commitment is authentic; (2) that the inclusion proof is against that same signed size; and (3) that the disclosed record is the leaf at its index under the signed root. "Proofs you verify, not logs you trust": nothing here asks the verifier to trust the producer, its database, or its logs. For full tamper-evidence the auditor also confirms the STH itself appears in the anchor log (MemAnchorLog.Prove).

func ProveApproval added in v0.4.0

func ProveApproval(ctx context.Context, store agent.Durable, runID, step string, sth SignedTreeHead) (ProofBundle, error)

ProveApproval builds a ProofBundle proving one approver-decision record, named step, is committed in the tree sth signs. Decision records are named per decision (see agent.ApprovalTally.Records and agent.DecisionCheck.Step), so the step name identifies one exactly. The disclosed record carries the approver id and signature, so a verifier holding the approver's key can check it over agent.ApprovalDecisionBytes independently. ProveStep matches only StepValue records, so it cannot reach a decision.

func ProveConvergence

func ProveConvergence(ctx context.Context, store agent.Durable, runID, digest string, sth SignedTreeHead) (ProofBundle, error)

ProveConvergence builds a ProofBundle proving the convergence certificate for this digest was committed in the tree sth signs. Pair it with the policy leaf's ProofBundle for the same digest to show, in one committed tree, that the anchored policy was certified convergent.

func ProveGrant

func ProveGrant(ctx context.Context, store agent.Durable, runID, digest string, sth SignedTreeHead) (ProofBundle, error)

ProveGrant builds a ProofBundle proving the grant with this digest was committed in the tree sth signs. Pair it with an action's bundle whose Identity.AuthorityRef matches the digest to show the action ran under an in-log, issuer-signed authority.

func ProvePolicy

func ProvePolicy(ctx context.Context, store agent.Durable, runID, digest string, sth SignedTreeHead) (ProofBundle, error)

ProvePolicy builds a ProofBundle proving the policy with this digest was committed in the tree sth signs. Pair it with an action's ProofBundle whose result embeds the same digest to show the action ran under an in-log, anchored policy; the verifier then recomputes the digest from the disclosed bytes and runs the external oracle to certify the policy converges.

func ProveRecord

func ProveRecord(ctx context.Context, store agent.Durable, runID string, index int, sth SignedTreeHead) (ProofBundle, error)

ProveRecord builds a bundle proving the record at index is in the tree that sth commits to. The inclusion proof is built against the first sth.Size records of runID's journal (the tree the STH signed), so the bundle binds to an anchored STH rather than a freshly minted one. It fails if index is outside that tree, or if sth.Root does not match runID's journal at that size (wrong STH, or the history diverged from what was signed).

func ProveRunCertificate

func ProveRunCertificate(ctx context.Context, store agent.Durable, runID string, sth SignedTreeHead) (ProofBundle, error)

ProveRunCertificate builds a ProofBundle proving the run certificate leaf for runID was committed in the tree sth signs. Because RecordRunCertificate grows the journal, sth here is a tree head taken AFTER the certificate was anchored (later than the run STH embedded in the certificate).

func ProveStep

func ProveStep(ctx context.Context, store agent.Durable, runID, name string, sth SignedTreeHead) (ProofBundle, error)

ProveStep builds a ProofBundle proving that the durable step with this name (an agent.Step / Parallel task, recorded as a StepValue) is committed in the tree sth signs. It is the fan-in counterpart to ProveToolCall: after a Parallel run, each task is an independently provable record, so an auditor can prove "this specific compliance check ran and produced this result" without disclosing the other stages.

func ProveToolCall

func ProveToolCall(ctx context.Context, store agent.Durable, runID, toolUseID string, sth SignedTreeHead) (ProofBundle, error)

ProveToolCall is the semantic form of ProveRecord: prove that a specific tool call happened, by its ToolUseID, without the caller needing to know its journal index. It resolves the completed tool-result record for toolUseID and proves it against sth. This is the ergonomic entry point ("prove the charge on run X"), not "prove record index 7".

func (ProofBundle) Verify

func (b ProofBundle) Verify(pub ed25519.PublicKey) (bool, error)

Verify reports whether the bundle is internally consistent and authentic under pub (a key obtained out-of-band, e.g. from the anchor log operator). It returns false, not an error, for a well-formed-but-invalid proof; an error indicates the record could not be canonicalized.

func (ProofBundle) VerifyWith

func (b ProofBundle) VerifyWith(v Verifier) (bool, error)

VerifyWith is the scheme-agnostic form of ProofBundle.Verify: it authenticates the STH under any Verifier (ed25519, ML-DSA, or hybrid), then binds and checks the inclusion proof.

type RunCertSpec

type RunCertSpec struct {
	// ApprovedPolicies is the allowlist of policy digests the run was permitted to exercise. Every
	// policy the run actually used must be a member, or CertifyRun fails: a certificate cannot be
	// issued for a run that exercised a disallowed policy.
	ApprovedPolicies []string
}

RunCertSpec carries the caller's assertion inputs to CertifyRun. v1 needs only the approved allowlist; it is a struct so future properties (authority roots, quorum thresholds) can be added without changing the signature.

type RunCertificate

type RunCertificate struct {
	// RunID is the run this certificate is about.
	RunID string `json:"run_id"`

	// Properties names the asserted properties, so a reader sees the scope without decoding the
	// bindings. v1: "only-approved-policies", "policies-convergence-certified".
	Properties []string `json:"properties"`

	// ApprovedPolicies is the caller-supplied allowlist of policy digests the run was permitted to
	// exercise. The certificate binds to it so an auditor sees exactly which allowlist was asserted.
	ApprovedPolicies []string `json:"approved_policies"`

	// UsedPolicies is the sorted, distinct set of policy digests exercised by governed actions in the
	// run, recomputed from the committed leaves (PoliciesUsed). It is disclosed in full because the
	// only-approved-policies property is completeness-bearing: the verifier recomputes the absence
	// root over this set and confirms UsedPolicyAbsence.STH commits to it, so the set cannot omit a
	// policy that was in fact used.
	UsedPolicies []string `json:"used_policies"`

	// UsedPolicyAbsence is the signed commitment to the run's used-policy key set: an STH over
	// AbsenceRoot(records, PolicyUsedKey). Its Size is the number of distinct used policies and its
	// Root is the RFC 6962 root over their sorted keys, so recomputing that root from UsedPolicies and
	// comparing binds the disclosed set to what the run actually committed. See SignAbsenceRoot.
	UsedPolicyAbsence SignedTreeHead `json:"used_policy_absence"`

	// Convergence carries, per used policy digest, the anchored evidence that the policy is certified
	// convergent: the policy-leaf ProofBundle and the convergence-leaf ProofBundle, both bound to the
	// run STH below. The oracle cross-check (does the policy actually converge) is performed by the
	// verifier with a checker, as verify-convergence does; these bundles establish the cryptographic
	// root (anchored, digest-linked, same tree) that the oracle check hangs off.
	Convergence []PolicyConvergence `json:"convergence"`

	// STH is the run's signed tree head: the {Size, Root, Timestamp} commitment the policy and
	// convergence bundles are proven against. It is the same tree the whole run's journal commits.
	STH SignedTreeHead `json:"sth"`
}

A RunCertificate is a proof-carrying run: one portable, offline-checkable artifact asserting behavioral-property compliance over a WHOLE run, discharged from the audit package's existing primitives rather than from new cryptography. It composes what already exists (RFC 6962 inclusion proofs, signed tree heads, the policy-used absence commitment, anchored policy and convergence leaves) into one per-run statement plus a verifier, so a compliance reviewer checks a single bundle against one out-of-band public key instead of assembling the proofs by hand.

v1 asserts these properties, each dischargeable from a committed leaf:

  • only-approved-policies: every policy digest exercised by a governed action in the run is a member of a caller-supplied approved allowlist. This is the COMPLETENESS-bearing property: it commits WHICH policies were used, via the policy-used absence commitment (AbsenceRoot over PolicyUsedKey). The used set is exactly the sorted distinct keys the absence tree commits to, so a verifier recomputes that root from the disclosed set and confirms the signed absence STH commits to it; that closes the "some other policy was quietly used" gap that disclosing only positive leaves would leave open.
  • policies-convergence-certified: for each used policy digest, an anchored convergence certificate leaf exists in the same signed tree as the policy leaf and links to its digest. The certificate is a producer claim; a rigorous verifier cross-checks it against the external oracle (bide-audit verify-run -checker), exactly as verify-convergence does, so a certificate that overstates convergence is caught outside this package.

What the certificate proves, stated precisely: it proves properties of the GOVERNED, COMMITTED boundary of the run: which policies ran, that each is on the approved allowlist, and that each has an anchored convergence certificate that (with the oracle) is confirmed convergent. Composing the two properties yields "every governed state in the run was produced by an approved, oracle-certified-convergent policy," so the enforced invariant held throughout the governed boundary. It does NOT prove the model's judgment was correct, that ungoverned side effects were appropriate, or the runtime-refinement claim (the replay differential check covers the events this run took, not all inputs). The used-policy completeness rests entirely on the absence-root key-set commitment described above.

func CertifyRun

func CertifyRun(ctx context.Context, store agent.Durable, runID string, sth SignedTreeHead, spec RunCertSpec, priv ed25519.PrivateKey, timestamp int64) (RunCertificate, error)

CertifyRun assembles a RunCertificate for runID from its committed leaves, against the run's signed tree head sth. It recomputes the used-policy set from the journal (PoliciesUsed), confirms every used policy is in spec.ApprovedPolicies (refusing to certify a run that used a disallowed policy), signs the used-policy absence commitment with priv, and for each used policy assembles the anchored policy-leaf and convergence-leaf proof bundles against sth. It fails if a used policy has no anchored policy leaf or no anchored convergence leaf, so the certificate can only be issued for a run whose governed policies are fully anchored and certified.

priv is the same signing key that produced sth (the log's tree-head key): the used-policy absence STH is a second commitment over the SAME run, signed the same way, so a verifier checks both under one out-of-band public key. timestamp stamps the absence STH.

type RunVerification

type RunVerification struct {
	// OK is the overall verdict: true only when every asserted property held.
	OK bool
	// OnlyApprovedPolicies is true when every used policy is in the allowlist AND the disclosed
	// used-policy set is bound to the run's absence commitment (completeness).
	OnlyApprovedPolicies bool
	// ConvergenceCertified is true when every used policy has an anchored policy leaf and convergence
	// leaf, authentic and in the same signed tree, with linking digests. It does NOT include the
	// oracle cross-check, which the caller performs separately (the CLI does, with -checker).
	ConvergenceCertified bool
	// Reasons carries human-readable detail for any property that failed (empty on full success).
	Reasons []string
}

RunVerification is the structured outcome of VerifyRun: the overall verdict plus per-property results, so a caller (or the CLI) can report exactly which property held and which failed.

func VerifyRun

func VerifyRun(cert RunCertificate, pub ed25519.PublicKey) (RunVerification, error)

VerifyRun checks a RunCertificate's claims against the disclosed proofs and the out-of-band public key pub, entirely offline. It does NOT trust the certificate; it re-derives each property from the bundles it carries:

  1. only-approved-policies (completeness-bearing): the used-policy absence STH is authentic under pub, its Root equals the RFC 6962 root recomputed from the disclosed UsedPolicies key set (so the disclosed set IS what the run committed, not a set the producer chose), and every used policy is a member of ApprovedPolicies. Recomputing the absence root is what gives the negative teeth: a policy that was actually used cannot be dropped from UsedPolicies without changing the root and breaking the signature check.
  2. policies-convergence-certified: the run STH is authentic under pub; and for every used policy there is a Convergence entry whose policy-leaf and convergence-leaf bundles both verify under pub, are in the same tree as the run STH, and whose leaves link by digest (the policy leaf's PolicyContent.Digest and the convergence leaf's ConvergenceContent.Digest both equal the used digest). The oracle cross-check of the certificate's convergence claim is left to the caller (see the CLI's -checker); VerifyRun establishes the anchored, digest-linked cryptographic root it hangs off.

A false OK with populated Reasons means a well-formed-but-invalid certificate; an error means a bundle could not be canonicalized (a malformed artifact).

type SignedGrant

type SignedGrant struct {
	Grant Grant  `json:"grant"` // the grant whose canonical bytes are signed
	Alg   string `json:"alg"`   // signature scheme the issuer used (see signing.go)
	Sig   []byte `json:"sig"`   // issuer's signature over Grant.Bytes()
}

SignedGrant is a Grant plus the issuer's signature over its canonical bytes.

func SignGrant

func SignGrant(g Grant, s Signer) (SignedGrant, error)

SignGrant signs a grant with the issuer's key (Ed25519, ML-DSA, or hybrid, per the Signer).

func (SignedGrant) Verify

func (sg SignedGrant) Verify(v Verifier) (bool, error)

Verify checks the issuer's signature over the grant. The verifier is the issuer's public key, resolved by the caller's PKI; this is non-repudiation of the authorization, separate from the log's tree-head signature.

type SignedTreeHead

type SignedTreeHead struct {
	TreeHead         // the {Size, Root, Timestamp} commitment being signed
	Signature []byte // signature over TreeHead.canonical() under the scheme named by Alg
	// Alg names the signature scheme (see signing.go). Empty means "ed25519", so bundles
	// produced by the ed25519-only SignTreeHead / Verify path are unchanged on the wire and
	// keep verifying. Agility-aware producers set it explicitly (SignTreeHeadWith).
	Alg string `json:"alg,omitempty"`
}

SignedTreeHead is a TreeHead with an Ed25519 signature over its canonical encoding.

func SignAbsenceRoot

func SignAbsenceRoot(records []agent.Record, keyFn KeyFunc, priv ed25519.PrivateKey, timestamp int64) SignedTreeHead

SignAbsenceRoot commits and signs the run's absence key set under keyFn in one call: it builds a TreeHead over AbsenceRoot(records, keyFn) and signs it. Absence proofs verify against this separate commitment (not the journal STH), so this is the one-step producer for it. Anchor the result like any STH; a verifier holding the journal recomputes AbsenceRoot to confirm the STH commits to this run's key set before trusting an absence proof against it.

func SignTreeHead

func SignTreeHead(th TreeHead, priv ed25519.PrivateKey) SignedTreeHead

SignTreeHead signs a TreeHead. Anchor the result out-of-band (this is what makes the journal tamper-evident against later rewrites).

func SignTreeHeadWith

func SignTreeHeadWith(th TreeHead, s Signer) (SignedTreeHead, error)

SignTreeHeadWith signs a TreeHead under any scheme (ed25519, ML-DSA, or the hybrid of both), tagging the result with the signer's algorithm. Anchor the result out-of-band as usual.

func (SignedTreeHead) Verify

func (sth SignedTreeHead) Verify(pub ed25519.PublicKey) bool

Verify reports whether the STH's signature is valid under pub. Any change to Size, Root, or Timestamp invalidates it. This is the ed25519 fast path; it accepts an STH with Alg empty or "ed25519" and rejects any other scheme (use VerifyWith for those).

func (SignedTreeHead) VerifyWith

func (sth SignedTreeHead) VerifyWith(v Verifier) bool

VerifyWith reports whether the STH's signature is valid under v, requiring the STH's algorithm to match the verifier's (an empty Alg is treated as ed25519). Use this for ML-DSA or hybrid STHs; Verify remains the ed25519-only convenience.

type Signer

type Signer interface {
	Alg() string
	Sign(message []byte) ([]byte, error)
}

Signer produces a signature over a message under a named algorithm.

type TreeHead

type TreeHead struct {
	Size      int    // number of records committed
	Root      []byte // RFC 6962 Merkle root over those records (see Root)
	Timestamp int64  // caller-supplied (e.g. time.Now().UnixNano())
}

TreeHead is a commitment to a run's journal at a point in time.

func NewTreeHead

func NewTreeHead(ctx context.Context, store agent.Durable, runID string, timestamp int64) (TreeHead, error)

NewTreeHead builds a TreeHead committing to runID's journal at the given timestamp.

type Verifier

type Verifier interface {
	Alg() string
	Verify(message, signature []byte) bool
}

Verifier checks a signature over a message under a named algorithm.

Directories

Path Synopsis
Package verify is a dependency-light, standalone verifier for Bide audit proofs.
Package verify is a dependency-light, standalone verifier for Bide audit proofs.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL