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

extension

package
v1.10.0 Latest Latest
Warning

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

Go to latest
Published: Nov 3, 2026 License: MIT Imports: 35 Imported by: 0

Documentation

Overview

Package extension provides a Forge-compatible extension for Chronicle.

Overview

Extension wraps the entire Chronicle stack — chronicle.Chronicle, compliance.Engine, retention.Enforcer, and handler.API — as a single forge.Extension that can be registered into any Forge application with one call.

Lifecycle

  1. New(opts...) — create the extension (no I/O yet)
  2. Extension.Register(fapp) — called by Forge during startup; builds the Chronicle instance, wires all sub-components, registers HTTP routes into the Forge router (unless disabled), and provides chronicle.Emitter in the DI container
  3. Extension.Start(ctx) — run store migrations (unless disabled) and launch the background schedulers: retention, and checkpointing when the checkpoints block is enabled
  4. Extension.Stop(ctx) — cancel the schedulers' shared context
  5. Extension.Health(ctx) — delegates to store.Ping for readiness probes

Store Requirement

The extension requires a store.Store (the composite interface, not chronicle.Storer). Provide it via WithStore:

ext := extension.New(extension.WithStore(myStore))

Internally, [Register] calls store.NewAdapter to bridge store.Store into chronicle.Storer before constructing chronicle.New.

DI Pattern

After [Register], other Forge extensions receive the chronicle.Emitter interface through Vessel (the Forge DI container):

vessel.Invoke(app.Container(), func(emitter chronicle.Emitter) {
    // emit audit events without importing Chronicle internals
    emitter.Info(ctx, "login", "session", id).Category("auth").Record()
})

Or access the emitter directly:

emitter := ext.Emitter()

Accessors

Options

Package extension provides a Forge-compatible extension for Chronicle. It implements the forge.Extension interface so Chronicle can be mounted into any Forge app with automatic route registration, DI injection, metrics, and tracing.

Configuration can be provided programmatically via Option functions or via YAML configuration files under "extensions.chronicle" or "chronicle" keys.

Index

Constants

View Source
const (
	ExtensionName        = "chronicle"
	ExtensionDescription = "Immutable audit trail with hash chains, GDPR erasure, and compliance reporting"
	ExtensionVersion     = "0.1.0"
)

Extension metadata.

Variables

