Single Source of Truth
One SOAT project can be the record for a system where many orchestrations, triggers and agents write reports as documents and facts as memories, and read each other's, with no external store beside it.
This page maps what such a record needs to the module that provides it. The module pages hold the definitions; nothing is re-defined here. Single Source of Truth walks the write half against one project.
What the record needs
| Need | Mechanism | Where |
|---|---|---|
| One address per record | project_id + path is unique on documents | Documents |
| One fact stored once | Dedup and supersession on write; a declared supersedes | Memories |
| Access control on every read and write | IAM policies, tag conditions, boundary_policy | IAM, Agents |
| A write that cannot be lost | version counter, expected_version / If-Match, 409 VERSION_CONFLICT | Concurrent Writes |
| Every past state readable | Append-only document versions; restore appends | Documents |
| Withdraw without losing history | Tombstone version; ?include_withdrawn=true | Documents |
| Retire a fact that stopped holding | invalidated_at; retracted assertion | Memories |
| Who claimed what, through which door | Memory assertion ledger, source_type | Memories |
| Typed fields, not labels | metadata beside tags, one line between them | IAM |
| One shape every writer is held to | Metadata schema per path prefix, 400 VALIDATION_FAILED | Metadata Schemas |
| Query the record by its fields | Equality, in, ordering on ?metadata= (documents, tasks) and in knowledge search | Documents |
| Say what a document is to another | derived_from, supersedes, cites; ?related_to= | Documents |
| Retrieval with citations | Hybrid search over documents and memories, source_type on every hit | Knowledge |
| Every change reaches its subscribers | Durable event triggers, webhooks | Triggers, Webhooks |
| An operational ledger nothing rewrites | Audit log, activity, usage | Audit Log, Activity, Usage |
| The corpus as a file | NDJSON export, oldest first | Export |
Writes
Every document and memory carries a version. A write may state the version it read; a write against any other version is refused whole, and two writes that race on one record are serialized whether or not either stated one. Concurrent Writes is the definition.
A document write that changes content, title, path, metadata, tags or chunk configuration archives the state it replaced, so a run can cite the version it read and a later reader can fetch exactly that. Restore appends a new version rather than rewinding, and runs through the ordinary update path, so the restored content is re-chunked and re-embedded.
A memory write is recorded twice: the memory is state, and each write attempt is an assertion naming the principal, the mechanism and the outcome, including the writes that changed nothing.
Removal
Neither a document nor a memory needs to be deleted to leave the record.
| Document | Memory | |
|---|---|---|
| Operation | POST /api/v1/documents/{document_id}/withdraw | POST /api/v1/memories/{memory_id}/retract |
| Stored as | A tombstone version | invalidated_at with superseded_by_memory_id null, plus a retracted assertion |
| Leaves | Listings and knowledge search; chunks drop from the index | Listings, dedup and knowledge search |
| Still readable | By id, with every version | By id, with its assertions |
| Comes back through | Restoring a content version | A new write; dedup never matches a retired fact |
| Event | documents.withdrawn, documents.restored | memories.retracted |
DELETE stays permanent on both.
Structure
A record carries two annotation bags. tags are flat string labels the platform reads for authorization and scoping. metadata is typed JSON the platform stores, validates and filters, and never reads for itself. The line between them is defined once, in Tags and metadata.
A metadata schema declares what documents under a path prefix must carry, and every write path that stores the bag is judged by it: the REST routes, a formation, a restore. Documents already stored are not re-judged until their path or metadata is next written.
The same metadata object filters GET /api/v1/documents and POST /api/v1/knowledge/search, with equality, in and gt / gte / lt / lte, matched by type: {"revision": 3} and {"revision": "3"} are different filters. A filter narrows what the caller's policy already allows.
A relation is a typed edge one document asserts about another. Asserting it is a write of the asserting document alone, and ?related_to= finds neighbours on either side.
Delivery
An event trigger writes its firing before the target is touched, so a process that dies mid-dispatch leaves a row another process redelivers. Delivery is at-least-once and deduplicated per (event, trigger), so an event target must be idempotent. Webhooks deliver the same events outside the platform. The names are listed in Webhook Events.
Export
Each export streams newline-delimited JSON, oldest first, one object per line, in the shape the module's listing returns for the same caller. Each is its own IAM action, separate from the listing.
| Export | Narrowed by | Action |
|---|---|---|
GET /api/v1/documents/export | path_prefix; withdrawn documents and /.system/ left out | documents:ExportDocuments |
GET /api/v1/memory-stores/{memory_store_id}/export | ?tags=; include_invalidated=true adds retired facts | memories:ExportMemories |
GET /api/v1/activity/export | The listing's filters | activity:ExportActivity |
GET /api/v1/audit-log/export | The listing's filters | audit:ExportAuditEntries |
Scope
A write precondition covers one record. There is no transaction across records: two writes to two documents are two writes, and where several must move as one, the step is an idempotent orchestration, as Concurrent Writes states.