Spec-01-Fact-Model
What this spec defines
The atomic fact record, typed values, scopes, reification pattern,
Hybrid Logical Clock field, and entity URI normalization rules used
by the v0.9.0a1 protocol line.
Extraction status
This file contains the ADR-010 prose extraction for the fact model component. It intentionally does not include HTTP routes, schema migrations, adapter ABI, lint semantics, or CID behavior; those belong in their own component specs.
Legacy version labels from archived source material are normalized
to the current v0.9.0a1 protocol line here. Historical wording
remains available in spec/archive/evolution/ and
spec/EVOLUTION.md.
Atomic fact shape
Every piece of knowledge in Stigmem is an atomic fact.
A fact is immutable once written. Updates are expressed as new
facts. The latest fact for a given (entity, relation, scope)
triple wins unless contradiction policy applies in the fact-semantics
spec.
(entity, relation, value, source, timestamp, hlc, confidence, scope)
entitystigmem://company.example/user/alice. Informal forms such as user:alice are deprecated. Stored in canonical normalized form.relationmemory:role, roadmap:status, preference:timezone.valuesourcestigmem://company.example/agent/assistant, stigmem://company.example/user/alice. Stored in canonical normalized form.timestampvalid_untilconfidence1.0 means certain, 0.5 uncertain, 0.0 retracted.scopeFactValue
FactValue is a discriminated union that constrains what a fact can
assert. The type tag forces consumers to handle each variant
explicitly so queries, indexing, and synthesis can operate on typed
data without runtime introspection.
FactValue =
| { type: "string", v: string }
| { type: "text", v: string }
| { type: "number", v: number }
| { type: "boolean", v: boolean }
| { type: "datetime", v: ISO8601 }
| { type: "ref", v: URI }
| { type: "null" }
The string vs text distinction is load-bearing.
Nodes index string values for exact-match queries. text values
feed semantic recall when an embedding pipeline is enabled. The
ref type creates typed edges in the knowledge graph.
Inline text values SHOULD be 64 KB or less. For larger payloads,
assert a ref fact pointing to external storage and keep the text
value as a summary. Nodes MAY reject text values above their
configured limit and MUST return HTTP 413 if they do.
FactScope
Scope is the visibility fence that determines which facts leave a node during federation. It is a single string enum because the common case is simple and more complex propagation rules build on top of this primitive.
FactScope =
| "local"
| "team"
| "company"
| "public"
localteamcompanycompany in its allowed scopes.publicNodes MUST NOT federate local or team facts without explicit operator override.
Reification
The fact tuple is binary: one entity, one relation, one value. N-ary relationships are represented by minting a synthetic relationship entity and asserting participant facts about that entity.
(entity="stigmem:rel:abc123", relation="rel:subject", value={type:"ref", v:"stigmem://company.example/company/a"})
(entity="stigmem:rel:abc123", relation="rel:object", value={type:"ref", v:"stigmem://company.example/company/b"})
(entity="stigmem:rel:abc123", relation="rel:type", value={type:"string", v:"policy:board-approval"})
rel:subject, rel:object, and rel:type are reserved in the
rel: namespace. Graph traversal follows ref values out of
reified entities the same way it follows any other ref, so
reified relationships participate naturally in recall.
Hybrid Logical Clock
Wall-clock timestamps alone cannot establish causality in a distributed system because clocks drift. A pure logical clock preserves causality but loses correlation with real time. Stigmem uses a Hybrid Logical Clock (HLC) that combines both.
HLC = wall_ms || counter
Wire format: {wall_ms_utc}.{counter}, for example
1746230400000.003. The string encoding uses a dot separator so
lexicographic comparison produces correct causal ordering without
parsing. wall_ms is zero-padded to 13 digits; counter is
zero-padded to 3 digits per node.
Advance rules.
- On local write:
hlc = max(now_ms, last_hlc_ms)aswall_ms; incrementcounterifwall_msis unchanged. - On receiving a federated fact:
hlc = max(now_ms, received_hlc_ms)aswall_ms; incrementcounter.
Two facts a and b are causally ordered if a.hlc < b.hlc.
Equal HLCs on different nodes indicate concurrent writes; contradiction policy applies.
Entity URI scheme
Entity URIs use this formal shape:
stigmem://{authority}/{type}/{id}
authoritycompany.exampletypeuser, agent, projectidalice, eg-42Examples:
stigmem://company.example/user/alice
stigmem://company.example/agent/cto
stigmem://company.example/issue/eg-42
stigmem://node.acme/decision/use-sqlite
Informal URIs (user:alice, agent:cto) are deprecated in the v0.9.0a1 line.
Nodes MAY accept informal URIs for backward compatibility and SHOULD
emit deprecation warnings when they do. Adapters targeting
v0.9.0a1 or later SHOULD use formal URIs for all new fact
assertions and MUST NOT emit informal URIs in new code.
Entity naming rules
The node applies a strict normalizer at ingest to prevent case-based
and whitespace-based fragmentation. Full alias resolution, such as
treating user:alice and user:a.smith as equivalent, is a
deferred resolver concern and is not part of this fact model.
Canonical form.
- Trim leading/trailing whitespace from
entityandsource. - Collapse internal ASCII whitespace runs to a single hyphen in path segments.
- Lowercase the URI scheme, authority, type, and id components.
- Percent-decode unreserved URI characters, then re-encode path segments using RFC 3986 unreserved set only.
- Reject empty type or id segments.
Examples.
stigmem://Company.Example/User/Alice stigmem://company.example/user/alicestigmem://company.example/Issue/EG-42stigmem://company.example/issue/eg-42user:Alice Smithuser:alice-smithNode behavior.
Normalize at ingest
Ingest MUST normalize entity and source before persistence.
Reject empty segments
Ingest MUST reject formal URIs with empty authority, type, or id.
Normalize query params
Query parameters for entity and source MUST be normalized before matching.
No rewrite of history
Nodes SHOULD provide migration tooling for legacy non-canonical entities, but MUST NOT rewrite immutable historical facts in place.