crbro-memory 2.9.1 → 2.11.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 +21 -21
- package/README.md +256 -456
- package/SECURITY.md +135 -8
- package/bin/crbro.mjs +555 -299
- package/dist/build-id.json +10 -0
- package/dist/cli/common.js +211 -0
- package/dist/cli/export.js +522 -0
- package/dist/cli/import.js +683 -0
- package/dist/cli/markdown.js +313 -0
- package/dist/cli/mcpclient.js +107 -0
- package/dist/cli/serve.js +453 -0
- package/dist/cli/view.js +358 -0
- package/dist/cli/viewer-page.js +378 -0
- package/dist/daemon/daemon.js +119 -11
- package/dist/daemon/endpoint.js +222 -21
- package/dist/daemon/lines.js +102 -13
- package/dist/daemon/proxy.js +480 -125
- package/dist/daemon/surface.js +116 -0
- package/dist/daemon/trace.js +73 -0
- package/dist/engine/atrest.js +132 -0
- package/dist/engine/backup.js +143 -19
- package/dist/engine/brain.js +22 -6
- package/dist/engine/codexconfig.js +476 -0
- package/dist/engine/cortex.js +762 -311
- package/dist/engine/doctor.js +676 -0
- package/dist/engine/evidence.js +171 -0
- package/dist/engine/heat.js +81 -37
- package/dist/engine/hippocampus.js +50 -36
- package/dist/engine/hookrefresh.js +296 -0
- package/dist/engine/hookverify.js +15 -3
- package/dist/engine/installer.js +1022 -0
- package/dist/engine/installio.js +279 -0
- package/dist/engine/keychain.js +735 -144
- package/dist/engine/maintenance.js +355 -87
- package/dist/engine/modinstall.js +98 -45
- package/dist/engine/prefrontal.js +126 -69
- package/dist/engine/projects.js +80 -0
- package/dist/engine/quarantine.js +204 -0
- package/dist/engine/secrets.js +41 -5
- package/dist/engine/shelf.js +34 -11
- package/dist/engine/synapses.js +183 -80
- package/dist/engine/triggers.js +141 -11
- package/dist/index.js +37 -3
- package/dist/mcp-surface.json +1 -0
- package/dist/miner/extractor.js +64 -17
- package/dist/miner/scheduler.js +144 -68
- package/dist/search/index.js +810 -176
- package/dist/search/quality.js +31 -0
- package/dist/search/semantic.js +208 -18
- package/dist/search/tokenize.js +44 -0
- package/dist/server.js +649 -343
- package/dist/sync/git.js +203 -72
- package/dist/sync/materialize.js +33 -8
- package/dist/sync/space.js +286 -101
- package/dist/sync/validate.js +204 -0
- package/dist/utils/budget.js +15 -2
- package/dist/utils/fs.js +40 -6
- package/dist/utils/ids.js +128 -14
- package/dist/utils/lock.js +219 -52
- package/hooks/crbro-codex.mjs +284 -0
- package/hooks/crbro-guard.mjs +281 -173
- package/hooks/crbro-hooks.shipped.json +20 -0
- package/hooks/crbro-lifecycle.mjs +16 -4
- package/hooks/crbro-subagent.mjs +180 -180
- package/package.json +12 -8
- package/dist/daemon/daemon.d.ts +0 -25
- package/dist/daemon/daemon.d.ts.map +0 -1
- package/dist/daemon/daemon.js.map +0 -1
- package/dist/daemon/endpoint.d.ts +0 -66
- package/dist/daemon/endpoint.d.ts.map +0 -1
- package/dist/daemon/endpoint.js.map +0 -1
- package/dist/daemon/lines.d.ts +0 -55
- package/dist/daemon/lines.d.ts.map +0 -1
- package/dist/daemon/lines.js.map +0 -1
- package/dist/daemon/proxy.d.ts +0 -55
- package/dist/daemon/proxy.d.ts.map +0 -1
- package/dist/daemon/proxy.js.map +0 -1
- package/dist/engine/backup.d.ts +0 -68
- package/dist/engine/backup.d.ts.map +0 -1
- package/dist/engine/backup.js.map +0 -1
- package/dist/engine/brain.d.ts +0 -79
- package/dist/engine/brain.d.ts.map +0 -1
- package/dist/engine/brain.js.map +0 -1
- package/dist/engine/cortex.d.ts +0 -670
- package/dist/engine/cortex.d.ts.map +0 -1
- package/dist/engine/cortex.js.map +0 -1
- package/dist/engine/dates.d.ts +0 -28
- package/dist/engine/dates.d.ts.map +0 -1
- package/dist/engine/dates.js.map +0 -1
- package/dist/engine/heat.d.ts +0 -28
- package/dist/engine/heat.d.ts.map +0 -1
- package/dist/engine/heat.js.map +0 -1
- package/dist/engine/hippocampus.d.ts +0 -60
- package/dist/engine/hippocampus.d.ts.map +0 -1
- package/dist/engine/hippocampus.js.map +0 -1
- package/dist/engine/hookverify.d.ts +0 -42
- package/dist/engine/hookverify.d.ts.map +0 -1
- package/dist/engine/hookverify.js.map +0 -1
- package/dist/engine/keychain.d.ts +0 -32
- package/dist/engine/keychain.d.ts.map +0 -1
- package/dist/engine/keychain.js.map +0 -1
- package/dist/engine/maintenance.d.ts +0 -218
- package/dist/engine/maintenance.d.ts.map +0 -1
- package/dist/engine/maintenance.js.map +0 -1
- package/dist/engine/modinstall.d.ts +0 -215
- package/dist/engine/modinstall.d.ts.map +0 -1
- package/dist/engine/modinstall.js.map +0 -1
- package/dist/engine/postmortem.d.ts +0 -81
- package/dist/engine/postmortem.d.ts.map +0 -1
- package/dist/engine/postmortem.js.map +0 -1
- package/dist/engine/prefrontal.d.ts +0 -61
- package/dist/engine/prefrontal.d.ts.map +0 -1
- package/dist/engine/prefrontal.js.map +0 -1
- package/dist/engine/secrets.d.ts +0 -30
- package/dist/engine/secrets.d.ts.map +0 -1
- package/dist/engine/secrets.js.map +0 -1
- package/dist/engine/shelf.d.ts +0 -156
- package/dist/engine/shelf.d.ts.map +0 -1
- package/dist/engine/shelf.js.map +0 -1
- package/dist/engine/source.d.ts +0 -6
- package/dist/engine/source.d.ts.map +0 -1
- package/dist/engine/source.js.map +0 -1
- package/dist/engine/synapses.d.ts +0 -82
- package/dist/engine/synapses.d.ts.map +0 -1
- package/dist/engine/synapses.js.map +0 -1
- package/dist/engine/triggers.d.ts +0 -53
- package/dist/engine/triggers.d.ts.map +0 -1
- package/dist/engine/triggers.js.map +0 -1
- package/dist/engine/usage.d.ts +0 -61
- package/dist/engine/usage.d.ts.map +0 -1
- package/dist/engine/usage.js.map +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/miner/extractor.d.ts +0 -12
- package/dist/miner/extractor.d.ts.map +0 -1
- package/dist/miner/extractor.js.map +0 -1
- package/dist/miner/index.d.ts +0 -61
- package/dist/miner/index.d.ts.map +0 -1
- package/dist/miner/index.js.map +0 -1
- package/dist/miner/scheduler.d.ts +0 -30
- package/dist/miner/scheduler.d.ts.map +0 -1
- package/dist/miner/scheduler.js.map +0 -1
- package/dist/miner/types.d.ts +0 -11
- package/dist/miner/types.d.ts.map +0 -1
- package/dist/miner/types.js.map +0 -1
- package/dist/search/index.d.ts +0 -323
- package/dist/search/index.d.ts.map +0 -1
- package/dist/search/index.js.map +0 -1
- package/dist/search/semantic.d.ts +0 -80
- package/dist/search/semantic.d.ts.map +0 -1
- package/dist/search/semantic.js.map +0 -1
- package/dist/search/synonyms.d.ts +0 -2
- package/dist/search/synonyms.d.ts.map +0 -1
- package/dist/search/synonyms.js.map +0 -1
- package/dist/search/tokenize.d.ts +0 -39
- package/dist/search/tokenize.d.ts.map +0 -1
- package/dist/search/tokenize.js.map +0 -1
- package/dist/server.d.ts +0 -41
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/sync/git.d.ts +0 -36
- package/dist/sync/git.d.ts.map +0 -1
- package/dist/sync/git.js.map +0 -1
- package/dist/sync/materialize.d.ts +0 -35
- package/dist/sync/materialize.d.ts.map +0 -1
- package/dist/sync/materialize.js.map +0 -1
- package/dist/sync/ops.d.ts +0 -142
- package/dist/sync/ops.d.ts.map +0 -1
- package/dist/sync/ops.js.map +0 -1
- package/dist/sync/space.d.ts +0 -116
- package/dist/sync/space.d.ts.map +0 -1
- package/dist/sync/space.js.map +0 -1
- package/dist/types/index.d.ts +0 -346
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js.map +0 -1
- package/dist/utils/budget.d.ts +0 -44
- package/dist/utils/budget.d.ts.map +0 -1
- package/dist/utils/budget.js.map +0 -1
- package/dist/utils/fs.d.ts +0 -59
- package/dist/utils/fs.d.ts.map +0 -1
- package/dist/utils/fs.js.map +0 -1
- package/dist/utils/hash.d.ts +0 -20
- package/dist/utils/hash.d.ts.map +0 -1
- package/dist/utils/hash.js.map +0 -1
- package/dist/utils/ids.d.ts +0 -47
- package/dist/utils/ids.d.ts.map +0 -1
- package/dist/utils/ids.js.map +0 -1
- package/dist/utils/lock.d.ts +0 -11
- package/dist/utils/lock.d.ts.map +0 -1
- package/dist/utils/lock.js.map +0 -1
- package/dist/utils/transcripts.d.ts +0 -63
- package/dist/utils/transcripts.d.ts.map +0 -1
- package/dist/utils/transcripts.js.map +0 -1
- package/dist/version.d.ts +0 -26
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js.map +0 -1
package/dist/sync/space.js
CHANGED
|
@@ -29,12 +29,58 @@ exports.syncAll = syncAll;
|
|
|
29
29
|
exports.attachSync = attachSync;
|
|
30
30
|
const node_fs_1 = require("node:fs");
|
|
31
31
|
const node_path_1 = __importDefault(require("node:path"));
|
|
32
|
+
const node_crypto_1 = require("node:crypto");
|
|
32
33
|
const fs_js_1 = require("../utils/fs.js");
|
|
34
|
+
const lock_js_1 = require("../utils/lock.js");
|
|
33
35
|
const hash_js_1 = require("../utils/hash.js");
|
|
34
36
|
const secrets_js_1 = require("../engine/secrets.js");
|
|
35
|
-
const
|
|
37
|
+
const evidence_js_1 = require("../engine/evidence.js");
|
|
36
38
|
const ops_js_1 = require("./ops.js");
|
|
37
39
|
const git_js_1 = require("./git.js");
|
|
40
|
+
const validate_js_1 = require("./validate.js");
|
|
41
|
+
// Fuera de shared/: el bloqueo debe sobrevivir al traslado de un espacio.
|
|
42
|
+
// Orden: shared-state -> shared-outbox-state -> JSON. Nunca a la inversa.
|
|
43
|
+
// Git retiene shared-state, pero NUNCA shared-outbox-state. Los emisores de
|
|
44
|
+
// Cortex llegan después de soltar la neurona y solo toman el segundo.
|
|
45
|
+
function withSharedState(brain, fn) {
|
|
46
|
+
return (0, lock_js_1.withLock)(node_path_1.default.join(brain.paths.root, 'shared-state'), fn);
|
|
47
|
+
}
|
|
48
|
+
function withOutboxState(brain, fn) {
|
|
49
|
+
return (0, lock_js_1.withLock)(node_path_1.default.join(brain.paths.root, 'shared-outbox-state'), fn);
|
|
50
|
+
}
|
|
51
|
+
function outboxDir(brain, space) {
|
|
52
|
+
return node_path_1.default.join(brain.paths.root, 'shared-outbox', space);
|
|
53
|
+
}
|
|
54
|
+
/** Con outbox-state adquirido y sin Git concurrente sobre este repositorio. */
|
|
55
|
+
async function drainOutboxUnlocked(brain, space) {
|
|
56
|
+
const pendingDir = outboxDir(brain, space);
|
|
57
|
+
let files;
|
|
58
|
+
try {
|
|
59
|
+
files = await node_fs_1.promises.readdir(pendingDir);
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
if (err.code === 'ENOENT')
|
|
63
|
+
return 0;
|
|
64
|
+
throw err;
|
|
65
|
+
}
|
|
66
|
+
let drained = 0;
|
|
67
|
+
for (const file of files.filter(f => f.endsWith('.json')).sort()) {
|
|
68
|
+
const pending = node_path_1.default.join(pendingDir, file);
|
|
69
|
+
const record = await (0, fs_js_1.readJSON)(pending);
|
|
70
|
+
if (!record)
|
|
71
|
+
continue;
|
|
72
|
+
if (!(await readSpace(brain, space)))
|
|
73
|
+
throw new Error('Cannot deliver pending notes: space is missing.');
|
|
74
|
+
const abs = node_path_1.default.join(spaceDir(brain, space), (0, ops_js_1.opsRelPath)(record.neuronId, record.identity.author, record.identity.device));
|
|
75
|
+
await node_fs_1.promises.mkdir(node_path_1.default.dirname(abs), { recursive: true });
|
|
76
|
+
// La línea inicial aísla un posible append truncado por caída del proceso.
|
|
77
|
+
// Si cayó tras append pero antes de unlink se repite: applyOps es idempotente.
|
|
78
|
+
await node_fs_1.promises.appendFile(abs, '\n' + record.ops.map(ops_js_1.encodeOp).join('\n') + '\n', 'utf-8');
|
|
79
|
+
await node_fs_1.promises.unlink(pending);
|
|
80
|
+
drained += record.ops.length;
|
|
81
|
+
}
|
|
82
|
+
return drained;
|
|
83
|
+
}
|
|
38
84
|
/**
|
|
39
85
|
* Who this machine is. Not a security claim — anyone with push access to the
|
|
40
86
|
* repository can write any name — just a label so a fact says where it came
|
|
@@ -52,16 +98,24 @@ async function getIdentity(brain, author) {
|
|
|
52
98
|
await (0, fs_js_1.writeJSON)(p, id);
|
|
53
99
|
return id;
|
|
54
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* The folder of a space. Throws for a name that is not a plain slug or that
|
|
103
|
+
* would resolve outside shared/: with "..", create used to run `git add -A`
|
|
104
|
+
* and push the whole brain to whatever remote it was given (G1).
|
|
105
|
+
*/
|
|
55
106
|
function spaceDir(brain, name) {
|
|
56
|
-
return
|
|
107
|
+
return (0, validate_js_1.safeSpaceDir)(brain.paths.shared, name);
|
|
57
108
|
}
|
|
109
|
+
/** The space's config, or null — also for a name that cannot be a space. */
|
|
58
110
|
async function readSpace(brain, name) {
|
|
111
|
+
if ((0, validate_js_1.spaceNameProblem)(name))
|
|
112
|
+
return null;
|
|
59
113
|
return (0, fs_js_1.readJSON)(node_path_1.default.join(spaceDir(brain, name), 'space.json'));
|
|
60
114
|
}
|
|
61
115
|
async function listSpaces(brain) {
|
|
62
116
|
try {
|
|
63
117
|
const entries = await node_fs_1.promises.readdir(brain.paths.shared, { withFileTypes: true });
|
|
64
|
-
return entries.filter(e => e.isDirectory()).map(e => e.name);
|
|
118
|
+
return entries.filter(e => e.isDirectory() && !(0, validate_js_1.spaceNameProblem)(e.name)).map(e => e.name);
|
|
65
119
|
}
|
|
66
120
|
catch {
|
|
67
121
|
return [];
|
|
@@ -69,7 +123,18 @@ async function listSpaces(brain) {
|
|
|
69
123
|
}
|
|
70
124
|
// ─── Creating and joining ────────────────────────────────────────
|
|
71
125
|
async function createSpace(brain, name, remote, author, branch = 'main') {
|
|
72
|
-
|
|
126
|
+
return withSharedState(brain, () => createSpaceUnlocked(brain, name, remote, author, branch));
|
|
127
|
+
}
|
|
128
|
+
/** One message for a name, remote or branch that cannot be used. */
|
|
129
|
+
function invalidInput(name, remote, branch) {
|
|
130
|
+
const problem = (0, validate_js_1.spaceNameProblem)(name) || (0, validate_js_1.remoteProblem)(remote) || (0, validate_js_1.branchProblem)(branch);
|
|
131
|
+
return problem ? { ok: false, message: `Nothing was created or cloned. ${problem}` } : null;
|
|
132
|
+
}
|
|
133
|
+
async function createSpaceUnlocked(brain, name, remote, author, branch) {
|
|
134
|
+
const invalido = invalidInput(name, remote, branch);
|
|
135
|
+
if (invalido)
|
|
136
|
+
return invalido;
|
|
137
|
+
if (!(await (0, git_js_1.gitAvailableAsync)())) {
|
|
73
138
|
return { ok: false, message: 'git is not installed, and it is what carries the shared memory. Install it and try again.' };
|
|
74
139
|
}
|
|
75
140
|
const dir = spaceDir(brain, name);
|
|
@@ -77,27 +142,33 @@ async function createSpace(brain, name, remote, author, branch = 'main') {
|
|
|
77
142
|
return { ok: false, message: `The space "${name}" already exists on this machine.` };
|
|
78
143
|
}
|
|
79
144
|
const id = await getIdentity(brain, author);
|
|
80
|
-
const pushed = await (0, git_js_1.
|
|
145
|
+
const pushed = await (0, git_js_1.initSpaceAsync)(dir, remote, branch);
|
|
81
146
|
const cfg = { v: 1, name, created_by: id.author, created: (0, fs_js_1.now)(), branch };
|
|
82
147
|
await (0, fs_js_1.writeJSON)(node_path_1.default.join(dir, 'space.json'), cfg);
|
|
83
148
|
await node_fs_1.promises.mkdir(node_path_1.default.join(dir, '.local'), { recursive: true });
|
|
84
|
-
(0, git_js_1.
|
|
85
|
-
(0, git_js_1.
|
|
86
|
-
const again = (0, git_js_1.
|
|
149
|
+
await (0, git_js_1.gitAsync)(['add', '-A', '.'], dir);
|
|
150
|
+
await (0, git_js_1.gitAsync)(['commit', '-q', '-m', `CRBRO space "${name}"`], dir);
|
|
151
|
+
const again = await (0, git_js_1.gitAsync)(['push', 'origin', `HEAD:${branch}`], dir, 30_000);
|
|
87
152
|
if (!pushed.ok && !again.ok) {
|
|
88
153
|
return {
|
|
89
154
|
ok: false,
|
|
90
155
|
message: 'The space was created locally but could not reach the remote. Check the URL and that you have push access.',
|
|
91
|
-
detail: (again.stderr || pushed.stderr).slice(0, 300),
|
|
156
|
+
detail: (0, secrets_js_1.redact)((again.stderr || pushed.stderr).slice(0, 300)).text,
|
|
92
157
|
};
|
|
93
158
|
}
|
|
94
159
|
return {
|
|
95
160
|
ok: true,
|
|
96
|
-
message: `Space "${name}" created and pushed. Anyone you invite to ${remote} can now join it
|
|
161
|
+
message: (0, secrets_js_1.redact)(`Space "${name}" created and pushed. Anyone you invite to ${remote} can now join it.`).text,
|
|
97
162
|
};
|
|
98
163
|
}
|
|
99
164
|
async function joinSpace(brain, name, remote, author, branch = 'main') {
|
|
100
|
-
|
|
165
|
+
return withSharedState(brain, () => joinSpaceUnlocked(brain, name, remote, author, branch));
|
|
166
|
+
}
|
|
167
|
+
async function joinSpaceUnlocked(brain, name, remote, author, branch) {
|
|
168
|
+
const invalido = invalidInput(name, remote, branch);
|
|
169
|
+
if (invalido)
|
|
170
|
+
return invalido;
|
|
171
|
+
if (!(await (0, git_js_1.gitAvailableAsync)())) {
|
|
101
172
|
return { ok: false, message: 'git is not installed, and it is what carries the shared memory. Install it and try again.' };
|
|
102
173
|
}
|
|
103
174
|
const dir = spaceDir(brain, name);
|
|
@@ -106,14 +177,14 @@ async function joinSpace(brain, name, remote, author, branch = 'main') {
|
|
|
106
177
|
}
|
|
107
178
|
await getIdentity(brain, author);
|
|
108
179
|
await node_fs_1.promises.mkdir(brain.paths.shared, { recursive: true });
|
|
109
|
-
const r = (0, git_js_1.
|
|
180
|
+
const r = await (0, git_js_1.cloneSpaceAsync)(dir, remote, branch);
|
|
110
181
|
if (!r.ok) {
|
|
111
182
|
return {
|
|
112
183
|
ok: false,
|
|
113
184
|
message: r.reason === 'auth'
|
|
114
185
|
? 'Cloned nothing: no access to that repository. Ask to be added as a collaborator.'
|
|
115
186
|
: 'Could not clone the space. Check the URL and your connection.',
|
|
116
|
-
detail: r.stderr.slice(0, 300),
|
|
187
|
+
detail: (0, secrets_js_1.redact)(r.stderr.slice(0, 300)).text,
|
|
117
188
|
};
|
|
118
189
|
}
|
|
119
190
|
await node_fs_1.promises.mkdir(node_path_1.default.join(dir, '.local'), { recursive: true });
|
|
@@ -124,7 +195,7 @@ function neuronOps(neuron, id) {
|
|
|
124
195
|
const at = (0, fs_js_1.now)();
|
|
125
196
|
const ops = [
|
|
126
197
|
{ v: ops_js_1.OPS_VERSION, op: 'neuron', nid: neuron.id, by: id.author, at,
|
|
127
|
-
name: neuron.name, ntype: neuron.type, domain: neuron.domain },
|
|
198
|
+
name: (0, secrets_js_1.redact)(neuron.name || '').text, ntype: neuron.type, domain: neuron.domain },
|
|
128
199
|
];
|
|
129
200
|
for (const f of neuron.facts || []) {
|
|
130
201
|
if (!f.text)
|
|
@@ -132,21 +203,25 @@ function neuronOps(neuron, id) {
|
|
|
132
203
|
const fid = f.id || (0, ops_js_1.entryId)(f.text);
|
|
133
204
|
ops.push({ v: ops_js_1.OPS_VERSION, op: 'fact', nid: neuron.id, by: id.author,
|
|
134
205
|
at: f.added || at, fid, text: f.text, conf: f.confidence ?? 1, src: f.source,
|
|
135
|
-
...(f.
|
|
206
|
+
...(f.reference ? { reference: f.reference } : {}),
|
|
207
|
+
...(f.temporal ? { temporal: f.temporal } : {}),
|
|
208
|
+
...(f.verification_invalidated_at ? { verification_invalidated_at: f.verification_invalidated_at } : {}),
|
|
209
|
+
...(f.keys?.length ? { keys: f.keys.map(k => (0, secrets_js_1.redact)(k).text) } : {}),
|
|
136
210
|
...(f.shelf_life ? { shelf: f.shelf_life } : {}) });
|
|
137
211
|
if (f.verified) {
|
|
138
|
-
ops.push({ v: ops_js_1.OPS_VERSION, op: 'verify', nid: neuron.id, by: id.author, at: f.verified, eid: fid, ekind: 'fact'
|
|
212
|
+
ops.push({ v: ops_js_1.OPS_VERSION, op: 'verify', nid: neuron.id, by: id.author, at: f.verified, eid: fid, ekind: 'fact',
|
|
213
|
+
...(f.verification ? { verification: f.verification } : {}) });
|
|
139
214
|
}
|
|
140
215
|
if (f.status === 'superseded' || f.status === 'retracted') {
|
|
141
216
|
ops.push({ v: ops_js_1.OPS_VERSION, op: 'status', nid: neuron.id, by: id.author,
|
|
142
|
-
at: f.revised || at, fid, to: f.status, why: f.revision_note });
|
|
217
|
+
at: f.revised || at, fid, to: f.status, why: f.revision_note ? (0, secrets_js_1.redact)(f.revision_note).text : undefined });
|
|
143
218
|
}
|
|
144
219
|
}
|
|
145
220
|
for (const d of neuron.decisions || []) {
|
|
146
221
|
if (!d.text)
|
|
147
222
|
continue;
|
|
148
223
|
ops.push({ v: ops_js_1.OPS_VERSION, op: 'decision', nid: neuron.id, by: id.author,
|
|
149
|
-
at: d.date || at, did: (0, ops_js_1.entryId)(d.text), text: d.text, why: d.rationale });
|
|
224
|
+
at: d.date || at, did: (0, ops_js_1.entryId)(d.text), text: d.text, why: d.rationale ? (0, secrets_js_1.redact)(d.rationale).text : d.rationale });
|
|
150
225
|
}
|
|
151
226
|
// Dated entries carry their own date in `at`, so the team sees when a
|
|
152
227
|
// pattern, error or debt was really recorded, not when it was shared.
|
|
@@ -169,7 +244,8 @@ function neuronOps(neuron, id) {
|
|
|
169
244
|
}
|
|
170
245
|
for (const [k, when] of Object.entries(neuron.entry_verified || {})) {
|
|
171
246
|
if (k && when)
|
|
172
|
-
ops.push({ v: ops_js_1.OPS_VERSION, op: 'verify', nid: neuron.id, by: id.author, at: when, eid: k, ekind: 'entry'
|
|
247
|
+
ops.push({ v: ops_js_1.OPS_VERSION, op: 'verify', nid: neuron.id, by: id.author, at: when, eid: k, ekind: 'entry',
|
|
248
|
+
...(neuron.entry_verification?.[k] ? { verification: neuron.entry_verification[k] } : {}) });
|
|
173
249
|
}
|
|
174
250
|
if (neuron.map && neuron.map.text) {
|
|
175
251
|
ops.push({ v: ops_js_1.OPS_VERSION, op: 'map', nid: neuron.id, by: id.author,
|
|
@@ -198,8 +274,40 @@ async function prepareShare(brain, cortex, neuronRef, spaceName) {
|
|
|
198
274
|
for (const kind of (0, secrets_js_1.secretKinds)(text || ''))
|
|
199
275
|
blocked.push({ where, kind });
|
|
200
276
|
};
|
|
201
|
-
|
|
202
|
-
|
|
277
|
+
const revisarReferencia = (where, reference) => {
|
|
278
|
+
if (reference === undefined)
|
|
279
|
+
return;
|
|
280
|
+
try {
|
|
281
|
+
(0, evidence_js_1.sanitizeReference)(reference);
|
|
282
|
+
revisar(where, JSON.stringify(reference));
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
blocked.push({ where, kind: 'unsafe source reference' });
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
(neuron.facts || []).forEach((f, i) => {
|
|
289
|
+
revisar(`facts[${i}]`, f.text);
|
|
290
|
+
revisarReferencia(`facts[${i}].reference`, f.reference);
|
|
291
|
+
revisarReferencia(`facts[${i}].verification.reference`, f.verification?.reference);
|
|
292
|
+
});
|
|
293
|
+
for (const [k, v] of Object.entries(neuron.entry_verification || {})) {
|
|
294
|
+
revisarReferencia(`entry_verification[${k}].reference`, v.reference);
|
|
295
|
+
}
|
|
296
|
+
(neuron.decisions || []).forEach((d, i) => {
|
|
297
|
+
revisar(`decisions[${i}]`, d.text);
|
|
298
|
+
revisar(`decisions[${i}].rationale`, d.rationale);
|
|
299
|
+
});
|
|
300
|
+
// Every other field that travels in an op, not only the entry texts (G8):
|
|
301
|
+
// a credential in a rationale, an alias or a revision note went out while
|
|
302
|
+
// the dry run reported nothing blocked.
|
|
303
|
+
(neuron.facts || []).forEach((f, i) => {
|
|
304
|
+
(f.keys || []).forEach((k, j) => revisar(`facts[${i}].keys[${j}]`, k));
|
|
305
|
+
if (f.revision_note)
|
|
306
|
+
revisar(`facts[${i}].revision_note`, f.revision_note);
|
|
307
|
+
});
|
|
308
|
+
revisar('name', neuron.name);
|
|
309
|
+
revisar('summary', neuron.summary);
|
|
310
|
+
(neuron.tags || []).forEach((t, i) => revisar(`tags[${i}]`, t));
|
|
203
311
|
(neuron.patterns || []).forEach((p, i) => revisar(`patterns[${i}]`, p));
|
|
204
312
|
(neuron.errors || []).forEach((e, i) => revisar(`errors[${i}]`, e));
|
|
205
313
|
(neuron.debts || []).forEach((d, i) => revisar(`debts[${i}]`, d));
|
|
@@ -219,6 +327,9 @@ async function prepareShare(brain, cortex, neuronRef, spaceName) {
|
|
|
219
327
|
};
|
|
220
328
|
}
|
|
221
329
|
async function commitShare(brain, cortex, neuronRef, spaceName, token) {
|
|
330
|
+
return withSharedState(brain, () => withOutboxState(brain, () => commitShareUnlocked(brain, cortex, neuronRef, spaceName, token)));
|
|
331
|
+
}
|
|
332
|
+
async function commitShareUnlocked(brain, cortex, neuronRef, spaceName, token) {
|
|
222
333
|
const prep = await prepareShare(brain, cortex, neuronRef, spaceName);
|
|
223
334
|
if ('error' in prep)
|
|
224
335
|
return { ok: false, message: prep.error };
|
|
@@ -240,7 +351,7 @@ async function commitShare(brain, cortex, neuronRef, spaceName, token) {
|
|
|
240
351
|
// keep flowing without anyone having to share it again. A neuron that was
|
|
241
352
|
// unshared earlier loses its tombstone here, so re-sharing works.
|
|
242
353
|
await removeUnshared(brain, neuron.id);
|
|
243
|
-
await
|
|
354
|
+
await markSharedUnlocked(brain, neuron.id, spaceName);
|
|
244
355
|
return { ok: true, message: `"${neuron.id}" is now shared in "${spaceName}".`, ops: ops.length };
|
|
245
356
|
}
|
|
246
357
|
function sharedMapPath(brain) {
|
|
@@ -250,9 +361,10 @@ async function sharedMap(brain) {
|
|
|
250
361
|
return (await (0, fs_js_1.readJSON)(sharedMapPath(brain))) || {};
|
|
251
362
|
}
|
|
252
363
|
async function markShared(brain, neuronId, space) {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
364
|
+
await withSharedState(brain, () => withOutboxState(brain, () => markSharedUnlocked(brain, neuronId, space)));
|
|
365
|
+
}
|
|
366
|
+
async function markSharedUnlocked(brain, neuronId, space) {
|
|
367
|
+
await (0, fs_js_1.updateJSON)(sharedMapPath(brain), current => ({ ...current, [neuronId]: space }));
|
|
256
368
|
}
|
|
257
369
|
// ─── Unsharing ───────────────────────────────────────────────────
|
|
258
370
|
//
|
|
@@ -268,16 +380,10 @@ async function unsharedList(brain) {
|
|
|
268
380
|
return Array.isArray(raw) ? raw.filter((x) => typeof x === 'string') : [];
|
|
269
381
|
}
|
|
270
382
|
async function addUnshared(brain, neuronId) {
|
|
271
|
-
|
|
272
|
-
if (!lista.includes(neuronId))
|
|
273
|
-
lista.push(neuronId);
|
|
274
|
-
await (0, fs_js_1.writeJSON)(unsharedPath(brain), lista);
|
|
383
|
+
await (0, fs_js_1.updateJSON)(unsharedPath(brain), current => [...new Set([...(current || []), neuronId])]);
|
|
275
384
|
}
|
|
276
385
|
async function removeUnshared(brain, neuronId) {
|
|
277
|
-
|
|
278
|
-
if (!lista.includes(neuronId))
|
|
279
|
-
return;
|
|
280
|
-
await (0, fs_js_1.writeJSON)(unsharedPath(brain), lista.filter(id => id !== neuronId));
|
|
386
|
+
await (0, fs_js_1.updateJSON)(unsharedPath(brain), current => current?.filter(id => id !== neuronId) || null);
|
|
281
387
|
}
|
|
282
388
|
/**
|
|
283
389
|
* Stop following a neuron in its space. Local only: no more notes go out and
|
|
@@ -287,6 +393,9 @@ async function removeUnshared(brain, neuronId) {
|
|
|
287
393
|
* already been forgotten can still be unshared.
|
|
288
394
|
*/
|
|
289
395
|
async function unshareNeuron(brain, cortex, neuronRef) {
|
|
396
|
+
return withSharedState(brain, () => withOutboxState(brain, () => unshareNeuronUnlocked(brain, cortex, neuronRef)));
|
|
397
|
+
}
|
|
398
|
+
async function unshareNeuronUnlocked(brain, cortex, neuronRef) {
|
|
290
399
|
const neuron = (await cortex.peek(neuronRef)) || (await cortex.findByName(neuronRef));
|
|
291
400
|
const id = neuron?.id || neuronRef;
|
|
292
401
|
const m = await sharedMap(brain);
|
|
@@ -294,6 +403,8 @@ async function unshareNeuron(brain, cortex, neuronRef) {
|
|
|
294
403
|
if (!space) {
|
|
295
404
|
return { ok: false, neuron_id: id, space: null, message: `"${id}" is not shared in any space.` };
|
|
296
405
|
}
|
|
406
|
+
// Lo ya confirmado debe llegar al log antes de dejar de seguir la neurona.
|
|
407
|
+
await drainOutboxUnlocked(brain, space);
|
|
297
408
|
delete m[id];
|
|
298
409
|
await (0, fs_js_1.writeJSON)(sharedMapPath(brain), m);
|
|
299
410
|
await addUnshared(brain, id);
|
|
@@ -307,68 +418,94 @@ async function unshareNeuron(brain, cortex, neuronRef) {
|
|
|
307
418
|
}
|
|
308
419
|
/**
|
|
309
420
|
* Leave a space: one last sync so pending notes go out, then the local copy
|
|
310
|
-
* of the space is
|
|
421
|
+
* of the space is archived and its neurons stop being followed. The local
|
|
311
422
|
* neurons and the remote repository are untouched. The ids are NOT
|
|
312
423
|
* tombstoned — re-joining should follow them again.
|
|
313
424
|
*/
|
|
314
425
|
async function leaveSpace(brain, cortex, name) {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
// Offline or no access are not failures here: the point is to leave, and
|
|
320
|
-
// what could not be pushed is reported, not blocking.
|
|
321
|
-
let synced = 'skipped';
|
|
322
|
-
try {
|
|
323
|
-
synced = (await syncSpaceNow(brain, cortex, name, 10_000)).state;
|
|
324
|
-
}
|
|
325
|
-
catch {
|
|
326
|
-
synced = 'offline';
|
|
327
|
-
}
|
|
328
|
-
// maxRetries: the space is a git repository, and on Windows its pack files
|
|
329
|
-
// are read-only; Node retries after fixing the mode, but only if asked.
|
|
330
|
-
await node_fs_1.promises.rm(spaceDir(brain, name), { recursive: true, force: true, maxRetries: 3 });
|
|
331
|
-
const m = await sharedMap(brain);
|
|
332
|
-
let neurons_unshared = 0;
|
|
333
|
-
for (const [nid, space] of Object.entries(m)) {
|
|
334
|
-
if (space === name) {
|
|
335
|
-
delete m[nid];
|
|
336
|
-
neurons_unshared++;
|
|
426
|
+
return withSharedState(brain, async () => {
|
|
427
|
+
const cfg = await readSpace(brain, name);
|
|
428
|
+
if (!cfg) {
|
|
429
|
+
return { ok: false, message: `You are not in a space called "${name}".`, synced_before_leaving: 'skipped', pushed_before_leaving: false, neurons_unshared: 0 };
|
|
337
430
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
431
|
+
// Offline or no access are not failures here: the point is to leave, and
|
|
432
|
+
// what could not be pushed is reported, not blocking.
|
|
433
|
+
let synced = 'skipped';
|
|
434
|
+
let pushed = false;
|
|
435
|
+
try {
|
|
436
|
+
const result = await syncSpaceUnlocked(brain, cortex, name, 10_000);
|
|
437
|
+
synced = result.state;
|
|
438
|
+
pushed = result.pushed;
|
|
439
|
+
}
|
|
440
|
+
catch {
|
|
441
|
+
synced = 'offline';
|
|
442
|
+
}
|
|
443
|
+
return withOutboxState(brain, async () => {
|
|
444
|
+
// Siempre se conserva TODO el repositorio (incluido .git), incluso tras
|
|
445
|
+
// enviar: las operaciones y su historial siguen siendo recuperables.
|
|
446
|
+
const archive = node_path_1.default.join(brain.paths.root, 'shared-archives', `${name}-${(0, node_crypto_1.randomUUID)()}`);
|
|
447
|
+
try {
|
|
448
|
+
// También lo confirmado mientras Git o la materialización estaban ocupados.
|
|
449
|
+
if (await drainOutboxUnlocked(brain, name))
|
|
450
|
+
pushed = false;
|
|
451
|
+
await node_fs_1.promises.mkdir(node_path_1.default.dirname(archive), { recursive: true });
|
|
452
|
+
await node_fs_1.promises.rename(spaceDir(brain, name), archive);
|
|
453
|
+
}
|
|
454
|
+
catch {
|
|
455
|
+
return {
|
|
456
|
+
ok: false, message: `Could not archive "${name}". The space and its logs were kept; you have not left.`,
|
|
457
|
+
synced_before_leaving: synced, pushed_before_leaving: pushed, neurons_unshared: 0,
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
const m = await sharedMap(brain);
|
|
461
|
+
let neurons_unshared = 0;
|
|
462
|
+
for (const [nid, space] of Object.entries(m)) {
|
|
463
|
+
if (space === name) {
|
|
464
|
+
delete m[nid];
|
|
465
|
+
neurons_unshared++;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
if (neurons_unshared > 0)
|
|
469
|
+
await (0, fs_js_1.writeJSON)(sharedMapPath(brain), m);
|
|
470
|
+
const aviso = pushed
|
|
471
|
+
? 'Your pending notes went out first.'
|
|
472
|
+
: 'The final push was not confirmed; pending notes remain recoverable in the archive.';
|
|
473
|
+
return {
|
|
474
|
+
ok: true,
|
|
475
|
+
message: `Left "${name}". ${aviso} ${neurons_unshared} neuron(s) are no longer followed; ` +
|
|
476
|
+
`your local neurons are kept. Recoverable space archive: ${archive}`,
|
|
477
|
+
synced_before_leaving: synced,
|
|
478
|
+
pushed_before_leaving: pushed,
|
|
479
|
+
neurons_unshared,
|
|
480
|
+
archive_path: archive,
|
|
481
|
+
};
|
|
482
|
+
});
|
|
483
|
+
});
|
|
353
484
|
}
|
|
354
|
-
/**
|
|
485
|
+
/** Registra antes de confirmar. Solo omite una neurona que ya no se comparte. */
|
|
355
486
|
async function emitOps(brain, neuronId, make) {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
487
|
+
return withOutboxState(brain, async () => {
|
|
488
|
+
const m = await sharedMap(brain);
|
|
489
|
+
const space = m[neuronId];
|
|
490
|
+
if (!space)
|
|
491
|
+
return;
|
|
492
|
+
const id = await getIdentity(brain);
|
|
493
|
+
const ops = make(id);
|
|
494
|
+
if (ops.length === 0)
|
|
495
|
+
return;
|
|
496
|
+
await (0, fs_js_1.writeJSON)(node_path_1.default.join(outboxDir(brain, space), `${(0, node_crypto_1.randomUUID)()}.json`), { neuronId, identity: id, ops });
|
|
497
|
+
// Si sync adquiere shared-state después de esta comprobación, debe esperar
|
|
498
|
+
// nuestro outbox-state antes de empezar Git. No hay ventana de append/Git.
|
|
499
|
+
if (!(await (0, fs_js_1.fileExists)(node_path_1.default.join(brain.paths.root, 'shared-state.lock')))) {
|
|
500
|
+
try {
|
|
501
|
+
await drainOutboxUnlocked(brain, space);
|
|
502
|
+
}
|
|
503
|
+
catch { /* Ya persistido: se reintenta en sync/leave, incluso tras reinicio. */ }
|
|
504
|
+
}
|
|
505
|
+
});
|
|
370
506
|
}
|
|
371
507
|
// ─── Sync ────────────────────────────────────────────────────────
|
|
508
|
+
const MAX_LOG_BYTES = 32 * 1024 * 1024;
|
|
372
509
|
async function readAllOps(dir, neuronId) {
|
|
373
510
|
const opsDir = node_path_1.default.join(dir, 'neurons', neuronId, 'ops');
|
|
374
511
|
let files;
|
|
@@ -383,7 +520,11 @@ async function readAllOps(dir, neuronId) {
|
|
|
383
520
|
if (!f.endsWith('.jsonl'))
|
|
384
521
|
continue;
|
|
385
522
|
try {
|
|
386
|
-
const
|
|
523
|
+
const p = node_path_1.default.join(opsDir, f);
|
|
524
|
+
// A log that size is not notes; reading it whole would only cost memory.
|
|
525
|
+
if ((await node_fs_1.promises.stat(p)).size > MAX_LOG_BYTES)
|
|
526
|
+
continue;
|
|
527
|
+
const raw = await node_fs_1.promises.readFile(p, 'utf-8');
|
|
387
528
|
all.push(...(0, ops_js_1.decodeOps)(raw).ops);
|
|
388
529
|
}
|
|
389
530
|
catch { /* unreadable log: skip it, keep the rest */ }
|
|
@@ -391,17 +532,26 @@ async function readAllOps(dir, neuronId) {
|
|
|
391
532
|
return all;
|
|
392
533
|
}
|
|
393
534
|
async function syncSpaceNow(brain, cortex, spaceName, timeoutMs = 30_000) {
|
|
535
|
+
return withSharedState(brain, () => syncSpaceUnlocked(brain, cortex, spaceName, timeoutMs));
|
|
536
|
+
}
|
|
537
|
+
async function syncSpaceUnlocked(brain, cortex, spaceName, timeoutMs) {
|
|
394
538
|
const base = {
|
|
395
539
|
space: spaceName, state: 'ok', neurons_touched: [], merged: [], pushed: false, message: '',
|
|
396
540
|
};
|
|
397
541
|
const cfg = await readSpace(brain, spaceName);
|
|
398
542
|
if (!cfg)
|
|
399
543
|
return { ...base, state: 'not_joined', message: `You are not in a space called "${spaceName}".` };
|
|
400
|
-
|
|
544
|
+
await withOutboxState(brain, () => drainOutboxUnlocked(brain, spaceName));
|
|
545
|
+
if (!(await (0, git_js_1.gitAvailableAsync)()))
|
|
401
546
|
return { ...base, state: 'no_git', message: 'git is not installed on this machine.' };
|
|
547
|
+
// space.json arrives from the remote: its branch is checked like any input.
|
|
548
|
+
const branch = cfg.branch || 'main';
|
|
549
|
+
if ((0, validate_js_1.branchProblem)(branch)) {
|
|
550
|
+
return { ...base, state: 'conflict', message: 'The space.json of this space names an invalid branch. Nothing was synced.' };
|
|
551
|
+
}
|
|
402
552
|
const dir = spaceDir(brain, spaceName);
|
|
403
553
|
const id = await getIdentity(brain);
|
|
404
|
-
const { pulled, pushed } = (0, git_js_1.
|
|
554
|
+
const { pulled, pushed } = await (0, git_js_1.syncSpaceAsync)(dir, id.author, branch, timeoutMs);
|
|
405
555
|
if (!pulled.ok) {
|
|
406
556
|
const state = (pulled.reason === 'offline' || pulled.reason === 'timeout') ? 'offline'
|
|
407
557
|
: pulled.reason === 'auth' ? 'auth'
|
|
@@ -425,27 +575,52 @@ async function syncSpaceNow(brain, cortex, spaceName, timeoutMs = 30_000) {
|
|
|
425
575
|
catch { /* nothing shared yet */ }
|
|
426
576
|
// Neurons this machine stopped following: no merge, no markShared.
|
|
427
577
|
const fuera = new Set(await unsharedList(brain));
|
|
578
|
+
const mapa = await sharedMap(brain);
|
|
579
|
+
const intactas = [];
|
|
580
|
+
let descartadas = 0;
|
|
428
581
|
for (const nid of neuronIds) {
|
|
429
582
|
if (fuera.has(nid))
|
|
430
583
|
continue;
|
|
431
|
-
|
|
584
|
+
// A folder name is what becomes a file name in the cortex: only what
|
|
585
|
+
// CRBRO itself would have written.
|
|
586
|
+
if (!validate_js_1.NEURON_DIR_RE.test(nid))
|
|
587
|
+
continue;
|
|
588
|
+
const owner = mapa[nid];
|
|
589
|
+
// Followed in another space: that space's sync owns it.
|
|
590
|
+
if (owner && owner !== spaceName)
|
|
591
|
+
continue;
|
|
592
|
+
// A local neuron you never shared here stays yours. Until 2.10 a
|
|
593
|
+
// teammate could create neurons/<your id>/ and the next sync merged into
|
|
594
|
+
// it and marked it shared, so everything you learned there went out (G2).
|
|
595
|
+
if (!owner && await (0, fs_js_1.fileExists)(brain.paths.neuron(nid))) {
|
|
596
|
+
intactas.push(nid);
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
const leidas = await readAllOps(dir, nid);
|
|
600
|
+
const { ops, dropped } = (0, validate_js_1.sanitizeIncomingOps)(nid, leidas);
|
|
601
|
+
descartadas += dropped;
|
|
432
602
|
if (ops.length === 0)
|
|
433
603
|
continue;
|
|
434
|
-
const
|
|
435
|
-
const { neuron, report } = (0, materialize_js_1.applyOps)(local, ops, { id: nid });
|
|
604
|
+
const { report, created } = await cortex.mergeFromSync(nid, ops);
|
|
436
605
|
const cambio = report.facts_added + report.facts_retracted + report.facts_superseded +
|
|
437
606
|
report.decisions_added + report.patterns_added + report.tags_added +
|
|
438
607
|
report.errors_added + report.debts_added + report.entries_removed +
|
|
439
|
-
report.verifications_updated + report.shelf_updated +
|
|
608
|
+
report.verifications_updated + report.shelf_updated + report.metadata_updated +
|
|
440
609
|
(report.map_updated ? 1 : 0);
|
|
441
|
-
if (cambio > 0 ||
|
|
442
|
-
await cortex.replaceFromSync(neuron);
|
|
610
|
+
if (cambio > 0 || created) {
|
|
443
611
|
base.neurons_touched.push(nid);
|
|
444
612
|
base.merged.push(report);
|
|
445
613
|
}
|
|
446
|
-
|
|
614
|
+
if (!owner)
|
|
615
|
+
await markSharedUnlocked(brain, nid, spaceName);
|
|
447
616
|
}
|
|
448
|
-
|
|
617
|
+
if (intactas.length)
|
|
618
|
+
base.left_alone = intactas;
|
|
619
|
+
if (descartadas)
|
|
620
|
+
base.ops_dropped = descartadas;
|
|
621
|
+
const pending = await withOutboxState(brain, () => drainOutboxUnlocked(brain, spaceName));
|
|
622
|
+
// Git solo envió lo que había antes: lo añadido después espera el próximo sync.
|
|
623
|
+
base.pushed = !!pushed?.ok && pending === 0;
|
|
449
624
|
const nuevos = base.merged.reduce((n, m) => n + m.facts_added, 0);
|
|
450
625
|
base.message = base.neurons_touched.length === 0
|
|
451
626
|
? 'Up to date. Nothing new from anyone.'
|
|
@@ -453,6 +628,12 @@ async function syncSpaceNow(brain, cortex, spaceName, timeoutMs = 30_000) {
|
|
|
453
628
|
if (!base.pushed && pushed) {
|
|
454
629
|
base.message += ' Your own notes could not be pushed this time; they will go with the next sync.';
|
|
455
630
|
}
|
|
631
|
+
if (intactas.length) {
|
|
632
|
+
base.message += ` ${intactas.length} neuron(s) in the space have the id of a private neuron of yours and were left ` +
|
|
633
|
+
`alone (${intactas.slice(0, 5).join(', ')}${intactas.length > 5 ? ', …' : ''}). To merge one, crbro_share it into "${spaceName}".`;
|
|
634
|
+
}
|
|
635
|
+
if (descartadas)
|
|
636
|
+
base.message += ` ${descartadas} malformed incoming note(s) were ignored.`;
|
|
456
637
|
return base;
|
|
457
638
|
}
|
|
458
639
|
/** Sync every space. Used at boot and at consolidation, with a short budget. */
|
|
@@ -484,18 +665,22 @@ function attachSync(brain, cortex) {
|
|
|
484
665
|
if (change.kind === 'fact') {
|
|
485
666
|
return [{ ...comun, op: 'fact', fid: change.fid, text: change.text,
|
|
486
667
|
conf: change.conf, src: change.src,
|
|
487
|
-
...(change.
|
|
668
|
+
...(change.reference ? { reference: change.reference } : {}),
|
|
669
|
+
...(change.temporal ? { temporal: change.temporal } : {}),
|
|
670
|
+
...(change.verification_invalidated_at ? { verification_invalidated_at: change.verification_invalidated_at } : {}),
|
|
671
|
+
...(change.keys?.length ? { keys: change.keys.map(k => (0, secrets_js_1.redact)(k).text) } : {}),
|
|
488
672
|
...(change.shelf ? { shelf: change.shelf } : {}) }];
|
|
489
673
|
}
|
|
490
674
|
if (change.kind === 'verify') {
|
|
491
|
-
return [{ ...comun, op: 'verify', eid: change.eid, ekind: change.ekind
|
|
675
|
+
return [{ ...comun, op: 'verify', eid: change.eid, ekind: change.ekind,
|
|
676
|
+
...(change.verification ? { verification: change.verification } : {}) }];
|
|
492
677
|
}
|
|
493
678
|
if (change.kind === 'status') {
|
|
494
|
-
return [{ ...comun, op: 'status', fid: change.fid, to: change.to, why: change.why }];
|
|
679
|
+
return [{ ...comun, op: 'status', fid: change.fid, to: change.to, why: change.why ? (0, secrets_js_1.redact)(change.why).text : change.why }];
|
|
495
680
|
}
|
|
496
681
|
if (change.kind === 'decision') {
|
|
497
682
|
return [{ ...comun, op: 'decision', did: (0, ops_js_1.entryId)(change.text),
|
|
498
|
-
text: change.text, why: change.why }];
|
|
683
|
+
text: change.text, why: change.why ? (0, secrets_js_1.redact)(change.why).text : change.why }];
|
|
499
684
|
}
|
|
500
685
|
if (change.kind === 'error') {
|
|
501
686
|
return [{ ...comun, op: 'error', text: change.text }];
|