View Source
var (
	// ErrAuthNotConfigured is returned when the admin API would be mounted with
	// no authentication and the operator has not said that is intentional.
	//
	// The API can permanently destroy audit data: POST /v1/retention/enforce
	// purges events, DELETE /v1/retention/:id disables retention, and
	// POST /v1/erasures cannot be undone. Chronicle's scope check keeps tenants
	// out of each other's data but does not gate these operations, so an
	// unauthenticated mount means any caller that reaches the routes can destroy
	// an app's history.
	ErrAuthNotConfigured = errors.New(
		"chronicle: admin API would be mounted without authentication. " +
			"Set auth.provider to a registered forge auth provider (with " +
			"auth.read_scopes / auth.write_scopes / auth.admin_scopes), or set " +
			"auth.allow_unauthenticated: true to accept that any caller reaching " +
			"these routes can purge audit history, or disable_routes: true to not " +
			"mount the API at all",
	)

	// ErrKeyStoreRequired is returned when crypto-erasure is enabled without a
	// key store to hold the per-subject keys.
	//
	// The key store decides whether sealed events can ever be read again, so it
	// cannot be defaulted: an in-memory one would silently make every event
	// unreadable after a restart, which looks identical to having erased every
	// subject.
	ErrKeyStoreRequired = errors.New(
		"chronicle: enable_crypto_erasure requires a key store; " +
			"pass extension.WithKeyStore(...) with a durable implementation, or " +
			"disable enable_crypto_erasure",
	)

	// ErrAuthProviderUnavailable is returned when an auth provider is named but
	// the forge auth registry is not in the DI container, so nothing could
	// enforce it. Failing here rather than mounting unprotected routes keeps a
	// misconfiguration from silently becoming an open API.
	ErrAuthProviderUnavailable = errors.New(
		"chronicle: auth.provider is set but no forge auth registry is " +
			"registered in the container; add the forge auth extension so the " +
			"provider can be resolved",
	)

	// ErrKeyProviderRequired is returned when a keyed digest is configured with
	// no key source.
	ErrKeyProviderRequired = errors.New(
		"chronicle: tamper_evidence.digest is hmac but no key source was configured; " +
			"set tamper_evidence.keys.provider and path, or pass extension.WithKeyProvider",
	)

	// ErrStoreCannotReceiveHasher is returned when tamper_evidence.digest is
	// hmac and the store passed to WithStore is of a type chronicle does not
	// recognise and cannot be given the chain after construction.
	//
	// WithStore bypasses buildStoreFromGroveDB, which is the only place a
	// pg/sqlite store otherwise gets WithHasher. A store that recomputes the
	// digest inside its own Append and never receives the configured chain
	// keeps re-linking every event under a default plain chain while Chronicle
	// writes HMAC digests -- silently, since the writes still succeed.
	//
	// Chronicle's own mongo, redis and memory backends do not recompute, so
	// they are accepted as they are; they were never the problem and have no
	// WithHasher option to point anyone at. Any other type is refused, because
	// whether it recomputes cannot be determined from outside and guessing
	// wrong produces the silent unkeyed chain above.
	ErrStoreCannotReceiveHasher = errors.New(
		"chronicle: tamper_evidence.digest is hmac but the store passed to WithStore " +
			"is not a store chronicle can hand the keyed chain to; give it a " +
			"SetHasher(*hash.Chain) method (chronicle's pg and sqlite stores have one) " +
			"so it re-links under the configured chain, or drop WithStore and let " +
			"chronicle build the store itself with the chain already wired in",
	)

	// ErrCheckpointSignerRequired is returned when checkpoints.enabled is true
	// and no ed25519 signing key is configured or supplied.
	//
	// A checkpoint without a signature asserts nothing: anyone who can write to
	// the store could write a fabricated one claiming any hash they like. So a
	// deployment that turns checkpointing on without naming a key source
	// refuses to start rather than silently taking none, or worse, an unsigned
	// artifact that looks authoritative.
	ErrCheckpointSignerRequired = errors.New(
		"chronicle: checkpoints.enabled is true but no signing key is configured; " +
			"set checkpoints.signer.provider and checkpoints.signer.path, or pass " +
			"extension.WithKeyProvider",
	)

	// ErrCheckpointsUnsupportedByStore is returned when checkpoints.enabled is
	// true and the resolved store cannot persist checkpoints durably enough to
	// be a root of trust.
	//
	// store/redis is the concrete case today: it is positioned as a
	// read-through cache layer, and a checkpoint that can be evicted proves
	// nothing. Starting anyway and simply taking no checkpoints would be
	// exactly the silent gap signed checkpoints exist to close, so the
	// extension refuses to start instead.
	ErrCheckpointsUnsupportedByStore = errors.New(
		"chronicle: checkpoints.enabled is true but the resolved store does not " +
			"support checkpoints; use a backend that can persist them durably " +
			"(pg, sqlite, mongo, or memory), or set checkpoints.enabled: false",
	)
)

Errors returned while configuring the extension.

Functions

This section is empty.

Types

type AuthConfig

type AuthConfig struct {
	// Provider is the name of a forge auth provider registered in the auth
	// extension's registry. Empty means no authentication.
	Provider string `json:"provider" mapstructure:"provider" yaml:"provider"`

	// ReadScopes are required to observe: list and fetch events, stats, reports,
	// policies and archives, and verify a chain.
	ReadScopes []string `json:"read_scopes" mapstructure:"read_scopes" yaml:"read_scopes"`

	// WriteScopes are required to create records that destroy nothing: saving a
	// retention policy, generating a compliance report.
	WriteScopes []string `json:"write_scopes" mapstructure:"write_scopes" yaml:"write_scopes"`

	// AdminScopes are required for the irreversible operations: enforcing
	// retention, deleting a policy, and requesting an erasure.
	AdminScopes []string `json:"admin_scopes" mapstructure:"admin_scopes" yaml:"admin_scopes"`

	// AllowUnauthenticated mounts the API with no authentication at all.
	//
	// Only appropriate when something in front of Chronicle already
	// authenticates every request, or in local development. Setting it is an
	// explicit acknowledgement that any caller reaching these routes can purge
	// audit history.
	AllowUnauthenticated bool `json:"allow_unauthenticated" mapstructure:"allow_unauthenticated" yaml:"allow_unauthenticated"`
}

AuthConfig configures authentication and authorisation for the admin API.

