@x12i/memorix-metadata 3.6.1 → 3.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +70 -0
- package/README.md +5 -3
- package/docs/memorix-format-reference.md +673 -0
- package/package.json +6 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
## 3.6.3
|
|
2
|
+
|
|
3
|
+
- Updated `docs/memorix-format-reference.md` (copied from `@x12i/memorix-format` at pack time). It now shows how a content type and its `profile` become the `ContentTypeProfileV2` that record validation uses, and how to validate a record against a pack offline. No validation change.
|
|
4
|
+
|
|
5
|
+
## 3.6.2
|
|
6
|
+
|
|
7
|
+
- Ship `docs/memorix-format-reference.md` and `CHANGELOG.md`. Object types, content types, and descriptors were already this package; the reference was not. No validation change.
|
|
8
|
+
|
|
9
|
+
## 3.6.1
|
|
10
|
+
|
|
11
|
+
- A discovery rule may set `contentType` and `sourceEndpoint` (`from` or `to`). `sourcePath` is the id inside that content document. Packs that omit both still validate.
|
|
12
|
+
|
|
13
|
+
## 3.6.0
|
|
14
|
+
|
|
15
|
+
- A content type may declare `writer`: one caller, or a map of discriminator value to caller (`""` when the content type has no discriminator). A content type may declare `privateMetadata`, the `_metadata` key names only that writer reads back. An empty writer map, a blank caller, or a private key that is not a single name (including `private`) is `META-TYPE`. Packs that omit both still validate.
|
|
16
|
+
|
|
17
|
+
## 3.5.0
|
|
18
|
+
|
|
19
|
+
- A content type profile may declare `deletion: { mode, keepBody? }`: `logical`, `tracked`, or `untracked`, with `keepBody` only on `tracked`. An unknown key or mode, a `keepBody` that is not a boolean, or `keepBody` on any other mode is `META-DELETION`. The same content type id on more than one object type must declare the same policy, and no declaration equals `tracked` without the body, so a pack that omits `deletion` still validates. `ContentTypeProfile` gains `deletion`.
|
|
20
|
+
|
|
21
|
+
## 3.4.2
|
|
22
|
+
|
|
23
|
+
- A content type may carry `schema`, a JSON Schema map keyed by discriminator value, and an optional `owner`. `validateMetadataPack({ generateStorySchemas: true })` fills a missing story schema from `inputSchemaFor(template)` for each story template. That option stays off until MS-1 is deployed: the current story writer would otherwise receive 400 and write no stories. An explicit schema already on the pack is kept.
|
|
24
|
+
|
|
25
|
+
## 3.4.1
|
|
26
|
+
|
|
27
|
+
- An object type may declare `functions`. Each entry has `id`, `question`, `page`, `composes`, `pageStory`, and `reads`, plus optional `template` and `kind` (`spine` or `threads`). Raw metadata and effective metadata return the array as it was installed, and a later install of the same pack keeps it. When `functions` is present, each `story` `presentation.values[].value` has exactly one function with that `template`. A pack that omits `functions` still validates, including one that already has a story presentation. More than one `pageStory` on the same page is kept as published. A content type may also declare `presentation` and `origin`.
|
|
28
|
+
|
|
29
|
+
## 3.4.0
|
|
30
|
+
|
|
31
|
+
- `analytics`: `scope` is optional (organization-wide counter). `where` accepts `in: [...]`, `exists: true`, or an array of conditions (all must hold). Groups take `subject` (label) and `split: { key, property, buckets, missing? }`. New `derived[]` (`sum` / `difference` / `ratio` over measures, earlier derived keys, or `group.BUCKET`). `measures[].windows` (`24h` / `7d` / `30d`, count only). `leaders[].countOf: { abstractType, property, where? }`; `sort` is optional when `countOf` is set.
|
|
32
|
+
- Pseudo-property `@datasource` (the record's data source) works wherever a property does. A group over it may omit `buckets`. `ANALYTICS_PSEUDO_PROPERTIES` and `ANALYTICS_RESERVED_BUCKETS` are exported.
|
|
33
|
+
- Buckets `""`, `none`, and `unknown` are reserved; only `split.missing` may use them.
|
|
34
|
+
- Example pack `examples/analytics/review-decisions.pack.json`.
|
|
35
|
+
|
|
36
|
+
## 3.3.0
|
|
37
|
+
|
|
38
|
+
- Built-in kind `analytics`: snapshot-scoped rollup declarations (scope pair, target record id, measures, groups, ratios, splits, unwinds, leaders, coverage, deltas, transitions, trends).
|
|
39
|
+
- Optional `enum` on abstract properties and object-type fields. Group/split buckets must be members of that enum.
|
|
40
|
+
|
|
41
|
+
## 3.2.3
|
|
42
|
+
|
|
43
|
+
- List and item views may store a remembered fetch: `properties`, `filter`, `sort`, `limit` (abstract property names).
|
|
44
|
+
- Example packs: Nexum 2.0.1 — drop `nexum-*` contribution OTs; persist is `content`/`snapshots` (`proposedGraph`); explore URL `:8080`.
|
|
45
|
+
|
|
46
|
+
## 3.2.2
|
|
47
|
+
|
|
48
|
+
- KnowX example pack: Nexum replaced Graphifier (`knowx-nexum` + ingest step id `nexum` + write-record `mode: "nexum"`; drop `knowx-graphify` / `mode: "graphifier"` / write-graph-links / index-graph).
|
|
49
|
+
- OpX example pack: drop leftover snapshot fields `data.graphDraft` / `data.patchPlan` (Nexum persist uses `nexum-*` object types).
|
|
50
|
+
|
|
51
|
+
## 3.2.1
|
|
52
|
+
|
|
53
|
+
- OpX example pack: Nexum replaced Graphifier (`nexum` + `opx-nexum-analyze`; drop `memorix-graphifier` / `opx-graphify-analyze`).
|
|
54
|
+
|
|
55
|
+
## 3.1.0
|
|
56
|
+
|
|
57
|
+
- `buildEffectiveMetadataTopology` / `EffectiveMetadataTopology`.
|
|
58
|
+
- Required `abstractBindings` on metadata packs.
|
|
59
|
+
- KnowX / OPX example packs: `aiService` skill bindings for extract / associate.
|
|
60
|
+
|
|
61
|
+
## 3.0.2
|
|
62
|
+
|
|
63
|
+
- Docs: README install pins + stack/live/core-service references.
|
|
64
|
+
|
|
65
|
+
# Changelog
|
|
66
|
+
|
|
67
|
+
## 3.0.1
|
|
68
|
+
|
|
69
|
+
- Patch release: README/port-map alignment, live-view wiring where applicable.
|
|
70
|
+
|
package/README.md
CHANGED
|
@@ -4,10 +4,12 @@ Declarative **metadata contracts**, **multi-agent effective resolution**, and **
|
|
|
4
4
|
|
|
5
5
|
This package is the **law** layer (what agents, types, views, mappings, and pipelines *are*). It does not install packs into Mongo, read subject records, or serve HTTP.
|
|
6
6
|
|
|
7
|
+
Install it on its own. The only runtime dependency is `@x12i/memorix-mapping`. Record shape and validation are `@x12i/memorix-format`, which has no dependencies. Neither package pulls in the service or the database.
|
|
8
|
+
|
|
7
9
|
## Install
|
|
8
10
|
|
|
9
11
|
```bash
|
|
10
|
-
npm install @x12i/memorix-metadata@3.
|
|
12
|
+
npm install @x12i/memorix-metadata@3.6.3
|
|
11
13
|
```
|
|
12
14
|
|
|
13
15
|
| Audience | Use this package? |
|
|
@@ -81,6 +83,6 @@ Abstracts stay **virtual** — no abstract collections and no abstract `recordId
|
|
|
81
83
|
|
|
82
84
|
## Docs
|
|
83
85
|
|
|
84
|
-
-
|
|
85
|
-
-
|
|
86
|
+
- [`docs/memorix-format-reference.md`](./docs/memorix-format-reference.md) — records, object types, content types, descriptors, and mappings. Section 5 covers object types, content types, and the descriptor; section 5.7 covers validating a record against a pack.
|
|
87
|
+
- [`examples/`](./examples/) — valid packs, and `examples/invalid/` with one failing rule each.
|
|
86
88
|
- Hub: [https://docs.memorix.x12i.com](https://docs.memorix.x12i.com)
|
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
# Memorix format reference: records, object types, content types, descriptors, and mappings
|
|
2
|
+
|
|
3
|
+
This document puts in one place everything that defines how Memorix data is shaped:
|
|
4
|
+
|
|
5
|
+
1. The **record envelope** (`memorix-record/2`): the shape of every stored document.
|
|
6
|
+
2. The **metadata pack** (`memorix-metadata/1`): where an agent declares its **object types (OTs)**, their **content types (CTs)**, the CT **descriptor** (`profile`), fields, abstracts, and relationships.
|
|
7
|
+
3. **Mappings** (the mapping AST): how provider payloads land on an OT/CT, and how concrete fields project to abstract properties.
|
|
8
|
+
|
|
9
|
+
Each section names the package and export that is the law for it. When this document and the code disagree, the code wins.
|
|
10
|
+
|
|
11
|
+
## Using these packages on their own
|
|
12
|
+
|
|
13
|
+
Memorix depends on these packages. Anyone else can install them without the service, the database layer, or the pipeline runtime.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @x12i/memorix-format # records
|
|
17
|
+
npm install @x12i/memorix-metadata # object types, content types, descriptors (brings @x12i/memorix-mapping)
|
|
18
|
+
npm install @x12i/memorix-mapping # mappings only
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
| Package | What you get | Runtime dependencies |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| `@x12i/memorix-format` | The `memorix-record/2` envelope, `validateMemorixRecordV2`, `docs/`, and `meta/` | none |
|
|
24
|
+
| `@x12i/memorix-metadata` | Object types, content types, the content-type descriptor (`profile`), abstracts, relationship declarations, and `validateMetadataPack` | `@x12i/memorix-mapping` only |
|
|
25
|
+
| `@x12i/memorix-mapping` | The mapping AST and `executeMapping` | none |
|
|
26
|
+
|
|
27
|
+
`@x12i/memorix-metadata` installs `@x12i/memorix-mapping` for you. Validating a record or a pack does not require `@x12i/memorix-data`, `@x12i/memorix-service`, or Mongo. Routing and the write path live in `@x12i/memorix-data`; they are listed below so the error codes make sense, and they are not needed to use the format or the descriptors.
|
|
28
|
+
|
|
29
|
+
This file ships as `docs/memorix-format-reference.md` in all three packages. The copy in `@x12i/memorix-format` is the source; the other two are copied from it when they are packed.
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { validateMemorixRecordV2 } from "@x12i/memorix-format/v2";
|
|
33
|
+
import {
|
|
34
|
+
validateMetadataPack,
|
|
35
|
+
type ObjectTypeDefinition,
|
|
36
|
+
type ContentTypeSpec,
|
|
37
|
+
} from "@x12i/memorix-metadata";
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
| Layer | Package | Exports and files |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| Record envelope | `@x12i/memorix-format` | `MemorixRecordV2`, `validateMemorixRecordV2` (from `@x12i/memorix-format/v2`); `docs/memorix-record-v2.md` |
|
|
43
|
+
| Format-level content-type profile | `@x12i/memorix-format` | `ContentTypeProfileV2`, `BUILTIN_CONTENT_PROFILES`, `resolveContentProfile` |
|
|
44
|
+
| OT / CT / descriptor / abstracts / relationships / mapping declarations | `@x12i/memorix-metadata` | `MetadataPack`, `ObjectTypeDefinition`, `ContentTypeSpec`, `ContentTypeProfileDecl`, `validateMetadataPack` |
|
|
45
|
+
| Platform interfaces (Entity / Event / Knowledge) | `@x12i/memorix-metadata` | `PLATFORM_INTERFACES` |
|
|
46
|
+
| Mapping AST and execution | `@x12i/memorix-mapping` | `MappingDocument`, `MappingExpr`, `executeMapping`, `validateMappingDocument` |
|
|
47
|
+
| Physical routing and write-time checks | `@x12i/memorix-data` (Memorix runtime; not needed to use the format) | `databaseNameFor`, `contentCollectionName`, the content writer |
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 1. Vocabulary
|
|
52
|
+
|
|
53
|
+
| Term | Meaning |
|
|
54
|
+
|---|---|
|
|
55
|
+
| **Object type (OT)** | The kind of subject, such as `work-items`, `people`, `vulnerabilities`. Declared in `pack.objectTypes[]`. |
|
|
56
|
+
| **Content type (CT)** | One facet of a subject, such as `snapshots`, `raw`, `analysis`, `workflow`. Declared inside an OT as `objectTypes[].contentTypes[]`. The same `recordId` across several CTs is one subject (composition), not a relationship. |
|
|
57
|
+
| **Data category** | `entity`, `event`, or `knowledge`. Set on the OT. Picks the database. It is not a CT. |
|
|
58
|
+
| **Descriptor** | The CT `profile` object. It is mandatory: `id` plus `profile` is the CT declaration. A write to a CT with no descriptor fails with `METADATA_DESCRIPTOR_MISSING`. See section 5.2. |
|
|
59
|
+
| **Discriminator** | An optional path inside `data` (for example `data.template`) whose value splits one CT into variants. Schemas, writers, and tabs can be keyed by discriminator value. |
|
|
60
|
+
| **Abstract type** | A metadata-only type that one or more OTs project into (for example `People`). Never has a collection or its own `recordId`. Views, agent services, and relationships speak abstracts. |
|
|
61
|
+
| **Platform interface** | The sealed abstracts `Entity`, `Event`, `Knowledge`. Every OT implements the one that matches its data category. |
|
|
62
|
+
| **Mapping** | A declarative transform written in the mapping AST. Purpose is `source`, `abstract`, or `completion`. |
|
|
63
|
+
| **Managed descriptor (legacy)** | Older Catalox "entity descriptors" in `memorix-retrieval/catalox-seeds`. Only an inventory of them is kept, in `meta/inventory/managed-descriptors.json` of `@x12i/memorix-format`. They are not format law and not agent packs. |
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 2. Physical layout
|
|
68
|
+
|
|
69
|
+
| Thing | Name | Notes |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| Database | `{orgId}-memorix-{entity\|event\|knowledge\|operational}` | The OT `dataCategory` picks one of the first three. |
|
|
72
|
+
| Subject content collection | `{objectType}-{contentType}` | For example `work-items-snapshots`. Lives in the OT's category database. |
|
|
73
|
+
| Relationships | `relationships` | Reserved name. One per category database. A CT cannot be named `relationships` (`ROUTE_RESERVED_RELATIONSHIPS`). |
|
|
74
|
+
| Deletion ledger | `deletions` | One per category database. Holds `memorix-deletion/1` entries. No hyphen, so it never collides with `{ot}-{ct}`. |
|
|
75
|
+
| Record directory | `record-directory` | In the `operational` database. |
|
|
76
|
+
|
|
77
|
+
OT and CT ids must match `^[a-z][a-z0-9-]*$` (lowercase, digits, hyphen, starting with a letter). Otherwise routing fails with `ROUTE_UNSAFE_OBJECT_TYPE` or `ROUTE_UNSAFE_CONTENT_TYPE`.
|
|
78
|
+
|
|
79
|
+
A write to an OT that is not in the caller's effective metadata fails with `ROUTE_UNKNOWN_OBJECT_TYPE`. A CT that is not declared on that OT fails with `ROUTE_UNKNOWN_CONTENT_TYPE`. Writes never create types; register them with `POST /api/metadata/ensure` first.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 3. The record envelope: `memorix-record/2`
|
|
84
|
+
|
|
85
|
+
Every document in an `{ot}-{ct}` collection has this shape. Validate with `validateMemorixRecordV2` from `@x12i/memorix-format/v2`, or `npx memorix-format validate-v2 <file|dir>`.
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
type MemorixRecordV2 = {
|
|
89
|
+
format: "memorix-record/2";
|
|
90
|
+
recordId: string;
|
|
91
|
+
revision: number;
|
|
92
|
+
objectType: string;
|
|
93
|
+
contentType: string;
|
|
94
|
+
dataCategory: "entity" | "event" | "knowledge";
|
|
95
|
+
concept: {
|
|
96
|
+
title: string;
|
|
97
|
+
identifiers: {
|
|
98
|
+
primary: { kind: string; value: string; issuer?: string; normalizedValue?: string };
|
|
99
|
+
alternates?: Array<{ kind: string; value: string; issuer?: string; normalizedValue?: string }>;
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
data: Record<string, unknown>;
|
|
103
|
+
subject?: { objectType: string; recordId: string };
|
|
104
|
+
context?: Record<string, unknown>;
|
|
105
|
+
_metadata?: Record<string, unknown>;
|
|
106
|
+
_markdown?: { content: string; source: "ai" | "json" | "content"; metadata?: Record<string, unknown> };
|
|
107
|
+
_audit?: Record<string, unknown>;
|
|
108
|
+
_history?: Array<Record<string, unknown>>;
|
|
109
|
+
analytics?: Record<string, unknown>;
|
|
110
|
+
enrichment?: Record<string, unknown>;
|
|
111
|
+
insights?: Record<string, unknown>;
|
|
112
|
+
// associated<Name>?: Array<{ relationshipDefinitionId: string; recordId: string; relationshipId?: string }>;
|
|
113
|
+
_system: {
|
|
114
|
+
state: "active" | "archived" | "superseded" | "deleted";
|
|
115
|
+
createdAt: string;
|
|
116
|
+
modifiedAt: string;
|
|
117
|
+
observedAt?: string;
|
|
118
|
+
writtenAt?: string;
|
|
119
|
+
lifecycle?: Array<{ process: string; processedAt: string }>;
|
|
120
|
+
provenance?: Record<string, unknown>;
|
|
121
|
+
_datasource?: {
|
|
122
|
+
service: string; ingress: string; basis: string; recordedAt: string;
|
|
123
|
+
origin?: string; channel?: string; originRecordId?: string; originAt?: string;
|
|
124
|
+
sources?: Array<{ system: string; name: string }>;
|
|
125
|
+
};
|
|
126
|
+
deletion?: { deletedAt: string; by: string; via: "content" | "patch"; patchId?: string; reason?: string };
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### 3.1 Envelope properties
|
|
132
|
+
|
|
133
|
+
| Property | Required | Meaning and rules |
|
|
134
|
+
|---|---|---|
|
|
135
|
+
| `format` | yes | Always `"memorix-record/2"`. |
|
|
136
|
+
| `recordId` | yes | Subject identity. Shared by every CT of the same subject. Never the Mongo `_id`. Source identifiers are adopted as `{issuer}/{kind}/{value}`, never a bare provider id. |
|
|
137
|
+
| `revision` | yes | Starts at 1 and increments on every successful mutation. A stale `expectedRevision` is rejected. |
|
|
138
|
+
| `objectType` | yes | Must be a declared OT. Must match the collection. |
|
|
139
|
+
| `contentType` | yes | Must be a CT declared on that OT. |
|
|
140
|
+
| `dataCategory` | yes | Must equal the OT's `dataCategory`. |
|
|
141
|
+
| `concept.title` | yes | Human-readable title. Default source for the platform interface `title` property. |
|
|
142
|
+
| `concept.identifiers.primary` | yes | `{ kind, value }`, optional `issuer` and `normalizedValue`. |
|
|
143
|
+
| `concept.identifiers.alternates` | no | Omit when empty. |
|
|
144
|
+
| `data` | yes | The subject content itself. JSON markdown is rendered from `data` only. |
|
|
145
|
+
| `subject` | no | `{ objectType, recordId }` of the subject this row is about, when that subject is a different OT. The row keeps its own `recordId`. List filters use `subject.recordId`. |
|
|
146
|
+
| `context` | no | Context payloads that are not the subject content. Omit when empty. Not rendered to markdown. |
|
|
147
|
+
| `_metadata` | no | Producer context (for example `stepId`, `writtenBy`). A key here must not also be in `data`. Not `_system.provenance` and not `_markdown.metadata`. |
|
|
148
|
+
| `_markdown` | no | Original text beside `data`. `source` is `ai`, `json`, or `content`. Writers fill `source: "json"` from `data` when it is omitted. Readers omit it unless the fetch asks for markdown. |
|
|
149
|
+
| `_audit` | no | Audit of the record. When present, `provider`, `model`, and `reasoningEffort` must be non-empty strings. Other keys are open. |
|
|
150
|
+
| `_history` | no | Earlier products of this record, oldest first. Each entry is an object stored as given. An empty array is valid and differs from an absent key. A write that removes fields appends `{ action: "patch", caller, modifiedAt, removed: string[] }`. |
|
|
151
|
+
| `analytics` | no | Numeric rollups. Allowed only when the CT descriptor has `allowSnapshotBuckets: true`. |
|
|
152
|
+
| `enrichment` | no | External or prior-system values as received. Same descriptor rule. |
|
|
153
|
+
| `insights` | no | Judgments, scores, recommendations. Same descriptor rule. |
|
|
154
|
+
| `associated<Name>` | no | Lightweight relationship refs only (`relationshipDefinitionId` + `recordId`, optional `relationshipId`). Copied related payloads are rejected. Authoritative relationships live in the `relationships` collection, not here. |
|
|
155
|
+
| `_system.state` | yes | `active`, `archived`, `superseded`, or `deleted`. |
|
|
156
|
+
| `_system.createdAt` / `modifiedAt` | yes | ISO timestamps. |
|
|
157
|
+
| `_system.observedAt` | no | Default source for the `Event.occurredAt` interface property. |
|
|
158
|
+
| `_system.writtenAt` | no | Clock time of the write that produced this revision. |
|
|
159
|
+
| `_system._datasource` | no | Memorix-owned document source (`service`, `ingress`, `basis`, `recordedAt`, optional `origin`, `channel`, `sources`). Not part of `data`. |
|
|
160
|
+
| `_system.lifecycle` | no | Appended only for meaningful process events. Omit when empty. Independent of `revision`. |
|
|
161
|
+
| `_system.provenance` | no | Source attribution such as `sourceId` or `endpoint`. |
|
|
162
|
+
| `_system.deletion` | conditional | Present if and only if `state` is `deleted` (`V2-DELETION`). |
|
|
163
|
+
|
|
164
|
+
### 3.2 Envelope rules that come from the CT descriptor
|
|
165
|
+
|
|
166
|
+
- Empty optional buckets are omitted. Never write `{}` or `[]`.
|
|
167
|
+
- `analytics`, `enrichment`, and `insights` appear only on CTs whose descriptor sets `allowSnapshotBuckets: true`.
|
|
168
|
+
- If the CT declares a discriminator with a `defaultValue` and the record lacks it, validation warns `V2-DISCRIMINATOR-DEFAULT` and the writer fills the default. If it is required with no default, validation fails with `V2-DISCRIMINATOR`.
|
|
169
|
+
- What a delete does is declared on the CT (`profile.deletion`), see section 5.2.
|
|
170
|
+
- Section 5.7 shows how the metadata descriptor becomes the profile that `validateMemorixRecordV2` checks.
|
|
171
|
+
|
|
172
|
+
Golden examples live in `examples/v2/` of `@x12i/memorix-format`.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## 4. The metadata pack: `memorix-metadata/1`
|
|
177
|
+
|
|
178
|
+
An agent's whole catalog is one pack. `POST /api/metadata/ensure` installs the pack you send and **replaces** the agent's catalog, so anything omitted becomes unknown. Always merge from `GET /api/metadata/raw?agentId=…`.
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
type MetadataPack = {
|
|
182
|
+
format: "memorix-metadata/1";
|
|
183
|
+
agentId: string;
|
|
184
|
+
version?: string;
|
|
185
|
+
agents: AgentDefinition[];
|
|
186
|
+
objectTypes: ObjectTypeDefinition[]; // section 5
|
|
187
|
+
abstractTypes: AbstractTypeDefinition[]; // section 6
|
|
188
|
+
relationshipDefinitions: RelationshipDefinition[]; // section 7
|
|
189
|
+
mappings: MappingDefinition[]; // section 8
|
|
190
|
+
views: ViewDefinition[];
|
|
191
|
+
writes: WriteDefinition[];
|
|
192
|
+
sources: SourceDefinition[];
|
|
193
|
+
services: ServiceDefinition[];
|
|
194
|
+
pipelines: PipelineDefinition[];
|
|
195
|
+
schedules: ScheduleDefinition[];
|
|
196
|
+
analytics?: AnalyticsDefinition[];
|
|
197
|
+
kindDeclarations?: KindDeclarationDefinition[];
|
|
198
|
+
extensions?: Record<string, Array<{ id: string } & Record<string, unknown>>>;
|
|
199
|
+
};
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Validate in-process with `validateMetadataPack(pack)` from `@x12i/memorix-metadata`. Issues come back as `{ path, code, message }` with codes such as `META-REQUIRED`, `META-CT-PROFILE`, `META-LEADING`, `META-REF-CONTENT`.
|
|
203
|
+
|
|
204
|
+
This document covers `objectTypes`, `abstractTypes`, `relationshipDefinitions`, and `mappings`. Views, sources, services, pipelines, schedules, and analytics are typed in the same `types.ts`.
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## 5. Object types and content types
|
|
209
|
+
|
|
210
|
+
### 5.1 Object type (`ObjectTypeDefinition`)
|
|
211
|
+
|
|
212
|
+
| Property | Required | Type | Meaning and rules |
|
|
213
|
+
|---|---|---|---|
|
|
214
|
+
| `id` | yes | string | OT id. Must be routing-safe (`^[a-z][a-z0-9-]*$`). Must not be `Entity`, `Event`, or `Knowledge` (`META-PLATFORM-RESERVED`). |
|
|
215
|
+
| `name` | yes | string | Display name. |
|
|
216
|
+
| `dataCategory` | yes | `entity` \| `event` \| `knowledge` | Picks the database. |
|
|
217
|
+
| `implements` | no | string[] | Interfaces this OT implements. Must include the platform interface for its category (`entity` → `Entity`, and so on) and no other platform interface (`META-INTERFACE-CATEGORY`). Omitted means the category interface. |
|
|
218
|
+
| `interfaceBindings` | no | `ObjectTypeInterfaceBinding[]` | Overrides the default path of an interface property. Each entry: `interfaceId`, `property`, `propertyPath`, optional `contentType` (defaults to the first CT that is not `relationships`). |
|
|
219
|
+
| `abstractTypes` | no | string[] | Abstract types this OT participates in. |
|
|
220
|
+
| `contentTypes` | yes | `ContentTypeSpec[]` | Non-empty. Ids unique within the OT. At most one may set `leading: true` (`META-LEADING`). See 5.2. |
|
|
221
|
+
| `fields` | no | `ObjectTypeFieldSpec[]` | Declared concrete fields. See 5.3. |
|
|
222
|
+
| `functions` | no | `ObjectTypeFunction[]` | Functions the skill API publishes on this OT. See 5.4. |
|
|
223
|
+
|
|
224
|
+
### 5.2 Content type (`ContentTypeSpec`) and its descriptor
|
|
225
|
+
|
|
226
|
+
| Property | Required | Type | Meaning and rules |
|
|
227
|
+
|---|---|---|---|
|
|
228
|
+
| `id` | yes | string | CT id. Routing-safe. Must not be `relationships`. |
|
|
229
|
+
| `profile` | yes | `ContentTypeProfileDecl` | **The descriptor.** See below. |
|
|
230
|
+
| `required` | no | boolean | Subjects of this OT should have this CT. When `true`, `requiredReason` is required. |
|
|
231
|
+
| `requiredReason` | conditional | string | Why the CT is required. |
|
|
232
|
+
| `notes` | no | string | Free text, for example who writes it. |
|
|
233
|
+
| `retention` | no | string | Retention note. |
|
|
234
|
+
| `leading` | no | boolean | Default page driver for the OT. At most one per OT. With none set, clients use the first CT that is not `relationships`. |
|
|
235
|
+
| `discriminator` | no | `{ path, required?, defaultValue? }` | `path` is required when `discriminator` is set. `defaultValue` must be a non-empty string. |
|
|
236
|
+
| `schema` | no | `Record<discriminatorValue, JSONSchema>` | JSON Schema for `data`, keyed by discriminator value. A failing write returns 422 `CONTENT_INVALID`. Omitted means only the discriminator is checked. |
|
|
237
|
+
| `owner` | no | string | When set, a later write is refused with 409 `ADDRESS_OWNED` if the stored row's owner (`_metadata.stepId`, else `_metadata.writtenBy`) is different. |
|
|
238
|
+
| `writer` | no | string \| `Record<discriminatorValue, caller>` | Which `x-memorix-caller` may write. A string owns every value. A map owns one value each (`""` stands for the CT when it has no discriminator). A different caller gets 409 `WRITER_NOT_OWNER`. |
|
|
239
|
+
| `privateMetadata` | no | string[] | `_metadata` keys returned only to the caller that wrote the row. Non-empty; key names match `^[A-Za-z0-9_-]+$` and must not be `private`. |
|
|
240
|
+
| `fieldMappings` | no | `FieldMappingProfile[]` | Assistant-published field presentation. Each: `id`, `format: "memorix-field-map/1"`, `namespace`, `fieldMap` (object), optional `discriminatorValue`, `standingRules`, `requiresCapabilities`, `provenance`. |
|
|
241
|
+
| `presentation` | no | `{ label?, order?, tab?, values? }` | Client tab. `tab` is `always`, `whenPresent`, or `hidden`. `values[]` are `{ value, label?, order?, origin? }` where `value` matches `data` at the discriminator path. |
|
|
242
|
+
| `origin` | no | `{ producer?, use?, record? }` | `producer` is `model`, `code`, or `mixed`. `use` is `always` or `conditional`. |
|
|
243
|
+
|
|
244
|
+
**The descriptor (`profile`)** is metadata only; it is never stored on the record.
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
type ContentTypeProfileDecl = {
|
|
248
|
+
allowSnapshotBuckets: boolean; // required
|
|
249
|
+
deletion?: {
|
|
250
|
+
mode: "logical" | "tracked" | "untracked";
|
|
251
|
+
keepBody?: boolean; // tracked only, default false
|
|
252
|
+
};
|
|
253
|
+
};
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
| Descriptor key | Rules |
|
|
257
|
+
|---|---|
|
|
258
|
+
| `allowSnapshotBuckets` | Required boolean. When `true`, records of this CT may carry `analytics`, `enrichment`, `insights`. The **same CT id must declare the same value on every OT** that uses it. |
|
|
259
|
+
| `deletion.mode` | `logical`: the row stays with `_system.state: "deleted"` and reads hide it. `tracked`: the row is removed and a `memorix-deletion/1` entry is written to `deletions`. `untracked`: the row is removed and nothing is recorded. Omitted `deletion` means `{ mode: "tracked", keepBody: false }`. |
|
|
260
|
+
| `deletion.keepBody` | Only valid with `tracked`. Keeps the removed body on the ledger entry. Purges and legacy-instance deletions never keep the body. A CT declares the same policy on every OT that uses it. |
|
|
261
|
+
|
|
262
|
+
Write-time enforcement: if the effective catalog has no descriptor for the CT, the writer throws `METADATA_DESCRIPTOR_MISSING` (HTTP 400, with a `next` hint pointing at `/api/metadata/ensure`).
|
|
263
|
+
|
|
264
|
+
### 5.3 Fields (`ObjectTypeFieldSpec`)
|
|
265
|
+
|
|
266
|
+
Optional declared concrete fields. Used for metadata-only path and type checks, abstract binding validation, and write-time enum checks.
|
|
267
|
+
|
|
268
|
+
| Property | Required | Meaning |
|
|
269
|
+
|---|---|---|
|
|
270
|
+
| `path` | yes | Property path inside the content document, for example `data.status`. |
|
|
271
|
+
| `valueType` | yes | For example `string`, `number`, `datetime`, `recordId`. |
|
|
272
|
+
| `contentType` | no | Which CT the field lives on. Must be declared on the same OT (`META-REF-CONTENT`). Omitted means all CTs of the OT. |
|
|
273
|
+
| `enum` | no | Closed vocabulary. Writers reject any other string, including `""` (`WRITE_ENUM_INVALID`). A missing field stays absent; it is never coerced to `""`. |
|
|
274
|
+
| `refersTo` | conditional | `{ objectType }`. Required when the field holds a `recordId` or an array of them. |
|
|
275
|
+
|
|
276
|
+
### 5.4 Functions (`ObjectTypeFunction`)
|
|
277
|
+
|
|
278
|
+
Published by the skill API and run with `POST /api/v1/agent-functions/agents/{agentId}/functions/{id}/run`.
|
|
279
|
+
|
|
280
|
+
| Property | Required | Meaning |
|
|
281
|
+
|---|---|---|
|
|
282
|
+
| `id` | yes | Function id. |
|
|
283
|
+
| `question` | yes | The question the function answers. |
|
|
284
|
+
| `page` | yes | Page the function renders into. |
|
|
285
|
+
| `composes` / `pageStory` | yes | Booleans. |
|
|
286
|
+
| `reads` | yes | `{ objectType?, contentType?, recordId?, template?, sameSubject? }[]`: content loaded before the run. |
|
|
287
|
+
| `template` | no | Matches `presentation.values[].value` and `data.template` on a story row. |
|
|
288
|
+
| `kind` | no | `spine` or `threads`. |
|
|
289
|
+
|
|
290
|
+
### 5.5 Content-type vocabulary in use
|
|
291
|
+
|
|
292
|
+
Any routing-safe id may be declared. These are the CTs the platform and current packs use (`meta/content-types.json` in `@x12i/memorix-format`):
|
|
293
|
+
|
|
294
|
+
| CT | Typical descriptor | Purpose |
|
|
295
|
+
|---|---|---|
|
|
296
|
+
| `snapshots` | `allowSnapshotBuckets: true` | Full readable view of the subject. The canonical CT. |
|
|
297
|
+
| `raw` | `false` | Default landing CT for pulled provider payloads. |
|
|
298
|
+
| `context` | `false` | Context fetched on demand. |
|
|
299
|
+
| `analysis`, `decisions`, `discovery`, `scoped`, `legacy` | `false` | Skill and judgment products. Some older collections use the singular postfix `decision`. |
|
|
300
|
+
| `narratives`, `narrative-steps` | `false` | Narrative records. |
|
|
301
|
+
| `workflow` | `false` | Product workflow overlay (`memorix-workflow/1`). A sibling CT, not a separate document system. |
|
|
302
|
+
| `extrated`, `chunks` | `false` | Document extraction and chunks. `extrated` is spelled as it is in the managed descriptors. |
|
|
303
|
+
| `knowx`, `nexum`, `confirmations` | `false` | KnowX facets, Nexum contributions, accept or reject decisions. |
|
|
304
|
+
| `ai-processing`, `suggested-plans`, `suggested-actions` | `false` | Optional product CTs. |
|
|
305
|
+
|
|
306
|
+
### 5.6 Who declares what
|
|
307
|
+
|
|
308
|
+
The lander declares OTs and their source CTs. A skill appends only its target CTs to an OT that already exists, and never re-declares source CTs. Because ensure replaces the catalog, a skill always merges into the full pack from `/api/metadata/raw` before it ensures.
|
|
309
|
+
|
|
310
|
+
### 5.7 From metadata descriptor to format profile
|
|
311
|
+
|
|
312
|
+
`@x12i/memorix-format` does not read metadata packs. `validateMemorixRecordV2` takes a `ContentTypeProfileV2`, the format-level descriptor:
|
|
313
|
+
|
|
314
|
+
```ts
|
|
315
|
+
type ContentTypeProfileV2 = {
|
|
316
|
+
contentType: string;
|
|
317
|
+
allowSnapshotBuckets: boolean;
|
|
318
|
+
discriminator?: { path: string; required: boolean; defaultValue?: string; allowedValues?: string[] };
|
|
319
|
+
allowedExtraRoots?: string[];
|
|
320
|
+
deletion?: { mode: "logical" | "tracked" | "untracked"; keepBody?: boolean };
|
|
321
|
+
};
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Memorix builds it from the metadata content type like this:
|
|
325
|
+
|
|
326
|
+
| `ContentTypeProfileV2` | Comes from (`ContentTypeSpec`) |
|
|
327
|
+
|---|---|
|
|
328
|
+
| `contentType` | `id` |
|
|
329
|
+
| `allowSnapshotBuckets` | `profile.allowSnapshotBuckets` |
|
|
330
|
+
| `deletion` | `profile.deletion`, parsed with `parseDeletionPolicy`. An invalid declaration reads as absent. |
|
|
331
|
+
| `discriminator.path` | `discriminator.path`. With no path, there is no discriminator. |
|
|
332
|
+
| `discriminator.required` | `discriminator.required === true` (default `false`) |
|
|
333
|
+
| `discriminator.defaultValue` | `discriminator.defaultValue` when it is a non-empty string |
|
|
334
|
+
| `allowedValues`, `allowedExtraRoots` | Not set from metadata. Available to callers that validate offline. |
|
|
335
|
+
|
|
336
|
+
The lookup is by content-type id across the caller's effective object types, and the first match wins. That is why one CT id must declare the same `allowSnapshotBuckets` and `deletion` on every OT (`META-CT-PROFILE`).
|
|
337
|
+
|
|
338
|
+
When no profile is passed, `validateMemorixRecordV2` falls back to `resolveContentProfile(contentType)`, which uses `BUILTIN_CONTENT_PROFILES`:
|
|
339
|
+
|
|
340
|
+
| CT | `allowSnapshotBuckets` | Built-in discriminator |
|
|
341
|
+
|---|---|---|
|
|
342
|
+
| `snapshots`, `knowledge` | `true` | none |
|
|
343
|
+
| `raw`, `workflow`, `context`, `relationships`, `events` | `false` | none |
|
|
344
|
+
| `discovery` | `false` | `data.discoveryType`, default `default` |
|
|
345
|
+
| `analysis` | `false` | `data.analysisType`, default `default` |
|
|
346
|
+
| `decisions` | `false` | `data.decisionType`, default `default` |
|
|
347
|
+
| any other id | `false` | none |
|
|
348
|
+
|
|
349
|
+
A pack's descriptor always replaces the built-in profile, including its discriminator.
|
|
350
|
+
|
|
351
|
+
To validate a record offline against a pack, without Memorix:
|
|
352
|
+
|
|
353
|
+
```ts
|
|
354
|
+
import {
|
|
355
|
+
validateMemorixRecordV2,
|
|
356
|
+
parseDeletionPolicy,
|
|
357
|
+
type ContentTypeProfileV2,
|
|
358
|
+
} from "@x12i/memorix-format/v2";
|
|
359
|
+
import type { ContentTypeSpec } from "@x12i/memorix-metadata";
|
|
360
|
+
|
|
361
|
+
function toProfile(ct: ContentTypeSpec): ContentTypeProfileV2 {
|
|
362
|
+
const deletion = ct.profile.deletion ? parseDeletionPolicy(ct.profile.deletion) : undefined;
|
|
363
|
+
return {
|
|
364
|
+
contentType: ct.id,
|
|
365
|
+
allowSnapshotBuckets: ct.profile.allowSnapshotBuckets,
|
|
366
|
+
...(deletion?.ok ? { deletion: deletion.policy } : {}),
|
|
367
|
+
...(ct.discriminator?.path
|
|
368
|
+
? {
|
|
369
|
+
discriminator: {
|
|
370
|
+
path: ct.discriminator.path,
|
|
371
|
+
required: ct.discriminator.required === true,
|
|
372
|
+
...(ct.discriminator.defaultValue ? { defaultValue: ct.discriminator.defaultValue } : {}),
|
|
373
|
+
},
|
|
374
|
+
}
|
|
375
|
+
: {}),
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const ct = pack.objectTypes.find((o) => o.id === record.objectType)!
|
|
380
|
+
.contentTypes.find((c) => c.id === record.contentType)!;
|
|
381
|
+
const result = validateMemorixRecordV2(record, { profile: toProfile(ct), enforceDiscriminator: true });
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
## 6. Abstract types and platform interfaces
|
|
387
|
+
|
|
388
|
+
### 6.1 Platform interfaces
|
|
389
|
+
|
|
390
|
+
Injected into every effective-metadata result. Packs cannot redefine them.
|
|
391
|
+
|
|
392
|
+
| Interface | Category | Properties (id → default path) |
|
|
393
|
+
|---|---|---|
|
|
394
|
+
| `Entity` | `entity` | `title` (string, required) → `concept.title` |
|
|
395
|
+
| `Event` | `event` | `title` (string, required) → `concept.title`; `occurredAt` (datetime, required) → `_system.observedAt` |
|
|
396
|
+
| `Knowledge` | `knowledge` | `title` (string, required) → `concept.title` |
|
|
397
|
+
|
|
398
|
+
Override a default path with `interfaceBindings` on the OT.
|
|
399
|
+
|
|
400
|
+
### 6.2 Abstract type (`AbstractTypeDefinition`)
|
|
401
|
+
|
|
402
|
+
Abstracts exist only in metadata. Declaring a collection or an abstract `recordId` is rejected (`META-ABS-RECORD`).
|
|
403
|
+
|
|
404
|
+
| Property | Required | Meaning |
|
|
405
|
+
|---|---|---|
|
|
406
|
+
| `id`, `name`, `meaning`, `category` | yes | Identity and description. |
|
|
407
|
+
| `properties` | no | `AbstractPropertyDefinition[]`: `{ id, valueType, required?, name?, description?, enum? }`. `valueType` is one of `string`, `number`, `boolean`, `date`, `datetime`, `enum`, `object`, `array`, `content`, `recordId`, `unknown`. |
|
|
408
|
+
| `members` | no | `AbstractMemberBinding[]`: which OTs participate and how each fills the properties. |
|
|
409
|
+
| `union` | no | `true` when more than one OT participates. |
|
|
410
|
+
| `platformOwned` | no | Only on the injected platform interfaces. Packs must omit it. |
|
|
411
|
+
| `notes` | no | Free text. |
|
|
412
|
+
|
|
413
|
+
### 6.3 Member and property bindings (OT → abstract mapping)
|
|
414
|
+
|
|
415
|
+
```ts
|
|
416
|
+
type AbstractMemberBinding = {
|
|
417
|
+
objectType: string;
|
|
418
|
+
contentType?: string; // default CT for this member's properties
|
|
419
|
+
properties?: AbstractPropertyBinding[];
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
type AbstractPropertyBinding = {
|
|
423
|
+
abstractProperty: string;
|
|
424
|
+
contentType: string;
|
|
425
|
+
propertyPath?: string; // direct binding, e.g. "data.displayName"
|
|
426
|
+
concreteValueType?: string; // for direct type-compatibility checks
|
|
427
|
+
mappingId?: string; // converted binding: a mapping with purpose "abstract"
|
|
428
|
+
reverse?: { propertyPath?: string; mappingId?: string }; // write-back; absent = read-only
|
|
429
|
+
};
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
Each property binding must set `propertyPath`, `mappingId`, or both (`META-BIND-PROP`). A `reverse` block must also name at least one of them (`META-BIND-REVERSE`). Without `reverse`, the abstract property cannot be written through.
|
|
433
|
+
|
|
434
|
+
The legacy separate `abstractBindings[]` kind is folded into `abstractTypes[].members` on load and is always empty after normalization.
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
## 7. Relationship definitions
|
|
439
|
+
|
|
440
|
+
Relationships are declared on abstracts, never on concrete collections. Storage is the central `relationships` collection; declaring storage on the definition is rejected (`META-REL-STORAGE`).
|
|
441
|
+
|
|
442
|
+
| Property | Required | Meaning |
|
|
443
|
+
|---|---|---|
|
|
444
|
+
| `id`, `name`, `meaning` | yes | Identity. |
|
|
445
|
+
| `directionality` | yes | `directed` or `undirected`. |
|
|
446
|
+
| `from`, `to` | yes | `{ abstractType, path }`. `path` is the directional navigation path on that abstract (`META-REL-PATH`). |
|
|
447
|
+
| `discoveryRules` | yes | At least one (`META-REL-RULES`). Each: `{ id, kind, sourcePath?, contentType?, sourceEndpoint?: "from" \| "to", notes? }`. `sourcePath` is inside the `data` of `contentType`. Without `contentType` the rule scans `raw` then `snapshots`. |
|
|
448
|
+
|
|
449
|
+
Concrete OT-to-OT edges are derived, not authored: see `buildEffectiveMetadataTopology` and `GET /api/metadata/topology`.
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
## 8. Mappings
|
|
454
|
+
|
|
455
|
+
### 8.1 Mapping declaration (`MappingDefinition`)
|
|
456
|
+
|
|
457
|
+
| Property | Required | Meaning |
|
|
458
|
+
|---|---|---|
|
|
459
|
+
| `id` | yes | Mapping id. |
|
|
460
|
+
| `purpose` | yes | `source`, `abstract`, or `completion` (`META-MAP-PURPOSE`). |
|
|
461
|
+
| `objectType`, `contentType` | yes | The OT/CT the mapping lands on or reads from. |
|
|
462
|
+
| `sourceId` | for `source` | The `sources[].id` the payload comes from. |
|
|
463
|
+
| `endpoint` | no | Provider endpoint, for example `/work-items/:id/context`. |
|
|
464
|
+
| `abstractType` | for `abstract` | The abstract whose properties the map produces. |
|
|
465
|
+
| `mode`, `trigger` | no | For example `mode: "knowledge"` or `"context"`, `trigger: "on-demand"`. |
|
|
466
|
+
| `map` | yes | Output path → expression. See 8.2. |
|
|
467
|
+
| `reverse` | no | `Record<outputProperty, { expr, targetPath }>`. Explicit write-back for abstract outputs. Never inferred from the forward map. |
|
|
468
|
+
|
|
469
|
+
Execution and schedule policy keys are rejected on mappings (`META-MAP-EXEC`). Completion policy belongs on the pipeline step that references the mapping.
|
|
470
|
+
|
|
471
|
+
What the `map` keys mean depends on the purpose:
|
|
472
|
+
|
|
473
|
+
| Purpose | Input (`path` reads from) | Output keys |
|
|
474
|
+
|---|---|---|
|
|
475
|
+
| `source` | The provider payload. `$` is the whole payload. | Record paths, such as `recordId`, `data.displayName`, or `data` (whole payload into `data`). |
|
|
476
|
+
| `abstract` | The stored record, such as `data.status` or `concept.title`. | Abstract property names, such as `title`, `status`. |
|
|
477
|
+
| `completion` | The stored record. | Record paths to fill. |
|
|
478
|
+
|
|
479
|
+
### 8.2 Mapping AST
|
|
480
|
+
|
|
481
|
+
Each `map` value is either a bare expression or `{ "expr": <expression>, "required": true | false }`. Missing optional outputs are omitted; a missing required output is an error.
|
|
482
|
+
|
|
483
|
+
| Operation | Shape | Behavior |
|
|
484
|
+
|---|---|---|
|
|
485
|
+
| `path` | `{ op: "path", path }` | Read a value by dotted path. `$` is the input root; inside `array.*` it is the current element. |
|
|
486
|
+
| `constant` | `{ op: "constant", value }` | A literal. Never a secret. |
|
|
487
|
+
| `cast` | `{ op: "cast", expr, to: "string" \| "number" \| "boolean" }` | Type conversion. |
|
|
488
|
+
| `date` | `{ op: "date", expr, acceptedFormats: [...] }` | Parse with explicit formats, for example `["iso"]`. |
|
|
489
|
+
| `enum` | `{ op: "enum", expr, map: { source: target }, default? }` | Value translation. `default` applies when the input is present but not in `map`. |
|
|
490
|
+
| `coalesce` | `{ op: "coalesce", exprs: [...] }` | First non-missing value. |
|
|
491
|
+
| `conditional` | `{ op: "conditional", when, then, else? }` | Branch. |
|
|
492
|
+
| `template` | `{ op: "template", template, vars: { name: expr }, requiredVars? }` | String template. `requiredVars` defaults to every var. |
|
|
493
|
+
| `array.map` | `{ op: "array.map", expr, item }` | Map each element (`$` is the element). |
|
|
494
|
+
| `array.filter` | `{ op: "array.filter", expr, predicate }` | Keep elements whose predicate is boolean `true`. |
|
|
495
|
+
| `transform` | `{ op: "transform", name, version, args? }` | A registered transform, for example `lowercase@1`. See `createDefaultRegistry`. |
|
|
496
|
+
|
|
497
|
+
Limits: depth 32, 512 nodes, 10,000 array items (`DEFAULT_MAPPING_LIMITS`).
|
|
498
|
+
|
|
499
|
+
Run with `executeMapping`, `simulateMapping` (adds coverage), and `executeReverseMapping` (returns an update plan; the mapping never writes).
|
|
500
|
+
|
|
501
|
+
---
|
|
502
|
+
|
|
503
|
+
## 9. Worked example
|
|
504
|
+
|
|
505
|
+
A pack fragment with one OT, two CTs, an abstract with a direct binding, a source mapping, and an abstract mapping. A complete real pack is `examples/opx/normalized/pack.json` in `@x12i/memorix-metadata`.
|
|
506
|
+
|
|
507
|
+
```json
|
|
508
|
+
{
|
|
509
|
+
"format": "memorix-metadata/1",
|
|
510
|
+
"agentId": "opx",
|
|
511
|
+
"agents": [{ "id": "opx", "name": "OPX" }],
|
|
512
|
+
"objectTypes": [
|
|
513
|
+
{
|
|
514
|
+
"id": "work-items",
|
|
515
|
+
"name": "Work item",
|
|
516
|
+
"dataCategory": "entity",
|
|
517
|
+
"abstractTypes": ["WorkItem"],
|
|
518
|
+
"contentTypes": [
|
|
519
|
+
{
|
|
520
|
+
"id": "snapshots",
|
|
521
|
+
"profile": { "allowSnapshotBuckets": true },
|
|
522
|
+
"leading": true
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
"id": "raw",
|
|
526
|
+
"profile": { "allowSnapshotBuckets": false, "deletion": { "mode": "untracked" } }
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
"id": "analysis",
|
|
530
|
+
"profile": { "allowSnapshotBuckets": false },
|
|
531
|
+
"discriminator": { "path": "data.template", "required": true },
|
|
532
|
+
"writer": "memorix-skill-analysis",
|
|
533
|
+
"presentation": {
|
|
534
|
+
"label": "Analysis",
|
|
535
|
+
"tab": "whenPresent",
|
|
536
|
+
"values": [{ "value": "risk", "label": "Risk" }]
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
],
|
|
540
|
+
"fields": [
|
|
541
|
+
{ "path": "data.status", "valueType": "string", "contentType": "snapshots",
|
|
542
|
+
"enum": ["open", "in-progress", "done"] },
|
|
543
|
+
{ "path": "data.ownerId", "valueType": "recordId", "contentType": "snapshots",
|
|
544
|
+
"refersTo": { "objectType": "people" } }
|
|
545
|
+
]
|
|
546
|
+
}
|
|
547
|
+
],
|
|
548
|
+
"abstractTypes": [
|
|
549
|
+
{
|
|
550
|
+
"id": "WorkItem",
|
|
551
|
+
"name": "Work item",
|
|
552
|
+
"meaning": "A unit of work tracked by any connected system",
|
|
553
|
+
"category": "entity",
|
|
554
|
+
"properties": [
|
|
555
|
+
{ "id": "title", "valueType": "string", "required": true },
|
|
556
|
+
{ "id": "status", "valueType": "enum", "enum": ["open", "in-progress", "done"] }
|
|
557
|
+
],
|
|
558
|
+
"members": [
|
|
559
|
+
{
|
|
560
|
+
"objectType": "work-items",
|
|
561
|
+
"contentType": "snapshots",
|
|
562
|
+
"properties": [
|
|
563
|
+
{ "abstractProperty": "title", "contentType": "snapshots", "propertyPath": "concept.title" },
|
|
564
|
+
{ "abstractProperty": "status", "contentType": "snapshots", "propertyPath": "data.status",
|
|
565
|
+
"reverse": { "propertyPath": "data.status" } }
|
|
566
|
+
]
|
|
567
|
+
}
|
|
568
|
+
]
|
|
569
|
+
}
|
|
570
|
+
],
|
|
571
|
+
"mappings": [
|
|
572
|
+
{
|
|
573
|
+
"id": "opx-source-work-items-to-raw",
|
|
574
|
+
"purpose": "source",
|
|
575
|
+
"sourceId": "opx-source",
|
|
576
|
+
"endpoint": "/work-items",
|
|
577
|
+
"objectType": "work-items",
|
|
578
|
+
"contentType": "raw",
|
|
579
|
+
"mode": "knowledge",
|
|
580
|
+
"map": { "data": { "op": "path", "path": "$" } }
|
|
581
|
+
},
|
|
582
|
+
{
|
|
583
|
+
"id": "opx-work-items-abstract",
|
|
584
|
+
"purpose": "abstract",
|
|
585
|
+
"abstractType": "WorkItem",
|
|
586
|
+
"objectType": "work-items",
|
|
587
|
+
"contentType": "snapshots",
|
|
588
|
+
"map": {
|
|
589
|
+
"title": { "expr": { "op": "path", "path": "concept.title" }, "required": true },
|
|
590
|
+
"status": {
|
|
591
|
+
"op": "enum",
|
|
592
|
+
"expr": { "op": "path", "path": "data.state" },
|
|
593
|
+
"map": { "notStarted": "open", "active": "in-progress", "closed": "done" }
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
],
|
|
598
|
+
"relationshipDefinitions": [],
|
|
599
|
+
"views": [], "writes": [], "sources": [], "services": [], "pipelines": [], "schedules": []
|
|
600
|
+
}
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
A stored record for that OT/CT, in collection `work-items-snapshots` of database `{orgId}-memorix-entity`:
|
|
604
|
+
|
|
605
|
+
```json
|
|
606
|
+
{
|
|
607
|
+
"format": "memorix-record/2",
|
|
608
|
+
"recordId": "opx-source/workItemId/WI-100",
|
|
609
|
+
"revision": 3,
|
|
610
|
+
"objectType": "work-items",
|
|
611
|
+
"contentType": "snapshots",
|
|
612
|
+
"dataCategory": "entity",
|
|
613
|
+
"concept": {
|
|
614
|
+
"title": "Prepare quarterly operations review",
|
|
615
|
+
"identifiers": { "primary": { "kind": "workItemId", "value": "WI-100", "issuer": "opx-source" } }
|
|
616
|
+
},
|
|
617
|
+
"data": { "status": "in-progress", "ownerId": "hr-source/employeeId/E-7" },
|
|
618
|
+
"insights": { "riskScore": 0.42 },
|
|
619
|
+
"_system": {
|
|
620
|
+
"state": "active",
|
|
621
|
+
"createdAt": "2026-01-10T10:00:00.000Z",
|
|
622
|
+
"modifiedAt": "2026-01-15T12:00:00.000Z"
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
`insights` is allowed here only because the `snapshots` descriptor sets `allowSnapshotBuckets: true`. The same bucket on an `analysis` row would be rejected.
|
|
628
|
+
|
|
629
|
+
---
|
|
630
|
+
|
|
631
|
+
## 10. Error codes you will meet
|
|
632
|
+
|
|
633
|
+
| Code | Where | Cause |
|
|
634
|
+
|---|---|---|
|
|
635
|
+
| `ROUTE_UNKNOWN_OBJECT_TYPE` | write / read | OT not in the caller's effective metadata. |
|
|
636
|
+
| `ROUTE_UNKNOWN_CONTENT_TYPE` | write / read | CT not declared on that OT. |
|
|
637
|
+
| `ROUTE_UNSAFE_OBJECT_TYPE` / `ROUTE_UNSAFE_CONTENT_TYPE` | routing | Id fails `^[a-z][a-z0-9-]*$`. |
|
|
638
|
+
| `ROUTE_RESERVED_RELATIONSHIPS` | routing | CT named `relationships`. |
|
|
639
|
+
| `ROUTE_CATEGORY_MISMATCH` | routing | OT category is not `entity`, `event`, or `knowledge`, or differs from what was expected. |
|
|
640
|
+
| `METADATA_DESCRIPTOR_MISSING` | write (400) | CT has no `profile` in the effective catalog. Ensure the pack first. |
|
|
641
|
+
| `CONTENT_INVALID` | write (422) | `data` fails the CT `schema` for its discriminator value. |
|
|
642
|
+
| `WRITE_ENUM_INVALID` | write | Value outside a declared field `enum`. |
|
|
643
|
+
| `ADDRESS_OWNED` / `WRITER_NOT_OWNER` | write (409) | CT `owner` or `writer` check failed. |
|
|
644
|
+
| `V2-DISCRIMINATOR` / `V2-DISCRIMINATOR-DEFAULT` | record validation | Required discriminator missing / default applied. |
|
|
645
|
+
| `V2-DELETION` | record validation | `_system.deletion` present without `state: "deleted"`, or the reverse. |
|
|
646
|
+
| `META-REQUIRED` | pack validation | Descriptor (`profile`) or `allowSnapshotBuckets` missing, or another required key missing. |
|
|
647
|
+
| `META-CT-PROFILE` | pack validation | The same CT id declares `allowSnapshotBuckets` or `profile.deletion` differently on two OTs. |
|
|
648
|
+
| `META-LEADING` | pack validation | More than one `leading` CT on an OT. |
|
|
649
|
+
| `META-INTERFACE-CATEGORY` | pack validation | `implements` does not match `dataCategory`. |
|
|
650
|
+
| `META-BIND-PROP` / `META-BIND-REVERSE` | pack validation | Abstract property binding with no `propertyPath` or `mappingId`. |
|
|
651
|
+
| `META-MAP-PURPOSE` / `META-MAP-EXEC` | pack validation | Bad mapping purpose, or execution policy on a mapping. |
|
|
652
|
+
|
|
653
|
+
---
|
|
654
|
+
|
|
655
|
+
## Related documents
|
|
656
|
+
|
|
657
|
+
In `@x12i/memorix-format`:
|
|
658
|
+
|
|
659
|
+
- `docs/memorix-record-v2.md`: the full envelope contract and its locked decisions.
|
|
660
|
+
- `docs/migrate-present-to-v2.md`: migrating the older present envelope to `memorix-record/2`.
|
|
661
|
+
- `docs/memorix-workflow-format.md`: the `workflow` content type.
|
|
662
|
+
- `docs/memorix-format.md`, `docs/memorix-format-requirements.md`, `docs/association-format.md`: the older present envelope. Read them only to migrate existing data.
|
|
663
|
+
- `meta/`: envelope, open-bucket, and content-type vocabulary as JSON.
|
|
664
|
+
- `examples/v2/`: valid `memorix-record/2` documents.
|
|
665
|
+
|
|
666
|
+
In `@x12i/memorix-metadata`:
|
|
667
|
+
|
|
668
|
+
- `examples/opx/`, `examples/knowx/`, `examples/analytics/`, `examples/custom-kinds/`: valid packs.
|
|
669
|
+
- `examples/invalid/`: packs that fail validation, one rule each.
|
|
670
|
+
|
|
671
|
+
In `@x12i/memorix-mapping`:
|
|
672
|
+
|
|
673
|
+
- `examples/`: source, abstract, and completion mappings.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@x12i/memorix-metadata",
|
|
3
|
-
"version": "3.6.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "3.6.3",
|
|
4
|
+
"description": "Memorix object-type and content-type descriptors, metadata pack contracts, and validation. Depends only on @x12i/memorix-mapping.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -15,14 +15,16 @@
|
|
|
15
15
|
"dist",
|
|
16
16
|
"examples",
|
|
17
17
|
"docs",
|
|
18
|
-
"README.md"
|
|
18
|
+
"README.md",
|
|
19
|
+
"CHANGELOG.md"
|
|
19
20
|
],
|
|
20
21
|
"scripts": {
|
|
21
22
|
"prebuild": "rm -rf dist",
|
|
22
23
|
"build": "tsc -p tsconfig.build.json",
|
|
23
24
|
"test": "vitest run",
|
|
24
25
|
"test:watch": "vitest",
|
|
25
|
-
"typecheck": "tsc --noEmit"
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
27
|
+
"prepack": "node ../scripts/sync-format-reference.mjs && npm run build"
|
|
26
28
|
},
|
|
27
29
|
"engines": {
|
|
28
30
|
"node": ">=18.0.0"
|