crbro-memory 2.9.1 → 2.10.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/README.md +22 -13
- package/SECURITY.md +27 -2
- package/bin/crbro.mjs +18 -1
- package/dist/engine/backup.d.ts.map +1 -1
- package/dist/engine/backup.js +4 -0
- package/dist/engine/backup.js.map +1 -1
- package/dist/engine/cortex.d.ts +18 -6
- package/dist/engine/cortex.d.ts.map +1 -1
- package/dist/engine/cortex.js +99 -83
- package/dist/engine/cortex.js.map +1 -1
- package/dist/engine/doctor.d.ts +93 -0
- package/dist/engine/doctor.d.ts.map +1 -0
- package/dist/engine/doctor.js +378 -0
- package/dist/engine/doctor.js.map +1 -0
- package/dist/engine/evidence.d.ts +26 -0
- package/dist/engine/evidence.d.ts.map +1 -0
- package/dist/engine/evidence.js +171 -0
- package/dist/engine/evidence.js.map +1 -0
- package/dist/engine/hippocampus.d.ts.map +1 -1
- package/dist/engine/hippocampus.js +32 -29
- package/dist/engine/hippocampus.js.map +1 -1
- package/dist/engine/keychain.d.ts.map +1 -1
- package/dist/engine/keychain.js +52 -20
- package/dist/engine/keychain.js.map +1 -1
- package/dist/engine/prefrontal.d.ts +4 -1
- package/dist/engine/prefrontal.d.ts.map +1 -1
- package/dist/engine/prefrontal.js +100 -66
- package/dist/engine/prefrontal.js.map +1 -1
- package/dist/engine/projects.d.ts +20 -0
- package/dist/engine/projects.d.ts.map +1 -0
- package/dist/engine/projects.js +80 -0
- package/dist/engine/projects.js.map +1 -0
- package/dist/engine/shelf.d.ts +5 -6
- package/dist/engine/shelf.d.ts.map +1 -1
- package/dist/engine/shelf.js +9 -8
- package/dist/engine/shelf.js.map +1 -1
- package/dist/search/index.d.ts +5 -0
- package/dist/search/index.d.ts.map +1 -1
- package/dist/search/index.js +18 -5
- package/dist/search/index.js.map +1 -1
- package/dist/search/quality.d.ts +18 -0
- package/dist/search/quality.d.ts.map +1 -0
- package/dist/search/quality.js +31 -0
- package/dist/search/quality.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +112 -16
- package/dist/server.js.map +1 -1
- package/dist/sync/materialize.d.ts +1 -0
- package/dist/sync/materialize.d.ts.map +1 -1
- package/dist/sync/materialize.js +33 -8
- package/dist/sync/materialize.js.map +1 -1
- package/dist/sync/ops.d.ts +7 -2
- package/dist/sync/ops.d.ts.map +1 -1
- package/dist/sync/ops.js.map +1 -1
- package/dist/sync/space.d.ts +5 -3
- package/dist/sync/space.d.ts.map +1 -1
- package/dist/sync/space.js +187 -77
- package/dist/sync/space.js.map +1 -1
- package/dist/types/index.d.ts +25 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/lock.d.ts +2 -0
- package/dist/utils/lock.d.ts.map +1 -1
- package/dist/utils/lock.js +132 -52
- package/dist/utils/lock.js.map +1 -1
- package/package.json +5 -5
package/dist/engine/cortex.js
CHANGED
|
@@ -12,8 +12,10 @@ const fs_js_1 = require("../utils/fs.js");
|
|
|
12
12
|
const ids_js_1 = require("../utils/ids.js");
|
|
13
13
|
const hash_js_1 = require("../utils/hash.js");
|
|
14
14
|
const ops_js_1 = require("../sync/ops.js");
|
|
15
|
+
const materialize_js_1 = require("../sync/materialize.js");
|
|
15
16
|
const secrets_js_1 = require("./secrets.js");
|
|
16
17
|
const shelf_js_1 = require("./shelf.js");
|
|
18
|
+
const evidence_js_1 = require("./evidence.js");
|
|
17
19
|
const TYPE_PREFIX_RE = /^(project_|tech_|lang_|person_|domain_|process_|protocol_)/;
|
|
18
20
|
/**
|
|
19
21
|
* Similarity above which a new fact is flagged as a near-duplicate of one
|
|
@@ -290,6 +292,10 @@ function earliestOf(a, b) {
|
|
|
290
292
|
}
|
|
291
293
|
function copyFact(f) {
|
|
292
294
|
const out = { ...f };
|
|
295
|
+
if (f.reference)
|
|
296
|
+
out.reference = { ...f.reference };
|
|
297
|
+
if (f.verification)
|
|
298
|
+
out.verification = { ...f.verification, ...(f.verification.reference ? { reference: { ...f.verification.reference } } : {}) };
|
|
293
299
|
if (f.keys)
|
|
294
300
|
out.keys = [...f.keys];
|
|
295
301
|
if (f.supersedes)
|
|
@@ -363,9 +369,7 @@ function unionNeuron(target, source) {
|
|
|
363
369
|
delete hit.keys;
|
|
364
370
|
// The later check is the newer evidence; the more volatile explicit class
|
|
365
371
|
// wins, as in a team space (a needless warning is cheaper than a missing one).
|
|
366
|
-
|
|
367
|
-
if (verificado)
|
|
368
|
-
hit.verified = verificado;
|
|
372
|
+
(0, evidence_js_1.mergeFactEvidence)(hit, sf);
|
|
369
373
|
const vida = (0, shelf_js_1.mostVolatile)(hit.shelf_life, sf.shelf_life);
|
|
370
374
|
if (vida)
|
|
371
375
|
hit.shelf_life = vida;
|
|
@@ -444,22 +448,10 @@ function unionNeuron(target, source) {
|
|
|
444
448
|
neuron.entry_status = estadosVivos;
|
|
445
449
|
else
|
|
446
450
|
delete neuron.entry_status;
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
verificadas[k] = l;
|
|
452
|
-
else if (!verificadas[k])
|
|
453
|
-
verificadas[k] = v;
|
|
454
|
-
}
|
|
455
|
-
const verificadasVivas = {};
|
|
456
|
-
for (const k of Object.keys(verificadas).sort())
|
|
457
|
-
if (vivos.has(k))
|
|
458
|
-
verificadasVivas[k] = verificadas[k];
|
|
459
|
-
if (Object.keys(verificadasVivas).length)
|
|
460
|
-
neuron.entry_verified = verificadasVivas;
|
|
461
|
-
else
|
|
462
|
-
delete neuron.entry_verified;
|
|
451
|
+
// Usar ambos pares originales: una fecha nueva y una fuente vieja no se mezclan.
|
|
452
|
+
neuron.entry_verified = target.entry_verified;
|
|
453
|
+
neuron.entry_verification = target.entry_verification;
|
|
454
|
+
(0, evidence_js_1.mergeEntryEvidence)(neuron, source, vivos);
|
|
463
455
|
const origenes = { ...(source.entry_source || {}), ...(target.entry_source || {}) };
|
|
464
456
|
const origenesVivos = {};
|
|
465
457
|
for (const k of Object.keys(origenes).sort())
|
|
@@ -578,33 +570,28 @@ class Cortex {
|
|
|
578
570
|
await this.emitter(neuronId, change);
|
|
579
571
|
}
|
|
580
572
|
catch {
|
|
581
|
-
//
|
|
582
|
-
//
|
|
573
|
+
// El emisor solo falla si no logró registrar la entrega persistente.
|
|
574
|
+
// La nota local sigue guardada, pero no se puede confirmar éxito total.
|
|
575
|
+
throw new Error('Local change saved, but shared delivery could not be recorded. Check local storage before retrying.');
|
|
583
576
|
}
|
|
584
577
|
}
|
|
585
578
|
/**
|
|
586
|
-
*
|
|
587
|
-
*
|
|
588
|
-
* contains everything local plus whatever arrived.
|
|
579
|
+
* Fusionar contra la versión leída dentro del mismo bloqueo de escritura.
|
|
580
|
+
* Un snapshot calculado antes del bloqueo puede borrar un learn o retiro.
|
|
589
581
|
*/
|
|
590
|
-
async
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
neuron.summary = current.summary || neuron.summary;
|
|
599
|
-
neuron.preferences = current.preferences;
|
|
600
|
-
neuron.connections = current.connections;
|
|
601
|
-
neuron.created = current.created;
|
|
602
|
-
}
|
|
582
|
+
async mergeFromSync(nid, ops) {
|
|
583
|
+
let report;
|
|
584
|
+
let created = false;
|
|
585
|
+
const saved = await (0, fs_js_1.updateJSON)(this.brain.paths.neuron(nid), current => {
|
|
586
|
+
created = current === null;
|
|
587
|
+
const merged = (0, materialize_js_1.applyOps)(current, ops, { id: nid });
|
|
588
|
+
report = merged.report;
|
|
589
|
+
const neuron = merged.neuron;
|
|
603
590
|
return neuron;
|
|
604
591
|
});
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
return
|
|
592
|
+
if (saved)
|
|
593
|
+
await this.reindex(saved);
|
|
594
|
+
return { report, created };
|
|
608
595
|
}
|
|
609
596
|
async reindex(neuron) {
|
|
610
597
|
if (!this.indexer)
|
|
@@ -739,6 +726,8 @@ class Cortex {
|
|
|
739
726
|
* Creates the neuron if it does not exist.
|
|
740
727
|
*/
|
|
741
728
|
async learn(topic, type, content, options) {
|
|
729
|
+
const reference = options?.reference === undefined ? undefined : (0, evidence_js_1.sanitizeReference)(options.reference);
|
|
730
|
+
const temporal = (0, evidence_js_1.sanitizeTemporal)(options?.temporal);
|
|
742
731
|
// Credentials never make it to disk. The sentence around them survives, so
|
|
743
732
|
// "the deploy token is [REDACTED: npm token]" still records that a token
|
|
744
733
|
// exists and what kind — the knowledge without the liability.
|
|
@@ -776,9 +765,7 @@ class Cortex {
|
|
|
776
765
|
let updatedInPlace = false;
|
|
777
766
|
let confirmations;
|
|
778
767
|
let shelf;
|
|
779
|
-
let reconfirmed = false;
|
|
780
768
|
let shelfLengthened = false;
|
|
781
|
-
let verificar = null;
|
|
782
769
|
const vidaPedida = (0, shelf_js_1.isShelfLife)(options?.shelfLife) ? options.shelfLife : undefined;
|
|
783
770
|
let skippedRetired = null;
|
|
784
771
|
// A retired decision, pattern, error or debt must not come back through
|
|
@@ -883,23 +870,21 @@ class Cortex {
|
|
|
883
870
|
cambiado = true;
|
|
884
871
|
}
|
|
885
872
|
}
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
verificar = { eid: existente.id || (0, hash_js_1.factId)(existente.text), at: existente.verified };
|
|
902
|
-
}
|
|
873
|
+
const referenceChanged = reference && JSON.stringify(reference) !== JSON.stringify(existente.reference);
|
|
874
|
+
const temporalChanged = temporal && temporal !== existente.temporal;
|
|
875
|
+
if (referenceChanged || temporalChanged) {
|
|
876
|
+
const previous = (0, evidence_js_1.mergeCheck)(existente, {});
|
|
877
|
+
existente.verification_invalidated_at = new Date(Math.max(Date.now(), Date.parse(previous.verified || '') || 0, Date.parse(previous.verification_invalidated_at || '') || 0)).toISOString();
|
|
878
|
+
delete existente.verified;
|
|
879
|
+
delete existente.verification;
|
|
880
|
+
}
|
|
881
|
+
if (referenceChanged) {
|
|
882
|
+
existente.reference = reference;
|
|
883
|
+
cambiado = true;
|
|
884
|
+
}
|
|
885
|
+
if (temporalChanged) {
|
|
886
|
+
existente.temporal = temporal;
|
|
887
|
+
cambiado = true;
|
|
903
888
|
}
|
|
904
889
|
if (cambiado) {
|
|
905
890
|
// Note for shared neurons: materialize takes max(conf) and
|
|
@@ -907,10 +892,17 @@ class Cortex {
|
|
|
907
892
|
// stays local. Documented, not fought — the log is append-only.
|
|
908
893
|
updatedInPlace = true;
|
|
909
894
|
this.tally.topics.add(n.id);
|
|
910
|
-
emitir = { kind: 'fact', text: existente.text, fid: existente.id || id,
|
|
911
|
-
conf: existente.confidence ?? 1, at: existente.added, src: existente.source, keys: existente.keys,
|
|
912
|
-
...(existente.shelf_life ? { shelf: existente.shelf_life } : {}) };
|
|
913
895
|
}
|
|
896
|
+
// Un intento anterior pudo guardar la neurona pero fallar antes
|
|
897
|
+
// de persistir su entrega. Reemitir permite recuperarla incluso
|
|
898
|
+
// tras reiniciar; applyOps deduplica por fid. No es verificación
|
|
899
|
+
// nueva y conserva la fecha original, no la del reintento.
|
|
900
|
+
emitir = { kind: 'fact', text: existente.text, fid: existente.id || id,
|
|
901
|
+
conf: existente.confidence ?? 1, at: existente.added, src: existente.source, keys: existente.keys,
|
|
902
|
+
...(existente.reference ? { reference: existente.reference } : {}),
|
|
903
|
+
...(existente.temporal ? { temporal: existente.temporal } : {}),
|
|
904
|
+
...(existente.verification_invalidated_at ? { verification_invalidated_at: existente.verification_invalidated_at } : {}),
|
|
905
|
+
...(existente.shelf_life ? { shelf: existente.shelf_life } : {}) };
|
|
914
906
|
const sv = (0, shelf_js_1.shelfOfFact)(existente);
|
|
915
907
|
shelf = { shelf_life: sv.shelf, inferred: sv.inferred, ...(sv.reason ? { reason: sv.reason } : {}) };
|
|
916
908
|
}
|
|
@@ -939,6 +931,10 @@ class Cortex {
|
|
|
939
931
|
fact.keys = keys;
|
|
940
932
|
if (vidaPedida)
|
|
941
933
|
fact.shelf_life = vidaPedida;
|
|
934
|
+
if (reference)
|
|
935
|
+
fact.reference = reference;
|
|
936
|
+
if (temporal)
|
|
937
|
+
fact.temporal = temporal;
|
|
942
938
|
const sv = (0, shelf_js_1.shelfOfFact)(fact);
|
|
943
939
|
shelf = { shelf_life: sv.shelf, inferred: sv.inferred, ...(sv.reason ? { reason: sv.reason } : {}) };
|
|
944
940
|
n.facts.push(fact);
|
|
@@ -948,6 +944,8 @@ class Cortex {
|
|
|
948
944
|
this.tally.topics.add(n.id);
|
|
949
945
|
emitir = { kind: 'fact', text: content, fid: id,
|
|
950
946
|
conf: fact.confidence, at: fact.added, src: fact.source, keys: fact.keys,
|
|
947
|
+
...(fact.reference ? { reference: fact.reference } : {}),
|
|
948
|
+
...(fact.temporal ? { temporal: fact.temporal } : {}),
|
|
951
949
|
...(fact.shelf_life ? { shelf: fact.shelf_life } : {}) };
|
|
952
950
|
}
|
|
953
951
|
break;
|
|
@@ -975,8 +973,8 @@ class Cortex {
|
|
|
975
973
|
n.patterns.push(content);
|
|
976
974
|
fechar(n, content);
|
|
977
975
|
origen(n, content);
|
|
978
|
-
emitir = { kind: 'pattern', text: content, at: (0, fs_js_1.now)() };
|
|
979
976
|
}
|
|
977
|
+
emitir = { kind: 'pattern', text: content, at: n.entry_dates?.[(0, ops_js_1.entryId)(content)] || (0, fs_js_1.now)() };
|
|
980
978
|
break;
|
|
981
979
|
}
|
|
982
980
|
case 'preference': {
|
|
@@ -996,8 +994,8 @@ class Cortex {
|
|
|
996
994
|
fechar(n, content);
|
|
997
995
|
origen(n, content);
|
|
998
996
|
this.tally.topics.add(n.id);
|
|
999
|
-
emitir = { kind: 'error', text: content, at: (0, fs_js_1.now)() };
|
|
1000
997
|
}
|
|
998
|
+
emitir = { kind: 'error', text: content, at: n.entry_dates?.[(0, ops_js_1.entryId)(content)] || (0, fs_js_1.now)() };
|
|
1001
999
|
break;
|
|
1002
1000
|
}
|
|
1003
1001
|
case 'debt': {
|
|
@@ -1010,8 +1008,8 @@ class Cortex {
|
|
|
1010
1008
|
fechar(n, content);
|
|
1011
1009
|
origen(n, content);
|
|
1012
1010
|
this.tally.topics.add(n.id);
|
|
1013
|
-
emitir = { kind: 'debt', text: content, at: (0, fs_js_1.now)() };
|
|
1014
1011
|
}
|
|
1012
|
+
emitir = { kind: 'debt', text: content, at: n.entry_dates?.[(0, ops_js_1.entryId)(content)] || (0, fs_js_1.now)() };
|
|
1015
1013
|
break;
|
|
1016
1014
|
}
|
|
1017
1015
|
}
|
|
@@ -1036,17 +1034,12 @@ class Cortex {
|
|
|
1036
1034
|
// key and the least likely to be worth sharing.
|
|
1037
1035
|
if (emitir)
|
|
1038
1036
|
await this.emit(final.id, emitir);
|
|
1039
|
-
// A reconfirming re-learn reaches the team as a check: the latest wins.
|
|
1040
|
-
const v = verificar;
|
|
1041
|
-
if (v)
|
|
1042
|
-
await this.emit(final.id, { kind: 'verify', eid: v.eid, ekind: 'fact', at: v.at });
|
|
1043
1037
|
return {
|
|
1044
1038
|
neuron: final, action, superseded, supersedes_unmatched: supersedesUnmatched,
|
|
1045
1039
|
near_duplicates: nearDuplicates, redacted: limpio.found,
|
|
1046
1040
|
duplicate, updated_in_place: updatedInPlace, skipped_retired: null,
|
|
1047
1041
|
...(confirmations !== undefined ? { confirmations } : {}),
|
|
1048
1042
|
...(shelf ? { shelf } : {}),
|
|
1049
|
-
...(reconfirmed ? { reconfirmed } : {}),
|
|
1050
1043
|
...(shelfLengthened ? { shelf_lengthened: true } : {}),
|
|
1051
1044
|
};
|
|
1052
1045
|
}
|
|
@@ -1114,6 +1107,8 @@ class Cortex {
|
|
|
1114
1107
|
* clock restarts too: the fact is shared, and so is the world it describes.
|
|
1115
1108
|
*/
|
|
1116
1109
|
async verify(neuronRef, targets) {
|
|
1110
|
+
// Validar antes de cualquier escritura, incluida la creación de neuronas.
|
|
1111
|
+
const evidence = (0, evidence_js_1.makeVerification)(targets.evidence, (0, fs_js_1.now)());
|
|
1117
1112
|
const facts = (targets.facts || []).filter(t => typeof t === 'string');
|
|
1118
1113
|
const entries = (targets.entries || []).filter(t => typeof t === 'string');
|
|
1119
1114
|
const neuron = (await this.peek(neuronRef)) || (await this.findByName(neuronRef));
|
|
@@ -1143,10 +1138,13 @@ class Cortex {
|
|
|
1143
1138
|
}
|
|
1144
1139
|
for (const f of vivos) {
|
|
1145
1140
|
f.id = f.id || (0, hash_js_1.factId)(f.text);
|
|
1146
|
-
|
|
1141
|
+
// Un check explícito posterior al cambio debe sobrevivir incluso
|
|
1142
|
+
// cuando ambas llamadas caben en el mismo milisegundo.
|
|
1143
|
+
f.verified = new Date(Math.max(Date.parse(cuando), (Date.parse(f.verification_invalidated_at || '') || 0) + 1)).toISOString();
|
|
1144
|
+
f.verification = { ...evidence, at: f.verified };
|
|
1147
1145
|
if (!verified.includes(f.id))
|
|
1148
1146
|
verified.push(f.id);
|
|
1149
|
-
emitir.push({ eid: f.id, ekind: 'fact', at:
|
|
1147
|
+
emitir.push({ eid: f.id, ekind: 'fact', at: f.verified, verification: f.verification });
|
|
1150
1148
|
}
|
|
1151
1149
|
}
|
|
1152
1150
|
const candidatas = [
|
|
@@ -1168,9 +1166,10 @@ class Cortex {
|
|
|
1168
1166
|
for (const c of vivas) {
|
|
1169
1167
|
const k = (0, ops_js_1.entryId)(c.text);
|
|
1170
1168
|
(n.entry_verified ||= {})[k] = cuando;
|
|
1169
|
+
(n.entry_verification ||= {})[k] = { ...evidence, at: cuando };
|
|
1171
1170
|
if (!verified.includes(k))
|
|
1172
1171
|
verified.push(k);
|
|
1173
|
-
emitir.push({ eid: k, ekind: 'entry', at: cuando });
|
|
1172
|
+
emitir.push({ eid: k, ekind: 'entry', at: cuando, verification: n.entry_verification[k] });
|
|
1174
1173
|
}
|
|
1175
1174
|
}
|
|
1176
1175
|
if (verified.length === 0)
|
|
@@ -1619,6 +1618,15 @@ class Cortex {
|
|
|
1619
1618
|
if (Object.keys(current.entry_verified).length === 0)
|
|
1620
1619
|
delete current.entry_verified;
|
|
1621
1620
|
}
|
|
1621
|
+
if (current.entry_verification) {
|
|
1622
|
+
const vivos = liveEntryKeys(current);
|
|
1623
|
+
for (const k of Object.keys(current.entry_verification)) {
|
|
1624
|
+
if (!vivos.has(k))
|
|
1625
|
+
delete current.entry_verification[k];
|
|
1626
|
+
}
|
|
1627
|
+
if (Object.keys(current.entry_verification).length === 0)
|
|
1628
|
+
delete current.entry_verification;
|
|
1629
|
+
}
|
|
1622
1630
|
// And for provenance: a forgotten teammate line that comes back through
|
|
1623
1631
|
// a sync replay must be labelled by that replay, not by a stale key.
|
|
1624
1632
|
if (current.entry_source) {
|
|
@@ -1803,18 +1811,24 @@ class Cortex {
|
|
|
1803
1811
|
*/
|
|
1804
1812
|
async emitAdded(antes, final) {
|
|
1805
1813
|
const cuando = (0, fs_js_1.now)();
|
|
1806
|
-
const teniaFact = new
|
|
1814
|
+
const teniaFact = new Map(antes.facts.map(f => [f.id || (0, hash_js_1.factId)(f.text), f]));
|
|
1807
1815
|
for (const f of final.facts) {
|
|
1808
1816
|
const fid = f.id || (0, hash_js_1.factId)(f.text);
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1817
|
+
const previo = teniaFact.get(fid) || antes.facts.find(p => (0, ops_js_1.normalizeText)(p.text) === (0, ops_js_1.normalizeText)(f.text));
|
|
1818
|
+
const metadata = (v) => JSON.stringify([v.keys, v.shelf_life, v.reference, v.temporal, v.verification_invalidated_at]);
|
|
1819
|
+
if (!previo || metadata(previo) !== metadata(f)) {
|
|
1820
|
+
await this.emit(final.id, { kind: 'fact', text: f.text, fid, conf: f.confidence ?? 1, at: f.added, src: f.source, keys: f.keys,
|
|
1821
|
+
...(f.reference ? { reference: f.reference } : {}), ...(f.temporal ? { temporal: f.temporal } : {}),
|
|
1822
|
+
...(f.verification_invalidated_at ? { verification_invalidated_at: f.verification_invalidated_at } : {}),
|
|
1823
|
+
...(f.shelf_life ? { shelf: f.shelf_life } : {}) });
|
|
1824
|
+
}
|
|
1825
|
+
if (f.verified && (previo?.verified !== f.verified || JSON.stringify(previo?.verification) !== JSON.stringify(f.verification))) {
|
|
1826
|
+
await this.emit(final.id, { kind: 'verify', eid: fid, ekind: 'fact', at: f.verified,
|
|
1827
|
+
...(f.verification ? { verification: f.verification } : {}) });
|
|
1828
|
+
}
|
|
1815
1829
|
// A moved fact that was already retired must arrive retired, or the
|
|
1816
1830
|
// teammates would see as current what the source had superseded.
|
|
1817
|
-
if (f.status === 'superseded' || f.status === 'retracted') {
|
|
1831
|
+
if (previo?.status !== f.status && (f.status === 'superseded' || f.status === 'retracted')) {
|
|
1818
1832
|
await this.emit(final.id, { kind: 'status', fid, to: f.status, at: f.revised || cuando, why: f.revision_note });
|
|
1819
1833
|
}
|
|
1820
1834
|
}
|
|
@@ -1842,9 +1856,10 @@ class Cortex {
|
|
|
1842
1856
|
}
|
|
1843
1857
|
// Checks of decisions and patterns that arrived with them, or are newer than the target's.
|
|
1844
1858
|
for (const [k, at] of Object.entries(final.entry_verified || {})) {
|
|
1845
|
-
if (antes.entry_verified?.[k] === at)
|
|
1859
|
+
if (antes.entry_verified?.[k] === at && JSON.stringify(antes.entry_verification?.[k]) === JSON.stringify(final.entry_verification?.[k]))
|
|
1846
1860
|
continue;
|
|
1847
|
-
await this.emit(final.id, { kind: 'verify', eid: k, ekind: 'entry', at
|
|
1861
|
+
await this.emit(final.id, { kind: 'verify', eid: k, ekind: 'entry', at,
|
|
1862
|
+
...(final.entry_verification?.[k] ? { verification: final.entry_verification[k] } : {}) });
|
|
1848
1863
|
}
|
|
1849
1864
|
// Preferences are never emitted, here as in learn.
|
|
1850
1865
|
}
|
|
@@ -1891,6 +1906,7 @@ class Cortex {
|
|
|
1891
1906
|
entry_dates: { ...(from.entry_dates || {}) },
|
|
1892
1907
|
entry_status: { ...(from.entry_status || {}) },
|
|
1893
1908
|
entry_verified: { ...(from.entry_verified || {}) },
|
|
1909
|
+
entry_verification: structuredClone(from.entry_verification || {}),
|
|
1894
1910
|
};
|
|
1895
1911
|
delete parte.map;
|
|
1896
1912
|
const textos = [
|