The API exposes operations that permanently destroy audit data — enforcing retention purges events, deleting a policy silently disables retention, and an erasure request cannot be undone. Chronicle's per-request scope check only stops one tenant reaching another's data; it does nothing to stop an authenticated caller from destroying their own app's history. So the extension refuses to start unless either Provider is set or AllowUnauthenticated is explicitly true.

func (AuthConfig) Configured

func (c AuthConfig) Configured() bool

Configured reports whether an auth provider was named.

func (AuthConfig) Validate

func (c AuthConfig) Validate(routesEnabled bool) error

Validate checks that the operator made a deliberate choice about API access.

type CheckpointConfig

type CheckpointConfig struct {
	// Enabled turns on periodic checkpointing.
	Enabled bool `json:"enabled" mapstructure:"enabled" yaml:"enabled"`

	// EveryEvents takes a checkpoint once a stream has gained this many events.
	EveryEvents int `json:"every_events" mapstructure:"every_events" yaml:"every_events"`

	// EveryInterval takes one at least this often, whatever the volume.
	//
	// The interval matters more than it looks: the window between checkpoints
	// is exactly the span an attacker can still rewrite, so a quiet stream that
	// never reaches EveryEvents would otherwise sit unprotected indefinitely.
	EveryInterval time.Duration `json:"every_interval" mapstructure:"every_interval" yaml:"every_interval"`

	// Signer configures where the ed25519 signing key comes from.
	Signer KeyConfig `json:"signer" mapstructure:"signer" yaml:"signer"`
}

CheckpointConfig controls periodic signed checkpoints.

A checkpoint is a signed statement about where a stream's chain stood. Keying the digest stops per-event forgery; a checkpoint is what makes a later rewrite of already-recorded events provable, because the assertion cannot be restated without the signing key.

func (CheckpointConfig) Validate

func (c CheckpointConfig) Validate(hasSigner bool) error

Validate checks that enabled checkpointing has somewhere to get a key.

hasSigner is a runtime fact the config alone cannot see: a keys.Provider supplied programmatically via WithKeyProvider satisfies checkpoints.signer the same way it satisfies tamper_evidence.keys, but nothing in Signer's fields would show that. A no-argument Validate would wrongly reject that documented path; see AuthConfig.Validate(routesEnabled bool) for the same shape applied to a different runtime fact.

Signer.Provider is checked against the exact set this extension implements ("", meaning take one from WithKeyProvider, or "file") rather than merely requiring it be non-empty. A value like "kms", "vault", a typo, or a capitalised "File" used to pass this check, leave no provider resolved, and reach checkpoint.NewEd25519Signer(nil) -- which then panicked the process on the scheduler's first tick instead of failing here at Register.

type Config

