@siduri-x/core 2.0.4 → 2.0.5

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/dist/siduri-db.js CHANGED
@@ -26,6 +26,7 @@ class SiduriDatabase {
26
26
  companion_id TEXT PRIMARY KEY,
27
27
  name TEXT NOT NULL,
28
28
  archetype TEXT,
29
+ role TEXT,
29
30
  origin TEXT,
30
31
  ethos TEXT,
31
32
  version TEXT NOT NULL,
@@ -215,6 +216,12 @@ class SiduriDatabase {
215
216
  catch {
216
217
  // Column already exists
217
218
  }
219
+ try {
220
+ this.db.exec("ALTER TABLE self_identity ADD COLUMN role TEXT");
221
+ }
222
+ catch {
223
+ // Column already exists
224
+ }
218
225
  }
219
226
  close() {
220
227
  this.db.close();
@@ -231,6 +238,7 @@ class SiduriDatabase {
231
238
  companionId: row.companion_id,
232
239
  name: row.name,
233
240
  archetype: row.archetype || undefined,
241
+ role: row.role || row.archetype || undefined,
234
242
  origin: row.origin || undefined,
235
243
  ethos: row.ethos || undefined,
236
244
  version: row.version,
@@ -238,18 +246,21 @@ class SiduriDatabase {
238
246
  };
239
247
  }
240
248
  setIdentity(identity) {
249
+ const role = identity.role || identity.archetype || null;
250
+ const archetype = identity.archetype || identity.role || null;
241
251
  const stmt = this.db.prepare(`
242
- INSERT INTO self_identity (companion_id, name, archetype, origin, ethos, version, updated_at)
243
- VALUES (?, ?, ?, ?, ?, ?, datetime('now'))
252
+ INSERT INTO self_identity (companion_id, name, archetype, role, origin, ethos, version, updated_at)
253
+ VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'))
244
254
  ON CONFLICT(companion_id) DO UPDATE SET
245
255
  name = excluded.name,
246
256
  archetype = excluded.archetype,
257
+ role = excluded.role,
247
258
  origin = excluded.origin,
248
259
  ethos = excluded.ethos,
249
260
  version = excluded.version,
250
261
  updated_at = datetime('now')
251
262
  `);
252
- stmt.run(identity.companionId, identity.name, identity.archetype || null, identity.origin || null, identity.ethos || null, identity.version);
263
+ stmt.run(identity.companionId, identity.name, archetype, role, identity.origin || null, identity.ethos || null, identity.version);
253
264
  }
254
265
  getPersonality(companionId) {
255
266
  const stmt = this.db.prepare('SELECT * FROM self_personality WHERE companion_id = ?');
@@ -311,6 +322,14 @@ class SiduriDatabase {
311
322
  const stmt = this.db.prepare(`
312
323
  INSERT INTO self_directives (id, companion_id, priority, directive, status, category, scope_actor, supersedes_id, created_at)
313
324
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
325
+ ON CONFLICT(id) DO UPDATE SET
326
+ companion_id = excluded.companion_id,
327
+ priority = excluded.priority,
328
+ directive = excluded.directive,
329
+ status = excluded.status,
330
+ category = excluded.category,
331
+ scope_actor = excluded.scope_actor,
332
+ supersedes_id = excluded.supersedes_id
314
333
  `);
315
334
  stmt.run(directive.id, directive.companionId, directive.priority !== undefined ? directive.priority : 50, directive.directive, normalizeStatus(directive.status, 'active'), directive.category || 'behavioral', directive.scopeActor || null, directive.supersedesId || null, directive.createdAt || new Date().toISOString());
316
335
  }
@@ -332,6 +351,9 @@ class SiduriDatabase {
332
351
  return;
333
352
  }
334
353
  if (normalizeStatus(row.status) !== 'pending') {
354
+ if (normalizeStatus(row.status) === 'active') {
355
+ return;
356
+ }
335
357
  throw new Error(`Cannot approve directive '${id}': invalid transition from status '${row.status}' to 'active' (only pending directives can be approved)`);
336
358
  }
337
359
  // If this directive supersedes an earlier directive, transition that prior directive to superseded
@@ -407,8 +429,10 @@ class SiduriDatabase {
407
429
  }
408
430
  }
409
431
  getRelationship(companionId, entityId) {
410
- const stmt = this.db.prepare('SELECT * FROM self_relationships WHERE companion_id = ? AND entity_id = ?');
411
- const row = stmt.get(companionId, entityId);
432
+ const stripped = entityId.startsWith('actor:') ? entityId.slice(6) : entityId;
433
+ const prefixed = entityId.startsWith('actor:') ? entityId : `actor:${entityId}`;
434
+ const stmt = this.db.prepare('SELECT * FROM self_relationships WHERE companion_id = ? AND (entity_id = ? OR entity_id = ? OR entity_id = ?)');
435
+ const row = stmt.get(companionId, entityId, stripped, prefixed);
412
436
  if (!row)
413
437
  return undefined;
414
438
  return {
@@ -636,7 +660,7 @@ class SiduriDatabase {
636
660
  INSERT INTO memory_claims (id, companion_id, subject, predicate, value, status, confidence, valid_from, valid_until, evidence, asserted_at, supersedes, source_event_id)
637
661
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, coalesce(?, datetime('now')), ?, ?)
638
662
  `);
639
- stmt.run(id, claim.companionId, claim.subject, claim.predicate, claim.value, status, confidence, claim.validFrom || null, claim.validUntil || null, claim.evidence ? JSON.stringify(claim.evidence) : null, assertedAt || null, claim.supersedes || null, claim.sourceEventId || null);
663
+ stmt.run(id, claim.companionId || 'default', claim.subject, claim.predicate, claim.value, status, confidence, claim.validFrom || null, claim.validUntil || null, claim.evidence ? JSON.stringify(claim.evidence) : null, assertedAt || null, claim.supersedes || null, claim.sourceEventId || null);
640
664
  return {
641
665
  ...claim,
642
666
  id,
@@ -656,6 +680,9 @@ class SiduriDatabase {
656
680
  return;
657
681
  }
658
682
  if (normalizeStatus(row.status) !== 'pending') {
683
+ if (normalizeStatus(row.status) === 'approved') {
684
+ return;
685
+ }
659
686
  throw new Error(`Cannot approve claim '${id}': invalid transition from status '${row.status}' to 'approved' (only pending claims can be approved)`);
660
687
  }
661
688
  // If this claim supersedes an earlier claim, transition that prior claim to superseded
@@ -678,6 +705,94 @@ class SiduriDatabase {
678
705
  const stmt = this.db.prepare("UPDATE memory_claims SET status = 'approved' WHERE id = ? AND LOWER(status) = 'pending'");
679
706
  stmt.run(id);
680
707
  }
708
+ // Canonically promote approved claim to Self domain state (identity, role, relationships)
709
+ const approvedClaim = this.getClaim(id);
710
+ if (approvedClaim) {
711
+ this.promoteClaimToSelf(approvedClaim);
712
+ }
713
+ }
714
+ /**
715
+ * Canonically promotes a Claim into Self domain tables.
716
+ */
717
+ promoteClaimToSelf(claim) {
718
+ const companionId = claim.companionId || 'default';
719
+ const subject = (claim.subject || '').toLowerCase();
720
+ const predicate = (claim.predicate || '').toLowerCase();
721
+ const value = claim.value || '';
722
+ if (!value)
723
+ return;
724
+ // 1. Identity mutations: companion identity/role/origin/name/ethos
725
+ if (subject.startsWith('companion:') ||
726
+ subject === 'companion' ||
727
+ subject === 'siduri' ||
728
+ subject === 'self') {
729
+ const existing = this.getIdentity(companionId) || {
730
+ companionId,
731
+ name: 'Siduri',
732
+ version: '1.0.0',
733
+ updatedAt: new Date().toISOString(),
734
+ };
735
+ if (predicate === 'role' || predicate === 'archetype') {
736
+ existing.archetype = value;
737
+ existing.role = value;
738
+ this.setIdentity(existing);
739
+ this.commitDirective({
740
+ id: `dir-role-${claim.id || Date.now()}`,
741
+ companionId,
742
+ priority: 70,
743
+ directive: `Acknowledge role as ${value}`,
744
+ status: 'active',
745
+ category: 'relational',
746
+ createdAt: new Date().toISOString(),
747
+ });
748
+ }
749
+ else if (predicate === 'origin' || predicate === 'created_by') {
750
+ existing.origin = value;
751
+ this.setIdentity(existing);
752
+ }
753
+ else if (predicate === 'name') {
754
+ existing.name = value;
755
+ this.setIdentity(existing);
756
+ }
757
+ else if (predicate === 'ethos') {
758
+ existing.ethos = value;
759
+ this.setIdentity(existing);
760
+ }
761
+ return;
762
+ }
763
+ // 2. Relationship mutations: creator or user stated relationship
764
+ if (claim.claimType === 'relationship' ||
765
+ predicate === 'stated_relationship' ||
766
+ predicate === 'relationship' ||
767
+ predicate === 'relationship_to_siduri') {
768
+ const rawSubject = (claim.subject || 'actor:user').replace(/^actor:actor:/, 'actor:');
769
+ const isCreator = value.toLowerCase() === 'creator';
770
+ this.upsertRelationship({
771
+ companionId,
772
+ entityId: rawSubject,
773
+ entityType: 'human',
774
+ role: value,
775
+ stance: isCreator ? 'familiar_loyal' : 'neutral',
776
+ trustScore: isCreator ? 1.0 : 0.8,
777
+ familiarity: isCreator ? 0.9 : 0.5,
778
+ interactionConventions: isCreator
779
+ ? ['Direct communication', 'Highest administrative trust']
780
+ : [],
781
+ });
782
+ return;
783
+ }
784
+ // 3. Behavioral rule claim
785
+ if (predicate === 'behavioral_rule' || predicate === 'rule') {
786
+ this.commitDirective({
787
+ id: `dir-rule-${claim.id || Date.now()}`,
788
+ companionId,
789
+ priority: 60,
790
+ directive: value,
791
+ status: 'active',
792
+ category: 'behavioral',
793
+ createdAt: new Date().toISOString(),
794
+ });
795
+ }
681
796
  }
682
797
  rejectClaim(id, companionId) {
683
798
  if (companionId) {
@@ -730,7 +730,8 @@ describe('SiduriDatabase', () => {
730
730
  status: 'active',
731
731
  category: 'behavioral',
732
732
  });
733
- expect(() => db.approveDirective('dir-active-1')).toThrow(/invalid transition from status 'active' to 'active'/i);
733
+ // Approving an already-active directive is now idempotent (no-op, does not throw)
734
+ expect(() => db.approveDirective('dir-active-1')).not.toThrow();
734
735
  // 3. Rejecting an already ACTIVE directive throws
735
736
  expect(() => db.rejectDirective('dir-active-1')).toThrow(/invalid transition from status 'active' to 'rejected'/i);
736
737
  });
package/dist/teaching.js CHANGED
@@ -20,12 +20,20 @@ function extractDeterministicTeaching(message, context, sourceEventId) {
20
20
  if (!text) {
21
21
  return { claims, behaviorProposals };
22
22
  }
23
- const actorId = context?.actor?.actorId;
24
- const actorSubject = actorId ? `actor:${actorId}` : 'actor:anonymous';
23
+ const rawActorId = context?.actor?.actorId;
24
+ const cleanActorId = rawActorId ? rawActorId.replace(/^actor:/, '') : undefined;
25
+ const actorSubject = cleanActorId ? `actor:${cleanActorId}` : 'actor:anonymous';
25
26
  const companionId = context?.companionId || 'default';
26
27
  const sensitivity = context?.conversation?.channel === 'public' ? 'public' : 'private';
28
+ // Strip leading companion addressing: "Siduri, you are...", "Hey Siduri: ..."
29
+ const cleanText = text.replace(/^(?:(?:hey|hi|hello)\s+)?(?:siduri|companion|assistant)\s*[,:]\s*/i, '');
30
+ const isTeachMode = context?.mode === 'teach' || /^(?:!teach|\/teach|\bteach mode\b|\blearn this rule\b)/i.test(text);
31
+ const hasExplicitTeachingCue = isTeachMode ||
32
+ /\b(?:remember that|remember this rule|remember this|from now on|learn this|rule:)\b/i.test(text);
33
+ // Casual banter / hedged opinion filter to avoid mutating Self from generic conversation
34
+ const isCasualBanter = /\b(?:probably|maybe|such a|so |very |really |just |not |the funniest|the best|the worst|the coolest|great|awesome|funny|nice|kind|crazy|wrong|right|silly)\b/i;
27
35
  // 1. Companion's Name: "your name is X" / "you are called X"
28
- const companionNameMatch = text.match(/\b(?:your name is|you are called)\s+(.+?)(?=\s+and\s+(?:i\b|my\b|you\b)|[.;,]|$)/i);
36
+ const companionNameMatch = cleanText.match(/\b(?:your name is|you are called)\s+(.+?)(?=\s+and\s+(?:i\b|my\b|you\b)|[.;,]|$)/i);
29
37
  if (companionNameMatch) {
30
38
  const name = cleanValue(companionNameMatch[1], 80);
31
39
  claims.push({
@@ -45,12 +53,124 @@ function extractDeterministicTeaching(message, context, sourceEventId) {
45
53
  predicate: 'name',
46
54
  value: name,
47
55
  memoryClass: 'identity',
56
+ category: 'relational',
48
57
  sourceEventId,
49
58
  });
50
59
  }
51
- // 2. Actor's Name: "my name is X"
52
- const myNameMatch = text.match(/\bmy name is\s+(.+?)(?=\s+and\s+(?:i\b|my\b|you\b)|[.;,]|$)/i);
53
- if (myNameMatch && !/\b(?:private|public|everywhere)\b/i.test(text)) {
60
+ // 2. Identity / Role Teaching:
61
+ // "your role is X", "you work at X", "you were created by X", "you are from X",
62
+ // or explicit "you are [an/a] X [at Y]" when in teach mode or with explicit teaching cues
63
+ const roleMatch = cleanText.match(/\byour role is\s+(.+?)(?=\s+and\s+(?:i\b|my\b|you\b)|[.;,]|$)/i);
64
+ const workMatch = cleanText.match(/\byou work (?:at|for)\s+(.+?)(?=\s+and\s+(?:i\b|my\b|you\b)|[.;,]|$)/i);
65
+ const createdByMatch = cleanText.match(/\byou (?:were|are) created by\s+(.+?)(?=\s+and\s+(?:i\b|my\b|you\b)|[.;,]|$)/i);
66
+ const fromMatch = cleanText.match(/\byou are from\s+(.+?)(?=\s+and\s+(?:i\b|my\b|you\b)|[.;,]|$)/i);
67
+ // Match "you are [an/a/the] X [at Y]" only when in Teach mode or explicit teaching cues, and not casual banter
68
+ const youAreMatch = (isTeachMode || hasExplicitTeachingCue)
69
+ ? cleanText.match(/\b(?:remember that\s+)?you are\s+(?:(?:an?|the)\s+)?(.+?)(?=\s+and\s+(?:i\b|my\b|you\b)|[.;,]|$)/i)
70
+ : null;
71
+ const validYouAreRole = youAreMatch &&
72
+ !companionNameMatch &&
73
+ !fromMatch &&
74
+ !isCasualBanter.test(youAreMatch[1]) &&
75
+ !/^(?:called|named)\b/i.test(youAreMatch[1])
76
+ ? cleanValue(youAreMatch[1].replace(/^(?:an?|the)\s+/i, ''), 100)
77
+ : undefined;
78
+ const rawEffectiveRole = roleMatch ? cleanValue(roleMatch[1], 100) : validYouAreRole;
79
+ const effectiveRole = rawEffectiveRole ? rawEffectiveRole.replace(/^(?:an?|the)\s+/i, '') : undefined;
80
+ if (effectiveRole) {
81
+ claims.push({
82
+ subject: `companion:${companionId}`,
83
+ predicate: 'role',
84
+ value: effectiveRole,
85
+ content: `The companion's role is ${effectiveRole}.`,
86
+ claimType: 'semantic',
87
+ provenance: 'deterministic_teaching',
88
+ sensitivity: 'public',
89
+ sourceEventId,
90
+ });
91
+ behaviorProposals.push({
92
+ directive: `Acknowledge role as ${effectiveRole}`,
93
+ priority: 70,
94
+ subject: `companion:${companionId}`,
95
+ predicate: 'role',
96
+ value: effectiveRole,
97
+ memoryClass: 'identity',
98
+ category: 'relational',
99
+ sourceEventId,
100
+ });
101
+ }
102
+ if (workMatch) {
103
+ const workplace = cleanValue(workMatch[1], 80);
104
+ claims.push({
105
+ subject: `companion:${companionId}`,
106
+ predicate: 'origin',
107
+ value: workplace,
108
+ content: `The companion works at ${workplace}.`,
109
+ claimType: 'semantic',
110
+ provenance: 'deterministic_teaching',
111
+ sensitivity: 'public',
112
+ sourceEventId,
113
+ });
114
+ behaviorProposals.push({
115
+ directive: `Acknowledge workplace as ${workplace}`,
116
+ priority: 70,
117
+ subject: `companion:${companionId}`,
118
+ predicate: 'origin',
119
+ value: workplace,
120
+ memoryClass: 'identity',
121
+ category: 'relational',
122
+ sourceEventId,
123
+ });
124
+ }
125
+ if (createdByMatch) {
126
+ const creator = cleanValue(createdByMatch[1], 80);
127
+ claims.push({
128
+ subject: `companion:${companionId}`,
129
+ predicate: 'created_by',
130
+ value: creator,
131
+ content: `The companion was created by ${creator}.`,
132
+ claimType: 'semantic',
133
+ provenance: 'deterministic_teaching',
134
+ sensitivity: 'public',
135
+ sourceEventId,
136
+ });
137
+ behaviorProposals.push({
138
+ directive: `Acknowledge creator as ${creator}`,
139
+ priority: 75,
140
+ subject: `companion:${companionId}`,
141
+ predicate: 'created_by',
142
+ value: creator,
143
+ memoryClass: 'identity',
144
+ category: 'relational',
145
+ sourceEventId,
146
+ });
147
+ }
148
+ if (fromMatch) {
149
+ const origin = cleanValue(fromMatch[1], 80);
150
+ claims.push({
151
+ subject: `companion:${companionId}`,
152
+ predicate: 'origin',
153
+ value: origin,
154
+ content: `The companion is from ${origin}.`,
155
+ claimType: 'semantic',
156
+ provenance: 'deterministic_teaching',
157
+ sensitivity: 'public',
158
+ sourceEventId,
159
+ });
160
+ behaviorProposals.push({
161
+ directive: `Acknowledge origin as ${origin}`,
162
+ priority: 70,
163
+ subject: `companion:${companionId}`,
164
+ predicate: 'origin',
165
+ value: origin,
166
+ memoryClass: 'identity',
167
+ category: 'relational',
168
+ sourceEventId,
169
+ });
170
+ }
171
+ // 3. Actor's Name: "my name is X"
172
+ const myNameMatch = cleanText.match(/\bmy name is\s+(.+?)(?=\s+and\s+(?:i\b|my\b|you\b)|[.;,]|$)/i);
173
+ if (myNameMatch && !/\b(?:private|public|everywhere)\b/i.test(cleanText)) {
54
174
  const name = cleanValue(myNameMatch[1], 80);
55
175
  claims.push({
56
176
  subject: actorSubject,
@@ -63,8 +183,8 @@ function extractDeterministicTeaching(message, context, sourceEventId) {
63
183
  sourceEventId,
64
184
  });
65
185
  }
66
- // 3. Preferred Address / Call me X: "call me X"
67
- const callMeMatch = text.match(/\b(?:(?:from now on|only),?\s*)?call me\s+(.+?)(?:\s+(?:in private|privately|in public|publicly|everywhere|in direct conversations))?(?=\s+and\s+(?:i\b|my\b|you\b)|[.;,]|$)/i);
186
+ // 4. Preferred Address / Call me X: "call me X"
187
+ const callMeMatch = cleanText.match(/\b(?:(?:from now on|only),?\s*)?call me\s+(.+?)(?:\s+(?:in private|privately|in public|publicly|everywhere|in direct conversations))?(?=\s+and\s+(?:i\b|my\b|you\b)|[.;,]|$)/i);
68
188
  if (callMeMatch) {
69
189
  const address = cleanValue(callMeMatch[1], 80);
70
190
  const directiveInstruction = `Address ${actorSubject} as ${address}`;
@@ -85,13 +205,15 @@ function extractDeterministicTeaching(message, context, sourceEventId) {
85
205
  predicate: 'preferred_address',
86
206
  value: address,
87
207
  memoryClass: 'behavioral',
208
+ category: 'behavioral',
88
209
  sourceEventId,
89
210
  });
90
211
  }
91
- // 4. Stated relationship: "I am your X" / "I'm your creator"
92
- const relMatch = text.match(/\b(?:i am|i'm)\s+your\s+([A-Za-z0-9_\s-]+?)(?=\s+and\s+(?:i\b|my\b|you\b)|[.;,]|$)/i);
212
+ // 5. Stated relationship: "I am your creator, Kur Zagin" / "I am your X" / "I'm your creator"
213
+ const relMatch = cleanText.match(/\b(?:i am|i'm)\s+your\s+([A-Za-z0-9_-]+)(?:,\s*([A-Za-z0-9_\s-]+?))?(?=\s+and\s+(?:i\b|my\b|you\b)|[.;]|$)/i);
93
214
  if (relMatch) {
94
215
  const relationship = cleanValue(relMatch[1], 60);
216
+ const actorName = relMatch[2] ? cleanValue(relMatch[2], 80) : undefined;
95
217
  claims.push({
96
218
  subject: actorSubject,
97
219
  predicate: 'stated_relationship',
@@ -109,11 +231,78 @@ function extractDeterministicTeaching(message, context, sourceEventId) {
109
231
  predicate: 'stated_relationship',
110
232
  value: relationship,
111
233
  memoryClass: 'relationship',
234
+ category: 'relational',
235
+ sourceEventId,
236
+ });
237
+ if (actorName) {
238
+ claims.push({
239
+ subject: actorSubject,
240
+ predicate: 'name',
241
+ value: actorName,
242
+ content: `The actor's name is ${actorName}.`,
243
+ claimType: 'preference',
244
+ provenance: 'deterministic_teaching',
245
+ sensitivity,
246
+ sourceEventId,
247
+ });
248
+ }
249
+ }
250
+ // 6. Behavioral rule / directive: "Remember this rule: X" / "Remember that rule: X" / "Rule: X" / "From now on, always X"
251
+ const ruleMatch = cleanText.match(/\b(?:remember\s+(?:this|that)\s+rule:\s*|rule:\s*|(?:from now on,?\s+)?always\s+)(.+?)(?=\s+and\s+(?:i\b|my\b|you\b)|[.;]|$)/i);
252
+ if (ruleMatch) {
253
+ let instruction = cleanValue(ruleMatch[1], 160);
254
+ if (instruction) {
255
+ instruction = instruction.charAt(0).toUpperCase() + instruction.slice(1);
256
+ behaviorProposals.push({
257
+ directive: instruction,
258
+ priority: 60,
259
+ subject: actorSubject,
260
+ predicate: 'rule',
261
+ value: instruction,
262
+ category: 'behavioral',
263
+ memoryClass: 'behavioral',
264
+ sourceEventId,
265
+ });
266
+ claims.push({
267
+ subject: actorSubject,
268
+ predicate: 'behavioral_rule',
269
+ value: instruction,
270
+ content: `Behavioral rule: ${instruction}`,
271
+ claimType: 'preference',
272
+ provenance: 'deterministic_teaching',
273
+ sensitivity,
274
+ sourceEventId,
275
+ });
276
+ }
277
+ }
278
+ // Preference: "I prefer concise answers"
279
+ const prefAnswersMatch = cleanText.match(/\b(?:i prefer|my preference is)\s+(.+?)(?=\s+and\s+(?:i\b|my\b|you\b)|[.;,]|$)/i);
280
+ if (prefAnswersMatch && !cleanText.match(/\bmy\s+preferred\s+([A-Za-z0-9_]+)\s+is\b/i)) {
281
+ const prefVal = cleanValue(prefAnswersMatch[1], 100);
282
+ const directive = `Prefer ${prefVal}`;
283
+ behaviorProposals.push({
284
+ directive,
285
+ priority: 60,
286
+ subject: actorSubject,
287
+ predicate: 'preference',
288
+ value: prefVal,
289
+ category: 'behavioral',
290
+ memoryClass: 'behavioral',
291
+ sourceEventId,
292
+ });
293
+ claims.push({
294
+ subject: actorSubject,
295
+ predicate: 'preference',
296
+ value: prefVal,
297
+ content: `The actor prefers ${prefVal}.`,
298
+ claimType: 'preference',
299
+ provenance: 'deterministic_teaching',
300
+ sensitivity,
112
301
  sourceEventId,
113
302
  });
114
303
  }
115
- // 5. Explicit Domain / Preference fact: "my preferred X is Y" / "my X is Y"
116
- const prefMatch = text.match(/\bmy\s+preferred\s+([A-Za-z0-9_]+)\s+is\s+(.+?)(?=\s+and\s+(?:i\b|my\b|you\b)|[.;,]|$)/i);
304
+ // 7. Explicit Domain / Preference fact: "my preferred X is Y" / "my X is Y"
305
+ const prefMatch = cleanText.match(/\bmy\s+preferred\s+([A-Za-z0-9_]+)\s+is\s+(.+?)(?=\s+and\s+(?:i\b|my\b|you\b)|[.;,]|$)/i);
117
306
  if (prefMatch) {
118
307
  const predicate = cleanValue(prefMatch[1], 40);
119
308
  const val = cleanValue(prefMatch[2], 100);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siduri-x/core",
3
- "version": "2.0.4",
3
+ "version": "2.0.5",
4
4
  "description": "Core runtime types, evidence protocol, action dispatcher, capability validation, and SiduriRuntime protocol",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {