On this page
API reference for the trace package — reads the strictcli process trace store to resolve which tool invoked a deletion.
#internal/trace
#internal/trace
Package trace reads the strictcli process trace store, so a deletion can record who ran it.
The store is a shared, append-only JSONL record of process ancestry: at the seam where one command-line tool spawns another, the spawning invocation writes one line describing itself and hands that line's identifier to the child in STRICTCLI_TRACE_PARENT. saferm is a consumer, never a writer. It parses the variable and reads the store itself, per the normative specification (strictcli's docs/process-trace-store.md), because the framework deliberately exposes no accessor for the ancestry stack -- nothing in the framework may branch on data no framework code reads.
Everything here is observational. A capture cannot fail a deletion: an absent variable, a polluted one, a pruned store, a torn line and a parent that resolves to nothing are all legal states, each recorded as an anomaly and carried on from. Consumers noticing dangling parents is the store's primary failure-detection channel, which is why the anomalies are written into the record rather than discarded.
A capture resolves the FULL ancestry chain at capture time and keeps the flattened entries, not only their identifiers, so the record stays self-contained: age-based pruning of the store can never orphan it. The identifiers are kept alongside for correlation with whatever store data still exists.
#ParentEnv
const ParentEnv = "STRICTCLI_TRACE_PARENT"ParentEnv is the one variable ancestry travels through. It carries exactly one thing: the identifier of the entry describing the process that spawned this one.
#AnomalyMalformedParentValue
const AnomalyMalformedParentValue = "malformed-trace-parent"AnomalyMalformedParentValue: the environment variable was set to something that is not a canonical identifier. Recorded verbatim.
#AnomalyDanglingParent
const AnomalyDanglingParent = "dangling-parent"AnomalyDanglingParent: an identifier resolved to no entry -- the store was pruned or missing, the writer was another tool, or someone set the variable by hand. Legal by design, and the store's primary failure-detection channel.
#AnomalyMalformedEntry
const AnomalyMalformedEntry = "malformed-entry"AnomalyMalformedEntry: a line in a partition could not be read as an entry -- torn by a non-atomic write, missing one of the thirteen keys, or carrying an unparseable identifier.
#AnomalyStoreUnreadable
const AnomalyStoreUnreadable = "store-unreadable"AnomalyStoreUnreadable: a partition or the store directory could not be read at all. A store that does not exist is NOT this: that is an ordinary dangling parent.
#AnomalyChainCycle
const AnomalyChainCycle = "chain-cycle"AnomalyChainCycle: walking parent_id revisited an identifier. No store a conforming writer produces can contain one, since an entry's parent is always older than itself.
#AnomalyOversizedField
const AnomalyOversizedField = "oversized-entry-field"AnomalyOversizedField: a string an entry contributes to the embedded chain was longer than a chain entry may carry, and was truncated. The line was conforming -- nothing in the entry rules bounds a value's length -- so this is the consumer stating what it kept, not a complaint about the writer.
#AnomalyAnomaliesDropped
const AnomalyAnomaliesDropped = "anomalies-dropped"AnomalyAnomaliesDropped: the capture saw more anomalies than one record may carry. Synthetic, always last, and present only when something was dropped: it names how many, so a truncated anomaly list can never read as a complete one.
#Entry
type Entry structEntry is one line of the store: an invocation that spawned a child. Every key is always present in a conforming line, so an absent one makes the line malformed rather than defaulted.
#Anomaly
type Anomaly structAnomaly is something the capture saw and could not treat as well-formed.
#Capture
type Capture structCapture is what one deletion records about its ancestry.
Chain holds the flattened ancestry, nearest caller first, so the record stays readable after the store is pruned. ChainIDs is the same walk as bare identifiers, kept for correlation with whatever store data still exists.
#Collect
func Collect() *CaptureCollect resolves the ancestry of the running process from the store.
It returns nil when STRICTCLI_TRACE_PARENT is unset -- nothing claimed this invocation, which is not an anomaly and is the state every deletion is in until callers upgrade to a framework that writes the store.
#StoreDir
func StoreDir(home string) stringStoreDir is the store's literal path under home.
It is deliberately NOT derived from XDG_DATA_HOME or any other variable, despite matching the XDG default: a writer that honoured XDG_DATA_HOME and one that did not would write to two stores on the same machine, and a chain crossing them would dangle at both ends while both writers behaved correctly.
#Capture.Origin
func (c *Capture) Origin() (name, version *string)Origin is the immediate caller's declared name and version -- the two values a deletion records as its origin. Both are nil when nothing resolved, which is what "no tool claimed this" means.