type Config struct {
	// DisableRoutes prevents HTTP route registration.
	DisableRoutes bool `json:"disable_routes" mapstructure:"disable_routes" yaml:"disable_routes"`

	// DisableMigrate prevents auto-migration on start.
	DisableMigrate bool `json:"disable_migrate" mapstructure:"disable_migrate" yaml:"disable_migrate"`

	// BasePath is the URL prefix for chronicle routes (default: "/chronicle").
	BasePath string `json:"base_path" mapstructure:"base_path" yaml:"base_path"`

	// BatchSize is the event batch size for the Chronicle emitter.
	BatchSize int `json:"batch_size" mapstructure:"batch_size" yaml:"batch_size"`

	// FlushInterval is the batch flush interval.
	FlushInterval time.Duration `json:"flush_interval" mapstructure:"flush_interval" yaml:"flush_interval"`

	// RetentionInterval sets how often retention policies are enforced.
	// Set to 0 to disable automatic retention.
	RetentionInterval time.Duration `json:"retention_interval" mapstructure:"retention_interval" yaml:"retention_interval"`

	// EnableCryptoErasure enables GDPR crypto-erasure support. Requires a key
	// store; see [WithKeyStore] and [ErrKeyStoreRequired].
	EnableCryptoErasure bool `json:"enable_crypto_erasure" mapstructure:"enable_crypto_erasure" yaml:"enable_crypto_erasure"`

	// GroveDatabase is the name of a grove.DB registered in the DI container.
	// When set, the extension resolves this named database and auto-constructs
	// the appropriate store based on the driver type (pg/sqlite/mongo).
	// When empty and WithGroveDatabase was called, the default (unnamed) DB is used.
	GroveDatabase string `json:"grove_database" mapstructure:"grove_database" yaml:"grove_database"`

	// GroveKV is the name of a grove/kv.Store registered in the DI container.
	// When set, the extension resolves this named KV store and constructs
	// a Redis-backed Chronicle store. When empty and WithGroveKV was called,
	// the default (unnamed) KV store is used.
	GroveKV string `json:"grove_kv" mapstructure:"grove_kv" yaml:"grove_kv"`

	// Auth controls who may call the admin API.
	Auth AuthConfig `json:"auth" mapstructure:"auth" yaml:"auth"`

	// TamperEvidence selects how the hash chain resists rewriting: plain
	// (default) or HMAC-keyed. See [TamperEvidenceConfig].
	TamperEvidence TamperEvidenceConfig `json:"tamper_evidence" mapstructure:"tamper_evidence" yaml:"tamper_evidence"`

	// Checkpoints controls periodic signed checkpoints over each stream's
	// chain. See [CheckpointConfig]. Leaving this unset (the default) records
	// none: nothing about a keyed digest replaces the need for a checkpoint,
	// since an attacker with write access to the store can rewrite both the
	// event and its stream's pin.
	Checkpoints CheckpointConfig `json:"checkpoints" mapstructure:"checkpoints" yaml:"checkpoints"`

	// Dashboard configures how the contract dashboard scopes a session that
	// arrives with no app or tenant claims. See [DashboardConfig].
	Dashboard DashboardConfig `json:"dashboard" mapstructure:"dashboard" yaml:"dashboard"`

	// DashboardMutations has no effect.
	//
	// Deprecated: dashboard writes are governed by the scopes the contract
	// declares on each command, chronicle.write and chronicle.admin, held by
	// the signed-in user. The field stays so a config file that still sets
	// dashboard_mutations loads. It will be removed in a later release.
	DashboardMutations bool `json:"dashboard_mutations" mapstructure:"dashboard_mutations" yaml:"dashboard_mutations"`

	// RequireConfig requires config to be present in YAML files.
	// If true and no config is found, Register returns an error.
	RequireConfig bool `json:"-" yaml:"-"`
}

Config holds the Chronicle extension configuration. Fields can be set programmatically via Option functions or loaded from YAML configuration files (under "extensions.chronicle" or "chronicle" keys).

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns a Config with sensible defaults.

type DashboardConfig

type DashboardConfig struct {
	// AppID is the app a session belongs to when its claims name none
	// (chronicle.dashboard.app_id).
	AppID string `json:"app_id" mapstructure:"app_id" yaml:"app_id"`

	// TenantID is the tenant a session is narrowed to when its app came from
	// AppID and its claims name no tenant (chronicle.dashboard.tenant_id). It
	// applies to that config session only: a session that claims its app, even
	// this one, never takes it. It cannot be set without AppID. Empty leaves
	// the config session app-wide.
	TenantID string `json:"tenant_id" mapstructure:"tenant_id" yaml:"tenant_id"`
}

DashboardConfig names the app and tenant a dashboard session takes when its claims carry none.

The dashboard scopes every request from the signed-in user's claims. Until the auth provider fills those in, a single-app deployment has nothing to scope by and every intent is refused. Naming the app here lets it run. A claim that is present but unreadable is still refused, and a request with no signed-in user never takes either value.

func (DashboardConfig) Validate

func (c DashboardConfig) Validate() error

Validate refuses values the dashboard would store and compare exactly as written: edge whitespace, control characters, or a tenant with no app. Failing at startup beats a dashboard that boots and shows nothing.

type Extension

type Extension struct {
	*forge.BaseExtension
	// contains filtered or unexported fields
}

Extension adapts Chronicle as a Forge extension. It implements the forge.Extension interface so Chronicle can be mounted into any Forge app.

func New

func New(opts ...Option) *Extension

New creates a new Chronicle Forge extension with the given options.

func (*Extension) API

func (e *Extension) API() *handler.API

API returns the API handler.

func (*Extension) Chronicle

func (e *Extension) Chronicle() *chronicle.Chronicle

Chronicle returns the Chronicle instance for direct API usage.

func (*Extension) ComplianceEngine

func (e *Extension) ComplianceEngine() *compliance.Engine

ComplianceEngine returns the compliance engine.

func (*Extension) Emitter

