Bi-temporal PermissionedDataPoint
Every node and every edge in the KB carries the same envelope:
class TemporalRange(BaseModel):
valid_from: datetime # event time start
valid_to: Optional[datetime] # event time end (None = still true)
created_at: datetime # system time start
expired_at: Optional[datetime] # system time end (None = active)
class PermissionedDataPoint(BaseModel):
id: UUID
type: str = "PermissionedDataPoint"
temporal: TemporalRange
acl: ACL # owner + role-based + ABAC + ReBAC anchors
provenance: Provenance # dataset_id + data_id + extractor chain
layer: LayerMembership # PRIVATE / HIERARCHICAL / MARKETPLACE / GLOBAL
index_fields: list[str] # which fields feed the vector embedding
properties: dict[str, Any]
Two timelines
Following the Graphiti / Zep four-timestamp model:
| Pair | Tracks | Closes when |
|---|---|---|
valid_from / valid_to | Real-world event time | Fact stops being true |
created_at / expired_at | System ingest time | Fact is logically invalidated |
A contradicted edge closes valid_to (and optionally
expired_at + invalidated_by_edge_id) — it is never deleted. This
preserves the timeline for as_of=<past_ts> queries.
Provenance chain
Provenance carries dataset_id + data_id + the extractor chain
(["spacy", "gliner", "llm"]) + the pipeline run id. When a tenant
requests targeted forgetting (GDPR / CCPA), KBRuntime.forget
locates rows by dataset/data id and closes their validity window.
ACL envelope
The ACL block carries:
owner_principal_id+owner_tenant_id(RBAC anchor).roles_read/roles_write/roles_delete(RBAC).abac_tags(ABAC — region, classification, time-of-day, ...).rebac_anchor_ids(OpenFGA tuple keys likedocument:abc#viewer).deny_principal_ids(explicit denial list).
DefaultPermissionResolver (in
kb-permissions.md) fuses all four into a
single per-request AccessBitmap.
Bi-temporal merge in the composer
KBLayerComposer.compose_recall collects hits across layers
(private > hierarchical > marketplace > global), then applies the
precedence-aware bi-temporal merger:
- Group hits by entity
id. - The first occurrence (highest precedence) wins.
- Lower-precedence hits get appended to
metadata.dissenting_layersso the UI can surface them transparently. valid_from/valid_toare preserved on every hit so a downstreamas_ofreconstruction is lossless.