Documentation
¶
Overview ¶
Package ledger is the append-only, hash-chained audit log for brokered operations — Port scope grants and file Trades. It lives host-side at ~/.dejima/ledger.jsonl, outside every container's blast radius, so a compromised island cannot rewrite its own history.
Each entry chains to the previous one via a SHA-256 (or HMAC-SHA-256, if a key is configured) hash over the previous chain value plus the entry's own content. Any in-place edit or deletion of a past entry breaks the chain and is detectable by Verify. This is the substrate the Port spec requires before an island may be granted host-file access (docs/port-island-spec.md §4).
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Configure ¶
func Configure(hmacKey []byte)
Configure sets process-wide options for the Default ledger. It must be called before the first Default() use (e.g. at daemon startup, before any append).
A non-empty hmacKey keys the chain with HMAC-SHA-256 instead of plain SHA-256, so the hash chain can only be re-derived by a holder of the key — raising the bar from "tamper is detectable" to "tamper requires the key". The whole file must use one keying, so this is meaningful only on a fresh ledger: turning HMAC on over a file that already holds plain-SHA entries will make Verify report those older entries as broken. Calling after the Default log is already built is a no-op (the keying is fixed for the daemon's life).
func ResetDefault ¶
func ResetDefault()
ResetDefault drops the cached process-wide ledger so the next Default() re-resolves its path from $HOME (and re-reads the Configure'd HMAC key). For tests that redirect HOME between cases; not for production use (the daemon's HOME is fixed for its lifetime).
Types ¶
type Entry ¶
type Entry struct {
Seq uint64 `json:"seq"`
Time time.Time `json:"time"`
Type string `json:"type"` // port.grant | port.revoke | trade.read | trade.write | trade.deny | api.request | <lifecycle event>
Island string `json:"island"`
Agent string `json:"agent,omitempty"`
Scope string `json:"scope,omitempty"` // scope name
Path string `json:"path,omitempty"` // host path (scope) or path within scope (trade); request path (api.request)
Mode string `json:"mode,omitempty"` // ro | rw
Bytes int64 `json:"bytes,omitempty"`
SHA256 string `json:"sha256,omitempty"` // content hash of the file (trades)
Decision string `json:"decision,omitempty"` // allowed | denied
Detail string `json:"detail,omitempty"`
// Operational audit fields (api.request + lifecycle records).
Method string `json:"method,omitempty"` // HTTP method (api.request)
Status int `json:"status,omitempty"` // HTTP status code (api.request)
Actor string `json:"actor,omitempty"` // who made the request (identity; filled by the auth layer)
Role string `json:"role,omitempty"` // the actor's role, when known
Prev string `json:"prev"` // chain value of the previous entry ("" for the first)
Chain string `json:"chain"` // chain value of this entry
}
Entry is one append-only record. Trade entries (file crossings) carry Bytes and SHA256; scope entries (grant/revoke) carry Scope/Path/Mode; operational audit entries (api.request + lifecycle) carry Method/Status/Actor/Role. Prev and Chain are filled by Append and must not be set by callers.
Every field beyond the chain bookkeeping is `omitempty`, which is what lets a new build add fields without breaking the verification of entries written by an older build: chainValue re-marshals the parsed Entry, and an absent field marshals identically whether the writer knew about it or not. Do NOT drop omitempty on an existing or new field — it would silently break every chain.
type Log ¶
type Log struct {
// contains filtered or unexported fields
}
Log is an append-only ledger backed by a single JSONL file.
func Default ¶
Default returns the process-wide ledger at ~/.dejima/ledger.jsonl. A single daemon owns the file, so one shared Log keeps the in-memory chain head consistent across concurrent appends. The path is resolved (from $HOME) on first use and cached; ResetDefault drops the cache. The chain is keyed by the optional HMAC key set via Configure.
func New ¶
New returns a Log backed by path. If hmacKey is non-nil the chain is an HMAC-SHA-256 keyed by it; otherwise it is a plain SHA-256 chain.
func (*Log) Append ¶
Append seals e onto the chain and writes it as one JSONL line. It assigns Seq, Time (if zero), Prev, and Chain, and returns the sealed entry.