func (e *Extension) Emitter() chronicle.Emitter

Emitter returns the Chronicle Emitter for DI injection. Other extensions use this to emit audit events without importing Chronicle internals.

func (*Extension) Handler

func (e *Extension) Handler() http.Handler

Handler returns the HTTP handler for all API routes. Convenience for standalone use outside Forge.

func (*Extension) Health

func (e *Extension) Health(ctx context.Context) error

Health implements forge.Extension.

func (*Extension) Register

func (e *Extension) Register(fapp forge.App) error

Register implements forge.Extension. It initializes the store, creates the Chronicle instance, wires up all components, and optionally registers HTTP routes into the Forge router.

func (*Extension) RegisterContractContributor

func (e *Extension) RegisterContractContributor(
	disp *dispatcher.Dispatcher,
	reg dashcontract.Registry,
	wreg dashcontract.WardenRegistry,
) error

RegisterContractContributor implements dashboard.ContractContributorAware. It registers the chronicle contract contributor against the dashboard's own dispatcher and registries. The dashboard discovers it by asserting this interface alone, so the extension needs no other dashboard interface.

An extension that has not been through Register has no store to serve from. That is logged and skipped rather than returned, so one unstarted extension cannot fail the whole dashboard.

func (*Extension) RegisterRoutes

func (e *Extension) RegisterRoutes(router forge.Router)

RegisterRoutes registers all Chronicle API routes into a Forge router.

func (*Extension) RetentionEnforcer

func (e *Extension) RetentionEnforcer() *retention.Enforcer

RetentionEnforcer returns the retention enforcer.

func (*Extension) Start

func (e *Extension) Start(ctx context.Context) error

Start begins background processing (retention and checkpoint schedulers) and runs migrations unless disabled.

func (*Extension) Stop

func (e *Extension) Stop(_ context.Context) error

Stop gracefully shuts down background processing.

type KeyConfig

type KeyConfig struct {
	// Provider is "file", or empty to take a keys.Provider from [WithKeyProvider].
	Provider string `json:"provider" mapstructure:"provider" yaml:"provider"`

	// Path is the keyset file, for the file provider.
	Path string `json:"path" mapstructure:"path" yaml:"path"`
}

KeyConfig points at key material.

type Option

type Option func(*Extension)

Option configures the Chronicle Forge extension.

func WithArchiveSink

func WithArchiveSink(s sink.Sink) Option

WithArchiveSink sets the archive sink for retention.

func WithAuth

func WithAuth(provider string, readScopes, writeScopes, adminScopes []string) Option

WithAuth protects the admin API with a registered forge auth provider, requiring the given scopes per operation class.

Classes are graded by blast radius: read observes, write creates records that destroy nothing, and admin covers the irreversible operations (enforcing retention, deleting a policy, requesting an erasure). Pass nil for a class to require authentication without any particular scope.

extension.WithAuth("jwt",
    []string{"chronicle:read"},
    []string{"chronicle:write"},
    []string{"chronicle:admin"},
)

func WithBasePath

func WithBasePath(path string) Option

WithBasePath sets the URL prefix for chronicle routes.

func WithBatchSize

func WithBatchSize(n int) Option

WithBatchSize sets the event batch size.

func WithCheckpoints

func WithCheckpoints(cfg CheckpointConfig) Option

WithCheckpoints enables periodic signed checkpoints over each stream's chain.

A checkpoint is a signed statement about where a stream's chain stood; it is what makes a later rewrite of already-recorded events provable, because the assertion cannot be restated without the signing key. Enabling this with no signing key source -- neither cfg.Signer here nor WithKeyProvider -- fails Register with ErrCheckpointSignerRequired. Enabling it against a store that cannot persist checkpoints durably (store/redis, deliberately) fails Register with ErrCheckpointsUnsupportedByStore.

func WithConfig

func WithConfig(cfg Config) Option

WithConfig sets the Forge extension configuration.

func WithCryptoErasure

func WithCryptoErasure(enabled bool) Option

WithCryptoErasure enables GDPR crypto-erasure support.

Requires WithKeyStore; without one startup fails with ErrKeyStoreRequired. With both, each subject's metadata, reason and IP are encrypted before being hashed and stored, the store is wrapped so reads come back decrypted, and an erasure request destroys the subject's key. See crypto.Sealer for exactly which fields are covered and which deliberately are not.

func WithDashboardMutations deprecated

