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
- func AbsenceRoot(records []agent.Record, keyFn KeyFunc) []byte
- func AttenuatingSubAgent(name, description string, sub *agent.Agent, store agent.Durable, ...) agent.Tool
- func GrantFrom(ctx context.Context) (SignedGrant, Signer, bool)
- func Head(ctx context.Context, store agent.Durable, runID string) ([]byte, error)
- func PersistEvent(ctx context.Context, evStore EventStore, runID string, seq int, ...) error
- func PersistJournal(ctx context.Context, evStore EventStore, jStore agent.Durable, runID string) error
- func PoliciesUsed(records []agent.Record) []string
- func PolicyUsedKey(r agent.Record) (string, bool)
- func PolicyUsedKeyFor(digest string) string
- func Record(log *EventLog, stream *agent.AgentStream, onEvent func(agent.AgentEvent)) (agent.Message, error)
- func RecordConvergence(ctx context.Context, store agent.Durable, runID string, cert []byte, ...) (agent.Record, error)
- func RecordGrant(ctx context.Context, store agent.Durable, runID string, sg SignedGrant) (agent.Record, error)
- func RecordPolicy(ctx context.Context, store agent.Durable, runID string, policy []byte, ...) (agent.Record, error)
- func RecordRunCertificate(ctx context.Context, store agent.Durable, runID string, cert RunCertificate) (agent.Record, error)
- func Root(ctx context.Context, store agent.Durable, runID string) ([]byte, error)
- func Sign(head []byte, priv ed25519.PrivateKey) []byte
- func ToolUseKey(r agent.Record) (string, bool)
- func ToolUseKeyFor(toolUseID string) string
- func VerifyAbsence(root []byte, proof Absence) (bool, error)
- func VerifyAnchorInclusion(root []byte, entry AnchorEntry, proof Inclusion) (bool, error)
- func VerifyConsistency(firstRoot, laterRoot []byte, proof Consistency) bool
- func VerifyDelegationChain(chain []SignedGrant, issuerVerifier func(issuer string) (Verifier, bool), ...) (bool, error)
- func VerifyEventInclusion(root []byte, event agent.AgentEvent, proof Inclusion) (bool, error)
- func VerifyInclusion(root []byte, record agent.Record, proof Inclusion) (bool, error)
- func VerifySignature(head, sig []byte, pub ed25519.PublicKey) bool
- func WithGrant(ctx context.Context, sg SignedGrant, signer Signer) context.Context
- type Absence
- type AbsenceBundle
- type Anchor
- type AnchorEntry
- type AttenuateFunc
- type Attenuator
- type AuditedStore
- func (a *AuditedStore) Do(ctx context.Context, runID, name string, ...) (agent.Record, error)
- func (a *AuditedStore) History(ctx context.Context, runID string) ([]agent.Record, error)
- func (a *AuditedStore) OnError(fn func(runID string, err error)) *AuditedStore
- func (a *AuditedStore) WithClock(now func() int64) *AuditedStore
- type Consistency
- type ConvergenceContent
- type EarnedAuthority
- type Ed25519Signer
- type Ed25519Verifier
- type EventLog
- func (l *EventLog) Add(e agent.AgentEvent) error
- func (l *EventLog) Head() []byte
- func (l *EventLog) Len() int
- func (l *EventLog) Prove(index int) (Inclusion, error)
- func (l *EventLog) ProveConsistency(first int) (Consistency, error)
- func (l *EventLog) Root() []byte
- func (l *EventLog) TreeHead(timestamp int64) TreeHead
- type EventStore
- type EvidenceAction
- type EvidenceGrants
- type EvidenceItem
- type EvidenceOption
- func WithAllToolCalls() EvidenceOption
- func WithConsistency(first int) EvidenceOption
- func WithGrants() EvidenceOption
- func WithLabel(label string) EvidenceOption
- func WithRunCertificate(spec RunCertSpec) EvidenceOption
- func WithStep(name string) EvidenceOption
- func WithToolCall(toolUseID string) EvidenceOption
- type EvidencePackage
- type EvidenceReport
- type Grant
- type HybridSigner
- type HybridVerifier
- type Inclusion
- type KeyFunc
- type MLDSASigner
- type MLDSAVerifier
- type MemAnchorLog
- func (l *MemAnchorLog) Entries() []AnchorEntry
- func (l *MemAnchorLog) Len() int
- func (l *MemAnchorLog) Prove(seq int) (Inclusion, error)
- func (l *MemAnchorLog) ProveConsistency(first int) (Consistency, error)
- func (l *MemAnchorLog) Publish(_ context.Context, runID string, sth SignedTreeHead) error
- func (l *MemAnchorLog) Root() ([]byte, error)
- type MemEventStore
- type Neighbor
- type PolicyContent
- type PolicyConvergence
- type ProofBundle
- func ProveConvergence(ctx context.Context, store agent.Durable, runID, digest string, ...) (ProofBundle, error)
- func ProveGrant(ctx context.Context, store agent.Durable, runID, digest string, ...) (ProofBundle, error)
- func ProvePolicy(ctx context.Context, store agent.Durable, runID, digest string, ...) (ProofBundle, error)
- func ProveRecord(ctx context.Context, store agent.Durable, runID string, index int, ...) (ProofBundle, error)
- func ProveRunCertificate(ctx context.Context, store agent.Durable, runID string, sth SignedTreeHead) (ProofBundle, error)
- func ProveStep(ctx context.Context, store agent.Durable, runID, name string, ...) (ProofBundle, error)
- func ProveToolCall(ctx context.Context, store agent.Durable, runID, toolUseID string, ...) (ProofBundle, error)
- type RunCertSpec
- type RunCertificate
- type RunVerification
- type SignedGrant
- type SignedTreeHead
- type Signer
- type TreeHead
- type Verifier
Constants ¶
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.
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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:
- each grant's signature under its issuer's verifier (resolved via issuerVerifier),
- the root has no ParentRef; every other grant's ParentRef equals the previous grant's Digest,
- 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 ¶
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 ¶
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 ¶
VerifySignature reports whether sig is a valid signature of head under pub.
func WithGrant ¶
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.
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 AttenuateFunc ¶
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 ¶
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) 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).
type Ed25519Verifier ¶
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 ¶
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 ¶
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 (*EventLog) Add ¶
func (l *EventLog) Add(e agent.AgentEvent) error
Add appends one event as a canonical, kind-tagged leaf.
func (*EventLog) Head ¶
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) Prove ¶
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 ¶
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 ¶
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", or "grant", 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.
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", "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 ¶
func (e EvidencePackage) Verify(pub ed25519.PublicKey) (EvidenceReport, error)
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 ¶
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.
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).
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 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.
type KeyFunc ¶
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).
type MLDSAVerifier ¶
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) 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.
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.
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 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:
- 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.
- 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.
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.