@ordinatio/entities 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ordinatio
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,328 @@
1
+ # @ordinatio/entities
2
+
3
+ Entity knowledge, agent intelligence, notes, and contacts — the knowledge layer for any Ordinatio application.
4
+
5
+ ## What's in the box
6
+
7
+ | Module | Models | Purpose |
8
+ |--------|--------|---------|
9
+ | **Knowledge** | `EntityFieldDefinition`, `KnowledgeLedgerEntry`, `SearchQueryLog`, `SearchPattern` | Structured entity knowledge with immutable append-only ledger |
10
+ | **Knowledge+** | Time-travel, truth scoring, decay, branching, shadow graph, reflection, observer, ghost fields, health | Active knowledge reasoning — the module *thinks about what it knows* |
11
+ | **Agent** | `AgentInteraction`, `AgentSuggestion`, `AgentKnowledge`, `AgentPreference` | Agent intelligence: knowledge base, interaction analytics, proactive suggestions |
12
+ | **Notes** | `Note`, `NoteAttachment` | Entity-agnostic notes with attachments |
13
+ | **Contacts** | `Contact`, `ContactTag` | Contact CRUD with find-or-create for email sync |
14
+
15
+ 12 Prisma models, 1 enum, 45 error codes, 409 tests.
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install @ordinatio/entities
21
+ ```
22
+
23
+ Or via Domus:
24
+
25
+ ```bash
26
+ npx ordinatio add entities
27
+ ```
28
+
29
+ ## Quick Start
30
+
31
+ ### Standalone
32
+
33
+ ```typescript
34
+ import { PrismaClient } from '@prisma/client';
35
+ import {
36
+ getFieldDefinitions,
37
+ createFieldDefinition,
38
+ setEntityFields,
39
+ getEntityFields,
40
+ createNote,
41
+ getAllContacts,
42
+ createContact,
43
+ logInteraction,
44
+ queryKnowledge,
45
+ } from '@ordinatio/entities';
46
+
47
+ const db = new PrismaClient();
48
+
49
+ // --- Entity Knowledge ---
50
+ // Define what fields an entity type can have
51
+ await createFieldDefinition(db, {
52
+ entityType: 'client',
53
+ key: 'preferred_fabric',
54
+ label: 'Preferred Fabric',
55
+ dataType: 'text',
56
+ category: 'preferences',
57
+ });
58
+
59
+ // Set field values (immutable ledger — old values are superseded, never deleted)
60
+ await setEntityFields(db, 'client', 'client-123', {
61
+ preferred_fabric: 'Loro Piana cashmere',
62
+ budget_range: '$5000-8000',
63
+ }, 'agent', 'interaction-456');
64
+
65
+ // Read current values
66
+ const fields = await getEntityFields(db, 'client', 'client-123');
67
+
68
+ // --- Notes ---
69
+ await createNote(db, {
70
+ entityType: 'client',
71
+ entityId: 'client-123',
72
+ content: 'Prefers navy blue suits for business meetings.',
73
+ source: 'AGENT',
74
+ });
75
+
76
+ // --- Contacts ---
77
+ const contact = await createContact(db, {
78
+ email: 'john@example.com',
79
+ name: 'John Doe',
80
+ source: 'MANUAL',
81
+ });
82
+
83
+ // --- Agent Intelligence ---
84
+ await logInteraction(db, {
85
+ userId: 'user-1',
86
+ query: 'Show me overdue orders',
87
+ toolsUsed: ['listOrders', 'getOverdueTasks'],
88
+ });
89
+
90
+ const knowledge = await queryKnowledge(db, {
91
+ entity: 'fabric',
92
+ field: 'type',
93
+ });
94
+ ```
95
+
96
+ ### With Domus
97
+
98
+ ```typescript
99
+ import { createDomus } from '@ordinatio/domus';
100
+
101
+ const app = await createDomus({
102
+ modules: ['entities'],
103
+ });
104
+
105
+ const fields = await app.entities.getEntityFields('client', 'client-123');
106
+ await app.entities.createNote({ entityType: 'client', entityId: 'client-123', content: '...' });
107
+ ```
108
+
109
+ ## API Reference
110
+
111
+ ### Knowledge Module
112
+
113
+ | Function | Type | Description |
114
+ |----------|------|-------------|
115
+ | `getFieldDefinitions(db, entityType?, status?)` | Query | List field definitions |
116
+ | `getFieldDefinitionById(db, id)` | Query | Get a single field definition |
117
+ | `createFieldDefinition(db, input, callbacks?)` | Mutation | Create a field definition |
118
+ | `updateFieldDefinition(db, id, input, callbacks?)` | Mutation | Update a field definition |
119
+ | `getEntityFields(db, entityType, entityId)` | Query | Get current field values for an entity |
120
+ | `setEntityFields(db, entityType, entityId, fields, source, sourceId?, confidence?, setBy?, callbacks?)` | Mutation | Set field values (append-only ledger) |
121
+ | `getFieldHistory(db, entityType, entityId, fieldId?, limit?)` | Query | Get historical field values |
122
+ | `searchByFields(db, entityType, filters, limit?)` | Query | Search entities by field values |
123
+ | `logSearchQuery(db, input)` | Mutation | Log a search query (best-effort) |
124
+ | `getWeekOfYear(date)` | Pure | Calculate ISO week number |
125
+
126
+ ### Knowledge+ (Active Reasoning)
127
+
128
+ | Function | Type | Description |
129
+ |----------|------|-------------|
130
+ | `getKnowledgeAt(db, entityType, entityId, timestamp)` | Query | Reconstruct entity state at a point in time |
131
+ | `getFieldValueAt(db, entityType, entityId, fieldKey, timestamp)` | Query | Single field value at a point in time |
132
+ | `getKnowledgeTimeline(db, entityType, entityId, from, to, limit?)` | Query | Timeline of all changes in a date range |
133
+ | `computeTruthScore(assertions, now?)` | Pure | Weighted truth score: T = Σ(C×R×T) / Σ(C×R) |
134
+ | `computeRecencyFactor(createdAt, now?, maxAgeDays?)` | Pure | Exponential recency decay (1.0 → 0) |
135
+ | `computeComplexityScore(fieldsUsed, fieldsAvailable, categoriesUsed, maxCategories)` | Pure | Entity knowledge completeness score |
136
+ | `computeEntityTruthScores(db, entityType, entityId)` | Query | Truth scores for all fields on an entity |
137
+ | `computeDecayedConfidence(confidence, createdAt, halfLifeDays, now?)` | Pure | Exponential confidence decay: C × 0.5^(age/halfLife) |
138
+ | `isStale(confidence, createdAt, halfLifeDays, threshold?, now?)` | Pure | Check if decayed confidence is below threshold |
139
+ | `getStaleFields(db, entityType, entityId, threshold?)` | Query | All stale fields for an entity |
140
+ | `formatStalenessWarnings(staleFields)` | Pure | Agent-friendly staleness warning strings |
141
+ | `shouldBranch(newConfidence, existingConfidence?, threshold?)` | Pure | Should this write trigger validation? |
142
+ | `setEntityFieldsWithBranching(db, entityType, entityId, fields, source, confidence, options?)` | Mutation | Set fields with human-in-the-loop validation |
143
+ | `findRelatedEntities(db, entityType, entityId, options?)` | Query | Discover implicit relationships via shared field values |
144
+ | `getEntityRelationships(db, entityType, entityId, limit?)` | Query | Get all relationships for an entity |
145
+ | `computeRelationshipStrength(sharedFields, totalFieldsOnSource)` | Pure | Relationship strength from shared fields |
146
+ | `detectConflicts(db, entityType, entityId, rules?)` | Query | Check entity for contradictions |
147
+ | `scanForConflicts(db, entityType, rules?, limit?)` | Query | Batch contradiction scan across all entities |
148
+ | `evaluateConflictRule(rule, fields)` | Pure | Evaluate a single conflict rule |
149
+ | `evaluateConstraint(constraint, entityFields)` | Pure | Evaluate a single field constraint |
150
+ | `checkConstraints(db, entityType, entityId)` | Query | Check all constraints for an entity |
151
+ | `fireObservers(db, entityType, entityId, writtenFields, callbacks?)` | Mutation | Fire constraint observers after writes |
152
+ | `predictGhostFields(db, entityType, entityId, options?)` | Query | Predict missing fields from co-occurrence |
153
+ | `writeGhostFields(db, entityType, entityId, predictions, callbacks?)` | Mutation | Write predicted values with low confidence |
154
+ | `buildCoOccurrenceMap(db, entityType, fieldKeyA, fieldKeyB, minOccurrences?)` | Query | Co-occurrence statistics between two fields |
155
+ | `computeOverallScore(completeness, freshness, conflictRate, truthAverage)` | Pure | Weighted composite health score |
156
+ | `computeEntityHealth(db, entityType, entityId, conflictRules?)` | Query | Full health report for one entity |
157
+ | `getEntityTypeHealth(db, entityType, options?)` | Query | Aggregate health across all entities of a type |
158
+
159
+ ### Agent Module
160
+
161
+ | Function | Type | Description |
162
+ |----------|------|-------------|
163
+ | `queryKnowledge(db, input, seedProvider?)` | Query | Query agent knowledge base |
164
+ | `createKnowledgeEntry(db, input)` | Mutation | Add a knowledge entry |
165
+ | `updateKnowledgeEntry(db, id, input)` | Mutation | Update a knowledge entry |
166
+ | `deleteKnowledgeEntry(db, id)` | Mutation | Delete a knowledge entry |
167
+ | `ensureKnowledgeDefaults(db, seedProvider?)` | Mutation | Seed defaults (idempotent) |
168
+ | `resetKnowledgeDefaults(db, seedProvider?)` | Mutation | Reset to seed data |
169
+ | `getPreferences(db, input)` | Query | Get agent preferences |
170
+ | `setPreference(db, input)` | Mutation | Set a preference (upsert) |
171
+ | `deletePreference(db, id)` | Mutation | Delete a preference |
172
+ | `logInteraction(db, input)` | Mutation | Log an agent interaction |
173
+ | `markSatisfied(db, id, satisfied)` | Mutation | Mark interaction satisfaction |
174
+ | `getTopicDistribution(db, days?)` | Query | Topic distribution stats |
175
+ | `getRecentInteractions(db, userId, limit?)` | Query | Recent interactions |
176
+ | `getInteractionCount(db, days?)` | Query | Interaction count |
177
+ | `classifyIntent(query)` | Pure | Classify query intent |
178
+ | `extractTopic(query)` | Pure | Extract topic from query |
179
+ | `extractModules(toolNames)` | Pure | Map tools to modules |
180
+ | `analyzeAndSuggest(db)` | Mutation | Generate suggestions from patterns |
181
+ | `getSuggestions(db, status?)` | Query | Get suggestions |
182
+ | `dismissSuggestion(db, id, userId)` | Mutation | Dismiss a suggestion |
183
+ | `approveSuggestion(db, id)` | Mutation | Approve a suggestion |
184
+
185
+ ### Notes Module
186
+
187
+ | Function | Type | Description |
188
+ |----------|------|-------------|
189
+ | `createNote(db, input, callbacks?)` | Mutation | Create a note with optional attachments |
190
+ | `updateNote(db, noteId, entityId, input, callbacks?)` | Mutation | Update a note |
191
+ | `getNotes(db, options)` | Query | List notes with cursor pagination |
192
+ | `deleteNote(db, noteId, entityId)` | Mutation | Delete a note |
193
+
194
+ ### Contacts Module
195
+
196
+ | Function | Type | Description |
197
+ |----------|------|-------------|
198
+ | `getAllContacts(db, options?)` | Query | List contacts with filters |
199
+ | `getContactById(db, id)` | Query | Get a contact by ID |
200
+ | `getContactByEmail(db, email)` | Query | Find contact by email |
201
+ | `createContact(db, params, callbacks?)` | Mutation | Create a contact |
202
+ | `updateContact(db, id, data)` | Mutation | Update a contact |
203
+ | `deleteContact(db, id)` | Mutation | Delete a contact |
204
+ | `findOrCreateContact(db, email, name?, source?)` | Mutation | Find or create by email |
205
+
206
+ ## Callback Injection
207
+
208
+ Mutations accept optional callbacks for app-specific side effects:
209
+
210
+ ```typescript
211
+ import type { MutationCallbacks } from '@ordinatio/entities';
212
+
213
+ const callbacks: MutationCallbacks = {
214
+ logActivity: async (action, description, data) => {
215
+ // Wire to your activity/audit system
216
+ await myActivityService.log(action, description, data);
217
+ },
218
+ emitEvent: async (type, data) => {
219
+ // Wire to your event bus
220
+ await myEventBus.emit(type, data);
221
+ },
222
+ };
223
+
224
+ await createFieldDefinition(db, input, callbacks);
225
+ ```
226
+
227
+ Notes support extended callbacks for structured knowledge extraction:
228
+
229
+ ```typescript
230
+ import type { NoteKnowledgeCallbacks } from '@ordinatio/entities';
231
+
232
+ const noteCallbacks: NoteKnowledgeCallbacks = {
233
+ logActivity: async (action, description) => { /* ... */ },
234
+ setEntityFields: async (entityType, entityId, fields, source) => {
235
+ // Auto-extract structured knowledge from notes
236
+ await setEntityFields(db, entityType, entityId, fields, source);
237
+ },
238
+ };
239
+
240
+ await createNote(db, { entityType: 'client', entityId: 'c1', content: '...' }, noteCallbacks);
241
+ ```
242
+
243
+ ## Error Codes
244
+
245
+ ### KNOWLEDGE_300–361 (Entity Knowledge)
246
+
247
+ | Code | Description |
248
+ |------|-------------|
249
+ | `KNOWLEDGE_300` | Failed to list field definitions |
250
+ | `KNOWLEDGE_301` | Failed to create field definition |
251
+ | `KNOWLEDGE_302` | Failed to update field definition |
252
+ | `KNOWLEDGE_303` | Field definition not found |
253
+ | `KNOWLEDGE_304` | Failed to deactivate field definition |
254
+ | `KNOWLEDGE_305` | Failed to search by field values |
255
+ | `KNOWLEDGE_310` | Failed to get entity fields |
256
+ | `KNOWLEDGE_311` | Failed to set entity fields |
257
+ | `KNOWLEDGE_312` | Failed to get field history |
258
+ | `KNOWLEDGE_313` | Unknown field key |
259
+ | `KNOWLEDGE_314` | Failed to supersede old ledger entry |
260
+ | `KNOWLEDGE_320` | Failed to log search query |
261
+ | `KNOWLEDGE_321` | Failed to process search query log |
262
+ | `KNOWLEDGE_330` | Knowledge batch job failed |
263
+ | `KNOWLEDGE_331` | Failed to extract fields from notes |
264
+ | `KNOWLEDGE_332` | Failed to build temporal patterns |
265
+ | `KNOWLEDGE_340` | Failed to reconstruct knowledge at point in time |
266
+ | `KNOWLEDGE_341` | Invalid date range for timeline query |
267
+ | `KNOWLEDGE_342` | Failed to compute stale fields |
268
+ | `KNOWLEDGE_343` | Failed to write fields with branching |
269
+ | `KNOWLEDGE_344` | Validation callback failed during branching |
270
+ | `KNOWLEDGE_345` | Failed to discover entity relationships |
271
+ | `KNOWLEDGE_346` | Failed to detect contradictions |
272
+ | `KNOWLEDGE_347` | Failed to scan entities for contradictions |
273
+ | `KNOWLEDGE_348` | Failed to compute truth scores |
274
+ | `KNOWLEDGE_350` | Failed to evaluate field constraints |
275
+ | `KNOWLEDGE_351` | Observer callback failed |
276
+ | `KNOWLEDGE_352` | Invalid constraint definition |
277
+ | `KNOWLEDGE_355` | Failed to predict ghost fields |
278
+ | `KNOWLEDGE_356` | Failed to write ghost fields |
279
+ | `KNOWLEDGE_360` | Failed to compute entity health |
280
+ | `KNOWLEDGE_361` | Failed to compute entity type health |
281
+
282
+ ### AGENTKNOW_400–412 (Agent Intelligence)
283
+
284
+ | Code | Description |
285
+ |------|-------------|
286
+ | `AGENTKNOW_400` | Failed to query knowledge |
287
+ | `AGENTKNOW_401` | Knowledge entry not found |
288
+ | `AGENTKNOW_402` | Failed to create knowledge entry |
289
+ | `AGENTKNOW_403` | Failed to update knowledge entry |
290
+ | `AGENTKNOW_404` | Failed to delete knowledge entry |
291
+ | `AGENTKNOW_405` | Failed to reset knowledge defaults |
292
+ | `AGENTKNOW_406` | Failed to get preferences |
293
+ | `AGENTKNOW_407` | Failed to set preference |
294
+ | `AGENTKNOW_408` | Failed to delete preference |
295
+ | `AGENTKNOW_409` | Failed to log interaction |
296
+ | `AGENTKNOW_410` | Failed to get suggestions |
297
+ | `AGENTKNOW_411` | Failed to dismiss suggestion |
298
+ | `AGENTKNOW_412` | Failed to approve suggestion |
299
+
300
+ ## Prisma Schema
301
+
302
+ The standalone schema fragment is at `packages/ordinatio-domus/src/schema/entities.prisma` (12 models, 1 enum). Cross-domain foreign keys (to User, Client, Tag, EmailMessage) become plain `String?` for standalone use.
303
+
304
+ ## Tests
305
+
306
+ ```bash
307
+ pnpm --filter @ordinatio/entities test:run
308
+ # 409 tests across 20 files
309
+ ```
310
+
311
+ ## Pugil Integration
312
+
313
+ This package includes a Pugil reporter that generates Council-consumable `trial_report` artifacts from test results.
314
+
315
+ ```bash
316
+ # Normal test run (no Pugil overhead)
317
+ pnpm --filter @ordinatio/entities test:run
318
+
319
+ # With Pugil trial report generation
320
+ PUGIL_ENABLED=true pnpm --filter @ordinatio/entities test:run
321
+
322
+ # With Council cycle integration
323
+ PUGIL_ENABLED=true PUGIL_CYCLE_ID=cycle-entities-v1 pnpm --filter @ordinatio/entities test:run
324
+ ```
325
+
326
+ - **Config:** `src/pugil.config.ts` — maps test files to categories (unit, integration, adversarial, chaos, concurrency)
327
+ - **Reporter:** `src/pugil-reporter.ts` — Vitest custom reporter, writes to `pugil-reports/`
328
+ - **Types:** `PugilTestResult`, `PugilTestCategory` from `@ordinatio/core`