func WithDashboardMutations() Option

WithDashboardMutations has no effect.

Deprecated: dashboard writes are governed by the scopes the contract declares on each command, chronicle.write and chronicle.admin, held by the signed-in user. This option stays so existing callers still compile. It will be removed in a later release.

func WithDigestScheme

func WithDigestScheme(scheme string) Option

WithDigestScheme sets the digest scheme, "plain" or "hmac".

func WithDisableMigrate

func WithDisableMigrate() Option

WithDisableMigrate prevents auto-migration on start.

func WithDisableRoutes

func WithDisableRoutes() Option

WithDisableRoutes prevents HTTP route registration.

func WithFlushInterval

func WithFlushInterval(d time.Duration) Option

WithFlushInterval sets the batch flush interval.

func WithGroveDatabase

func WithGroveDatabase(name string) Option

WithGroveDatabase sets the name of the grove.DB to resolve from the DI container. The extension will auto-construct the appropriate store backend (postgres/sqlite/mongo) based on the grove driver type. Pass an empty string to use the default (unnamed) grove.DB.

func WithGroveKV

func WithGroveKV(name string) Option

WithGroveKV sets the name of the grove/kv.Store to resolve from the DI container. The extension will construct a Redis-backed Chronicle store from the KV store. Pass an empty string to use the default (unnamed) grove/kv.Store.

func WithKeyProvider

func WithKeyProvider(p keys.Provider) Option

WithKeyProvider supplies key material directly, for deployments resolving keys from a KMS rather than a file.

func WithKeyStore

func WithKeyStore(ks crypto.KeyStore) Option

WithKeyStore supplies the per-subject key store used by crypto-erasure.

Required when EnableCryptoErasure is set. The key store's durability decides whether sealed events can ever be read again: losing it is indistinguishable from erasing every subject, so an in-memory store is only suitable for tests.

func WithRequireConfig

func WithRequireConfig(require bool) Option

WithRequireConfig requires config to be present in YAML files. If true and no config is found, Register returns an error.

func WithRetentionInterval

func WithRetentionInterval(d time.Duration) Option

WithRetentionInterval sets how often retention policies are enforced. Set to 0 to disable automatic retention.

func WithStore

func WithStore(s store.Store) Option

WithStore provides the composite store for the extension.

Bypasses the extension's own store construction (WithGroveDatabase / WithGroveKV / auto-discovery), so under tamper_evidence.digest: hmac, Register needs to give this store the configured chain itself. It does so via an optional SetHasher(*hash.Chain) method, which both pgstore.Store and sqlitestore.Store implement because both recompute the digest inside Append.

Chronicle's mongo, redis and memory stores persist the digest Chronicle computed and never recompute, so they are accepted without one. Any other type fails Register with ErrStoreCannotReceiveHasher, because whether it recomputes cannot be determined from outside and guessing wrong is how a deployment ends up believing its chain is keyed while the store quietly re-links it unkeyed. Give such a store a SetHasher(*hash.Chain) method.

func WithUnauthenticatedAPI

func WithUnauthenticatedAPI() Option

WithUnauthenticatedAPI mounts the admin API with no authentication.

Only appropriate when something in front of Chronicle already authenticates every request, or in local development. Without this (or WithAuth) the extension refuses to start, because the API can purge audit history. See ErrAuthNotConfigured.

type TamperEvidenceConfig

type TamperEvidenceConfig struct {
	// Digest is "plain" (default) or "hmac".
	Digest string `json:"digest" mapstructure:"digest" yaml:"digest"`

	// Keys configures where the HMAC key comes from.
	Keys KeyConfig `json:"keys" mapstructure:"keys" yaml:"keys"`
}

TamperEvidenceConfig selects how the hash chain resists rewriting.

The default leaves the chain unkeyed, which is reproducible by anyone who can write to the store. Setting digest to hmac makes a digest depend on key material the database does not hold.

func (TamperEvidenceConfig) Validate

func (c TamperEvidenceConfig) Validate() error

Validate checks that a keyed digest has somewhere to get its key.

Refusing here rather than at the first event means a misconfigured deployment fails at startup instead of writing unkeyed digests an operator believes are keyed.

Directories

Path Synopsis
Package contract wires chronicle into the Forge dashboard's contract path.
Package contract wires chronicle into the Forge dashboard's contract path.

Jump to

Keyboard shortcuts

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