blun-king-cli 9.1.425 → 9.1.427
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.
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('node:crypto');
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const os = require('node:os');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
|
|
8
|
+
const { openCognitiveMemoryAdapter } = require('./cognitive-memory-adapter.cjs');
|
|
9
|
+
const { resolveCognitiveEvidenceGroup } = require('./cognitive-effective-view.cjs');
|
|
10
|
+
const { loadConfiguredCognitiveMemoryAdapter } = require('./cognitive-memory-provider.cjs');
|
|
11
|
+
|
|
12
|
+
const PACKAGE_VERSION = require('../package.json').version;
|
|
13
|
+
const SCHEMA = 'blun.cognitive-cross-portal-acceptance/v1';
|
|
14
|
+
const FIXED_TIME = '2031-04-05T09:00:00.000Z';
|
|
15
|
+
const TENANT_ID = 'tenant-v7';
|
|
16
|
+
const AGENT_ID = 'nova';
|
|
17
|
+
const USER_ID = 'mayk';
|
|
18
|
+
const PRIVATE_SCOPE = `relationship:${USER_ID}:private`;
|
|
19
|
+
|
|
20
|
+
function digest(value) {
|
|
21
|
+
return crypto.createHash('sha256').update(String(value)).digest('hex');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function requireCondition(condition, code) {
|
|
25
|
+
if (!condition) {
|
|
26
|
+
const error = new Error(code);
|
|
27
|
+
error.code = code;
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function addCheck(checks, id, detail, evidence) {
|
|
33
|
+
checks.push({
|
|
34
|
+
id,
|
|
35
|
+
status: 'PASS',
|
|
36
|
+
detail,
|
|
37
|
+
receipt: digest(`${SCHEMA}\0${id}\0${JSON.stringify(evidence)}`),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function identityLink({ portalId, actorId, method, authority, receiptId }) {
|
|
42
|
+
return {
|
|
43
|
+
version: 1,
|
|
44
|
+
tenantId: TENANT_ID,
|
|
45
|
+
canonicalUserId: USER_ID,
|
|
46
|
+
sourcePortal: portalId,
|
|
47
|
+
sourceSubjectId: actorId,
|
|
48
|
+
method,
|
|
49
|
+
status: 'active',
|
|
50
|
+
confirmedAt: FIXED_TIME,
|
|
51
|
+
authority,
|
|
52
|
+
receiptId,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function linkedAccess({ portalId, actorId, channelId, conversationId, method, authority, receiptId }) {
|
|
57
|
+
return {
|
|
58
|
+
tenantId: TENANT_ID,
|
|
59
|
+
userId: USER_ID,
|
|
60
|
+
actorId,
|
|
61
|
+
agentId: AGENT_ID,
|
|
62
|
+
portalId,
|
|
63
|
+
channelId,
|
|
64
|
+
conversationId,
|
|
65
|
+
requestedScope: PRIVATE_SCOPE,
|
|
66
|
+
permissionContext: {
|
|
67
|
+
authority: 'runtime_user_prompt_hook',
|
|
68
|
+
decision: 'passed',
|
|
69
|
+
receiptId: `operation-${receiptId}`,
|
|
70
|
+
identityReceiptId: receiptId,
|
|
71
|
+
},
|
|
72
|
+
identityLink: identityLink({ portalId, actorId, method, authority, receiptId }),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function directAccess({ agentId = AGENT_ID, userId, portalId, channelId, conversationId, requestedScope }) {
|
|
77
|
+
return {
|
|
78
|
+
tenantId: TENANT_ID,
|
|
79
|
+
userId,
|
|
80
|
+
actorId: userId,
|
|
81
|
+
agentId,
|
|
82
|
+
portalId,
|
|
83
|
+
channelId,
|
|
84
|
+
conversationId,
|
|
85
|
+
requestedScope,
|
|
86
|
+
permissionContext: {
|
|
87
|
+
authority: 'runtime_user_prompt_hook',
|
|
88
|
+
decision: 'passed',
|
|
89
|
+
receiptId: `operation-${conversationId}`,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function event({
|
|
95
|
+
agentId = AGENT_ID,
|
|
96
|
+
eventId,
|
|
97
|
+
expectedVersion,
|
|
98
|
+
occurredAt,
|
|
99
|
+
source,
|
|
100
|
+
observationId,
|
|
101
|
+
domain = 'open_thread',
|
|
102
|
+
key,
|
|
103
|
+
value,
|
|
104
|
+
scope,
|
|
105
|
+
supersedes,
|
|
106
|
+
withdraws,
|
|
107
|
+
retentionClass = 'relationship',
|
|
108
|
+
}) {
|
|
109
|
+
return {
|
|
110
|
+
tenantId: TENANT_ID,
|
|
111
|
+
agentId,
|
|
112
|
+
eventId,
|
|
113
|
+
expectedVersion,
|
|
114
|
+
occurredAt,
|
|
115
|
+
receivedAt: occurredAt,
|
|
116
|
+
retentionClass,
|
|
117
|
+
source,
|
|
118
|
+
observations: [{
|
|
119
|
+
observation_id: observationId,
|
|
120
|
+
domain,
|
|
121
|
+
key,
|
|
122
|
+
value,
|
|
123
|
+
confidence: 1,
|
|
124
|
+
epistemic_state: 'verified',
|
|
125
|
+
scope,
|
|
126
|
+
...(supersedes ? { supersedes } : {}),
|
|
127
|
+
...(withdraws ? { withdraws } : {}),
|
|
128
|
+
}],
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function effectiveObservation(items, key, scope) {
|
|
133
|
+
const entries = items
|
|
134
|
+
.filter((item) => item.key === key && item.scope === scope)
|
|
135
|
+
.map((item, index) => ({
|
|
136
|
+
...item,
|
|
137
|
+
observationId: item.observation_id,
|
|
138
|
+
occurredAt: Date.parse(item.occurred_at),
|
|
139
|
+
index,
|
|
140
|
+
}));
|
|
141
|
+
return resolveCognitiveEvidenceGroup(entries);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function sourceHashes(root, files) {
|
|
145
|
+
return Object.fromEntries(files.map((name) => [
|
|
146
|
+
name,
|
|
147
|
+
digest(fs.readFileSync(path.join(root, name))),
|
|
148
|
+
]));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function renderCognitiveCrossPortalAcceptance(report) {
|
|
152
|
+
const lines = [`/memory acceptance ${report.ok ? 'PASS' : 'FAIL'} ${report.passed}/${report.total}`];
|
|
153
|
+
for (const check of report.checks) {
|
|
154
|
+
lines.push(`${check.id} ${check.status} receipt=${check.receipt.slice(0, 16)}`);
|
|
155
|
+
}
|
|
156
|
+
lines.push(`receipt=${report.receiptDigest}`);
|
|
157
|
+
lines.push('synthetic temporary state; evidence only; grants no authority');
|
|
158
|
+
return lines.join('\n');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function runCognitiveCrossPortalAcceptance() {
|
|
162
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'blun-v7-cross-portal-'));
|
|
163
|
+
const protectedFiles = ['SOUL.md', 'identity/manifest.json'];
|
|
164
|
+
let adapter;
|
|
165
|
+
try {
|
|
166
|
+
fs.mkdirSync(path.join(root, 'identity'), { recursive: true });
|
|
167
|
+
fs.writeFileSync(path.join(root, 'SOUL.md'), '# Nova\n\nWarm, precise, and honest.\n', 'utf8');
|
|
168
|
+
fs.writeFileSync(path.join(root, 'identity', 'manifest.json'), `${JSON.stringify({
|
|
169
|
+
version: 1,
|
|
170
|
+
tenant_id: TENANT_ID,
|
|
171
|
+
active_agent_id: AGENT_ID,
|
|
172
|
+
})}\n`, 'utf8');
|
|
173
|
+
const before = sourceHashes(root, protectedFiles);
|
|
174
|
+
adapter = openCognitiveMemoryAdapter({ home: root });
|
|
175
|
+
const checks = [];
|
|
176
|
+
|
|
177
|
+
const telegram = linkedAccess({
|
|
178
|
+
portalId: 'telegram',
|
|
179
|
+
actorId: 'telegram-42',
|
|
180
|
+
channelId: 'direct',
|
|
181
|
+
conversationId: 'dm-42',
|
|
182
|
+
method: 'explicit_confirmed',
|
|
183
|
+
authority: 'identity_confirmation_service',
|
|
184
|
+
receiptId: 'identity-telegram-42',
|
|
185
|
+
});
|
|
186
|
+
const web = linkedAccess({
|
|
187
|
+
portalId: 'web',
|
|
188
|
+
actorId: 'oauth-77',
|
|
189
|
+
channelId: 'chat',
|
|
190
|
+
conversationId: 'web-chat-77',
|
|
191
|
+
method: 'verified_oauth',
|
|
192
|
+
authority: 'oauth_verifier',
|
|
193
|
+
receiptId: 'identity-oauth-77',
|
|
194
|
+
});
|
|
195
|
+
const cli = linkedAccess({
|
|
196
|
+
portalId: 'cli',
|
|
197
|
+
actorId: 'cli-profile-7',
|
|
198
|
+
channelId: 'terminal',
|
|
199
|
+
conversationId: 'cli-session-7',
|
|
200
|
+
method: 'secure_account_link',
|
|
201
|
+
authority: 'account_link_service',
|
|
202
|
+
receiptId: 'identity-cli-profile-7',
|
|
203
|
+
});
|
|
204
|
+
const initialEvent = event({
|
|
205
|
+
eventId: 'event-telegram-topic',
|
|
206
|
+
expectedVersion: 0,
|
|
207
|
+
occurredAt: '2031-04-05T09:01:00.000Z',
|
|
208
|
+
source: {
|
|
209
|
+
provider: 'telegram',
|
|
210
|
+
channel_id: 'direct',
|
|
211
|
+
actor_id: 'telegram-42',
|
|
212
|
+
context_id: 'dm-42',
|
|
213
|
+
message_id: 'telegram-message-1',
|
|
214
|
+
},
|
|
215
|
+
observationId: 'observation-shared-topic-v1',
|
|
216
|
+
key: 'shared-topic',
|
|
217
|
+
value: 'continue after the measured result',
|
|
218
|
+
scope: PRIVATE_SCOPE,
|
|
219
|
+
});
|
|
220
|
+
const firstCommit = adapter.commit(initialEvent, telegram);
|
|
221
|
+
const fromWeb = adapter.read({ tenantId: TENANT_ID, agentId: AGENT_ID }, web);
|
|
222
|
+
const webTopic = fromWeb.observations.find((item) => item.observation_id === 'observation-shared-topic-v1');
|
|
223
|
+
requireCondition(webTopic?.source?.provider === 'telegram'
|
|
224
|
+
&& webTopic.source.channel_id === 'direct', 'ACCEPTANCE_PORTAL_PROVENANCE_FAILED');
|
|
225
|
+
addCheck(checks, 'portal-provenance', 'Telegram provenance remains visible after a Web read.', [
|
|
226
|
+
webTopic.source.provider,
|
|
227
|
+
webTopic.source.channel_id,
|
|
228
|
+
webTopic.source.message_id,
|
|
229
|
+
]);
|
|
230
|
+
|
|
231
|
+
const fromCli = adapter.read({ tenantId: TENANT_ID, agentId: AGENT_ID }, cli);
|
|
232
|
+
requireCondition(fromWeb.observations.length === 1 && fromCli.observations.length === 1,
|
|
233
|
+
'ACCEPTANCE_LINKED_IDENTITY_FAILED');
|
|
234
|
+
addCheck(checks, 'linked-identity', 'One confirmed identity reads the same memory in Web and CLI.', [
|
|
235
|
+
fromWeb.observations[0].observation_id,
|
|
236
|
+
fromCli.observations[0].observation_id,
|
|
237
|
+
]);
|
|
238
|
+
|
|
239
|
+
const unlinkedWeb = directAccess({
|
|
240
|
+
userId: 'oauth-77',
|
|
241
|
+
portalId: 'web',
|
|
242
|
+
channelId: 'chat',
|
|
243
|
+
conversationId: 'web-chat-unlinked',
|
|
244
|
+
requestedScope: 'relationship:oauth-77:private',
|
|
245
|
+
});
|
|
246
|
+
const unlinked = adapter.read({ tenantId: TENANT_ID, agentId: AGENT_ID }, unlinkedWeb);
|
|
247
|
+
requireCondition(unlinked.observations.length === 0, 'ACCEPTANCE_UNLINKED_ISOLATION_FAILED');
|
|
248
|
+
addCheck(checks, 'unlinked-isolation', 'A similarly named but unlinked Web identity sees no private memory.', 0);
|
|
249
|
+
|
|
250
|
+
const atlasAccess = directAccess({
|
|
251
|
+
agentId: 'atlas',
|
|
252
|
+
userId: 'atlas-user',
|
|
253
|
+
portalId: 'cli',
|
|
254
|
+
channelId: 'terminal',
|
|
255
|
+
conversationId: 'atlas-session',
|
|
256
|
+
requestedScope: 'user:atlas-user:private',
|
|
257
|
+
});
|
|
258
|
+
adapter.commit(event({
|
|
259
|
+
agentId: 'atlas',
|
|
260
|
+
eventId: 'event-atlas-only',
|
|
261
|
+
expectedVersion: 0,
|
|
262
|
+
occurredAt: '2031-04-05T09:02:00.000Z',
|
|
263
|
+
source: {
|
|
264
|
+
provider: 'cli',
|
|
265
|
+
channel_id: 'terminal',
|
|
266
|
+
actor_id: 'atlas-user',
|
|
267
|
+
context_id: 'atlas-session',
|
|
268
|
+
message_id: 'atlas-message-1',
|
|
269
|
+
},
|
|
270
|
+
observationId: 'observation-atlas-only',
|
|
271
|
+
key: 'agent-only',
|
|
272
|
+
value: 'atlas private state',
|
|
273
|
+
scope: 'user:atlas-user:private',
|
|
274
|
+
}), atlasAccess);
|
|
275
|
+
requireCondition(adapter.read({ tenantId: TENANT_ID, agentId: AGENT_ID }, web)
|
|
276
|
+
.observations.every((item) => item.observation_id !== 'observation-atlas-only'),
|
|
277
|
+
'ACCEPTANCE_AGENT_ISOLATION_FAILED');
|
|
278
|
+
addCheck(checks, 'agent-isolation', 'A second agent stream remains outside Nova private reads.', 'atlas');
|
|
279
|
+
|
|
280
|
+
const nordAccess = directAccess({
|
|
281
|
+
userId: 'nord-member',
|
|
282
|
+
portalId: 'web',
|
|
283
|
+
channelId: 'group',
|
|
284
|
+
conversationId: 'group-nord',
|
|
285
|
+
requestedScope: 'team:nord',
|
|
286
|
+
});
|
|
287
|
+
const solAccess = directAccess({
|
|
288
|
+
userId: 'sol-member',
|
|
289
|
+
portalId: 'web',
|
|
290
|
+
channelId: 'group',
|
|
291
|
+
conversationId: 'group-sol',
|
|
292
|
+
requestedScope: 'team:sol',
|
|
293
|
+
});
|
|
294
|
+
adapter.commit(event({
|
|
295
|
+
eventId: 'event-nord-blocker',
|
|
296
|
+
expectedVersion: 1,
|
|
297
|
+
occurredAt: '2031-04-05T09:03:00.000Z',
|
|
298
|
+
source: {
|
|
299
|
+
provider: 'web',
|
|
300
|
+
channel_id: 'group',
|
|
301
|
+
actor_id: 'nord-member',
|
|
302
|
+
context_id: 'group-nord',
|
|
303
|
+
message_id: 'nord-message-1',
|
|
304
|
+
},
|
|
305
|
+
observationId: 'observation-nord-blocker',
|
|
306
|
+
domain: 'team',
|
|
307
|
+
key: 'group-blocker',
|
|
308
|
+
value: 'nord synthetic blocker',
|
|
309
|
+
scope: 'team:nord',
|
|
310
|
+
retentionClass: 'operational',
|
|
311
|
+
}), nordAccess);
|
|
312
|
+
const nord = adapter.read({ tenantId: TENANT_ID, agentId: AGENT_ID }, nordAccess);
|
|
313
|
+
const sol = adapter.read({ tenantId: TENANT_ID, agentId: AGENT_ID }, solAccess);
|
|
314
|
+
requireCondition(nord.observations.length === 1 && sol.observations.length === 0,
|
|
315
|
+
'ACCEPTANCE_GROUP_ISOLATION_FAILED');
|
|
316
|
+
addCheck(checks, 'group-isolation', 'The Nord scope receives its blocker while the Sol scope remains empty.', [
|
|
317
|
+
nord.observations[0].observation_id,
|
|
318
|
+
sol.observations.length,
|
|
319
|
+
]);
|
|
320
|
+
|
|
321
|
+
const replay = adapter.commit(initialEvent, telegram);
|
|
322
|
+
requireCondition(replay.idempotent === true && replay.version === firstCommit.version,
|
|
323
|
+
'ACCEPTANCE_DEDUPLICATION_FAILED');
|
|
324
|
+
addCheck(checks, 'deduplication', 'Replaying the same portal event creates no second memory event.', [
|
|
325
|
+
replay.version,
|
|
326
|
+
replay.idempotent,
|
|
327
|
+
]);
|
|
328
|
+
|
|
329
|
+
adapter.commit(event({
|
|
330
|
+
eventId: 'event-web-correction',
|
|
331
|
+
expectedVersion: 2,
|
|
332
|
+
occurredAt: '2031-04-05T09:04:00.000Z',
|
|
333
|
+
source: {
|
|
334
|
+
provider: 'web',
|
|
335
|
+
channel_id: 'chat',
|
|
336
|
+
actor_id: 'oauth-77',
|
|
337
|
+
context_id: 'web-chat-77',
|
|
338
|
+
message_id: 'web-message-2',
|
|
339
|
+
},
|
|
340
|
+
observationId: 'observation-shared-topic-v2',
|
|
341
|
+
key: 'shared-topic',
|
|
342
|
+
value: 'continue after the verified result',
|
|
343
|
+
scope: PRIVATE_SCOPE,
|
|
344
|
+
supersedes: 'observation-shared-topic-v1',
|
|
345
|
+
}), web);
|
|
346
|
+
const correctedCli = adapter.read({ tenantId: TENANT_ID, agentId: AGENT_ID }, cli);
|
|
347
|
+
const corrected = effectiveObservation(correctedCli.observations, 'shared-topic', PRIVATE_SCOPE);
|
|
348
|
+
requireCondition(corrected?.value === 'continue after the verified result'
|
|
349
|
+
&& corrected.supersedes === 'observation-shared-topic-v1', 'ACCEPTANCE_GLOBAL_CORRECTION_FAILED');
|
|
350
|
+
addCheck(checks, 'global-correction', 'A Web correction becomes the effective CLI value and keeps history.', [
|
|
351
|
+
corrected.observationId,
|
|
352
|
+
corrected.supersedes,
|
|
353
|
+
correctedCli.observations.length,
|
|
354
|
+
]);
|
|
355
|
+
|
|
356
|
+
adapter.commit(event({
|
|
357
|
+
eventId: 'event-cli-deletion',
|
|
358
|
+
expectedVersion: 3,
|
|
359
|
+
occurredAt: '2031-04-05T09:05:00.000Z',
|
|
360
|
+
source: {
|
|
361
|
+
provider: 'cli',
|
|
362
|
+
channel_id: 'terminal',
|
|
363
|
+
actor_id: 'cli-profile-7',
|
|
364
|
+
context_id: 'cli-session-7',
|
|
365
|
+
message_id: 'cli-message-3',
|
|
366
|
+
},
|
|
367
|
+
observationId: 'observation-shared-topic-deleted',
|
|
368
|
+
key: 'shared-topic',
|
|
369
|
+
value: 'withdrawn',
|
|
370
|
+
scope: PRIVATE_SCOPE,
|
|
371
|
+
withdraws: 'observation-shared-topic-v2',
|
|
372
|
+
}), cli);
|
|
373
|
+
const deletedWeb = adapter.read({ tenantId: TENANT_ID, agentId: AGENT_ID }, web);
|
|
374
|
+
requireCondition(effectiveObservation(deletedWeb.observations, 'shared-topic', PRIVATE_SCOPE) === null
|
|
375
|
+
&& deletedWeb.observations.some((item) => item.withdraws === 'observation-shared-topic-v2'),
|
|
376
|
+
'ACCEPTANCE_GLOBAL_DELETION_FAILED');
|
|
377
|
+
addCheck(checks, 'global-deletion', 'A CLI deletion removes the active Web value while retaining its tombstone.', [
|
|
378
|
+
deletedWeb.observations.length,
|
|
379
|
+
'observation-shared-topic-deleted',
|
|
380
|
+
]);
|
|
381
|
+
|
|
382
|
+
const beforeProviderFailure = adapter.read({ tenantId: TENANT_ID, agentId: AGENT_ID }, web);
|
|
383
|
+
let providerError = '';
|
|
384
|
+
try {
|
|
385
|
+
loadConfiguredCognitiveMemoryAdapter({
|
|
386
|
+
modulePath: path.join(root, 'missing-provider.cjs'),
|
|
387
|
+
home: root,
|
|
388
|
+
tenantId: TENANT_ID,
|
|
389
|
+
agentId: AGENT_ID,
|
|
390
|
+
agentName: 'Nova',
|
|
391
|
+
});
|
|
392
|
+
} catch (error) {
|
|
393
|
+
providerError = error?.code ?? '';
|
|
394
|
+
}
|
|
395
|
+
const afterProviderFailure = adapter.read({ tenantId: TENANT_ID, agentId: AGENT_ID }, web);
|
|
396
|
+
requireCondition(providerError === 'COGNITIVE_MEMORY_PROVIDER_PATH_INVALID'
|
|
397
|
+
&& JSON.stringify(beforeProviderFailure) === JSON.stringify(afterProviderFailure),
|
|
398
|
+
'ACCEPTANCE_PROVIDER_FAIL_CLOSED_FAILED');
|
|
399
|
+
addCheck(checks, 'provider-fail-closed', 'An unavailable provider fails before changing the existing memory stream.', [
|
|
400
|
+
providerError,
|
|
401
|
+
afterProviderFailure.version,
|
|
402
|
+
]);
|
|
403
|
+
|
|
404
|
+
const after = sourceHashes(root, protectedFiles);
|
|
405
|
+
const novaVerification = adapter.verify({ tenantId: TENANT_ID, agentId: AGENT_ID }, web);
|
|
406
|
+
const atlasVerification = adapter.verify({ tenantId: TENANT_ID, agentId: 'atlas' }, atlasAccess);
|
|
407
|
+
requireCondition(JSON.stringify(before) === JSON.stringify(after)
|
|
408
|
+
&& novaVerification.valid === true && atlasVerification.valid === true,
|
|
409
|
+
'ACCEPTANCE_BYTE_PRESERVATION_FAILED');
|
|
410
|
+
addCheck(checks, 'byte-preservation', 'Synthetic SOUL and identity source bytes remain unchanged and both chains verify.', [
|
|
411
|
+
after,
|
|
412
|
+
novaVerification.valid,
|
|
413
|
+
atlasVerification.valid,
|
|
414
|
+
]);
|
|
415
|
+
|
|
416
|
+
const report = {
|
|
417
|
+
schema: SCHEMA,
|
|
418
|
+
version: PACKAGE_VERSION,
|
|
419
|
+
scenarioId: 'v7-cross-portal-memory-v1',
|
|
420
|
+
ok: true,
|
|
421
|
+
passed: checks.length,
|
|
422
|
+
total: checks.length,
|
|
423
|
+
portals: ['cli', 'telegram', 'web'],
|
|
424
|
+
checks,
|
|
425
|
+
protectedSourceHashes: after,
|
|
426
|
+
receiptDigest: digest(checks.map((check) => `${check.id}:${check.receipt}`).join('\n')),
|
|
427
|
+
scope: 'synthetic-temporary-state',
|
|
428
|
+
authority: 'acceptance-evidence-only',
|
|
429
|
+
};
|
|
430
|
+
adapter.close();
|
|
431
|
+
adapter = undefined;
|
|
432
|
+
return report;
|
|
433
|
+
} finally {
|
|
434
|
+
try { adapter?.close(); } catch {}
|
|
435
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
module.exports = {
|
|
440
|
+
renderCognitiveCrossPortalAcceptance,
|
|
441
|
+
runCognitiveCrossPortalAcceptance,
|
|
442
|
+
};
|
|
443
|
+
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
const crypto = require('node:crypto');
|
|
4
4
|
const { createRuntimeCognitiveTurnLifecycle } = require('./cognitive-turn-lifecycle.cjs');
|
|
5
5
|
const { resolveCognitiveEvidenceGroup } = require('./cognitive-effective-view.cjs');
|
|
6
|
+
const {
|
|
7
|
+
renderCognitiveCrossPortalAcceptance,
|
|
8
|
+
runCognitiveCrossPortalAcceptance,
|
|
9
|
+
} = require('./cognitive-cross-portal-acceptance.cjs');
|
|
6
10
|
|
|
7
11
|
const FOCUS_DOMAINS = new Set(['self', 'team', 'goal', 'open_thread', 'next_trigger', 'expected_evidence']);
|
|
8
12
|
const POSITIVE_ID_RE = /^[1-9]\d*$/u;
|
|
@@ -25,12 +29,19 @@ function digestId(prefix, values) {
|
|
|
25
29
|
|
|
26
30
|
function isCognitiveMemoryCommand(args) {
|
|
27
31
|
const action = String(args ?? '').trim().split(/\s+/u, 1)[0].toLowerCase();
|
|
28
|
-
return action === 'focus' || action === '
|
|
32
|
+
return action === 'focus' || action === 'core' || action === 'correct'
|
|
33
|
+
|| action === 'delete' || action === 'acceptance';
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
function parseCognitiveMemoryCommand(args) {
|
|
32
37
|
const text = String(args ?? '').trim();
|
|
33
38
|
if (text.toLowerCase() === 'focus') return { action: 'focus' };
|
|
39
|
+
if (/^core(?:\s+--json)?$/iu.test(text)) {
|
|
40
|
+
return { action: 'core', json: /\s+--json$/iu.test(text) };
|
|
41
|
+
}
|
|
42
|
+
if (/^acceptance(?:\s+--json)?$/iu.test(text)) {
|
|
43
|
+
return { action: 'acceptance', json: /\s+--json$/iu.test(text) };
|
|
44
|
+
}
|
|
34
45
|
const correction = /^correct\s+(\S+)\s+(.+)$/iu.exec(text);
|
|
35
46
|
if (correction) {
|
|
36
47
|
const target = clean(correction[1], 128);
|
|
@@ -190,6 +201,25 @@ function formatFocus(items) {
|
|
|
190
201
|
return `/memory focus (${items.length})\n${lines.join('\n')}${suffix}`;
|
|
191
202
|
}
|
|
192
203
|
|
|
204
|
+
function formatCore(status) {
|
|
205
|
+
const state = status.crossPortal.status === 'blocked-profile-local'
|
|
206
|
+
? 'LOCAL_ONLY'
|
|
207
|
+
: status.crossPortal.status === 'blocked-profile-derived-identity'
|
|
208
|
+
? 'IDENTITY_BLOCKED' : 'CONFIGURED_UNVERIFIED';
|
|
209
|
+
const crossPortal = status.crossPortal.status === 'blocked-profile-local'
|
|
210
|
+
? 'BLOCKED profile-local fallback'
|
|
211
|
+
: status.crossPortal.status === 'blocked-profile-derived-identity'
|
|
212
|
+
? 'BLOCKED profile-derived identity'
|
|
213
|
+
: 'UNVERIFIED configured provider';
|
|
214
|
+
return [
|
|
215
|
+
`/memory core ${state}`,
|
|
216
|
+
`adapter: ${status.adapter.kind} (contract ${status.adapter.contractVersion})`,
|
|
217
|
+
`identity: ${status.identity.mode}`,
|
|
218
|
+
`integrity: ${status.integrity.valid ? 'PASS' : 'FAIL'}`,
|
|
219
|
+
`cross-portal: ${crossPortal}; evidence ${status.crossPortal.evidence}`,
|
|
220
|
+
].join('\n');
|
|
221
|
+
}
|
|
222
|
+
|
|
193
223
|
function runCognitiveMemoryCommand({
|
|
194
224
|
args,
|
|
195
225
|
env = process.env,
|
|
@@ -200,9 +230,17 @@ function runCognitiveMemoryCommand({
|
|
|
200
230
|
lifecycleFactory = createRuntimeCognitiveTurnLifecycle,
|
|
201
231
|
} = {}) {
|
|
202
232
|
const command = parseCognitiveMemoryCommand(args);
|
|
233
|
+
if (command.action === 'acceptance') {
|
|
234
|
+
const report = runCognitiveCrossPortalAcceptance();
|
|
235
|
+
return command.json ? JSON.stringify(report) : renderCognitiveCrossPortalAcceptance(report);
|
|
236
|
+
}
|
|
203
237
|
const telegram = normalizeTelegramSource(channelSource);
|
|
204
238
|
const lifecycle = openLifecycle(env, lifecycleFactory);
|
|
205
239
|
try {
|
|
240
|
+
if (command.action === 'core') {
|
|
241
|
+
const status = lifecycle.memoryStatus();
|
|
242
|
+
return command.json ? JSON.stringify(status) : formatCore(status);
|
|
243
|
+
}
|
|
206
244
|
const state = lifecycle.read();
|
|
207
245
|
const all = observationsVisibleToSource(normalizedFocusObservations(state), telegram);
|
|
208
246
|
const active = observationsVisibleToSource(effectiveFocusObservations(state), telegram);
|
|
@@ -76,12 +76,19 @@ function createCognitiveTurnLifecycle({
|
|
|
76
76
|
agentId,
|
|
77
77
|
runtimeId,
|
|
78
78
|
memoryAdapter,
|
|
79
|
+
memoryMode = memoryAdapter === undefined ? 'profile-local-fallback' : 'injected-adapter',
|
|
80
|
+
identityMode = 'shared-identity',
|
|
79
81
|
now = () => new Date().toISOString(),
|
|
80
82
|
} = {}) {
|
|
81
83
|
const tenant = safeId(tenantId);
|
|
82
84
|
const agent = safeId(agentId);
|
|
83
85
|
const runtime = safeId(runtimeId ?? `runtime-${process.pid}-${Date.now()}`);
|
|
84
|
-
|
|
86
|
+
const memoryModes = new Set(['profile-local-fallback', 'configured-provider', 'injected-adapter']);
|
|
87
|
+
const identityModes = new Set(['shared-identity', 'profile-derived']);
|
|
88
|
+
if (!tenant || !agent || !runtime || typeof now !== 'function'
|
|
89
|
+
|| !memoryModes.has(memoryMode) || !identityModes.has(identityMode)) {
|
|
90
|
+
fail('COGNITIVE_LIFECYCLE_INVALID');
|
|
91
|
+
}
|
|
85
92
|
const store = openCognitiveMemoryAdapter({ home, adapter: memoryAdapter });
|
|
86
93
|
const internalMemoryAccess = Object.freeze({
|
|
87
94
|
tenantId: tenant,
|
|
@@ -497,6 +504,29 @@ function createCognitiveTurnLifecycle({
|
|
|
497
504
|
});
|
|
498
505
|
}
|
|
499
506
|
|
|
507
|
+
function memoryStatus() {
|
|
508
|
+
const verification = store.verify({ tenantId: tenant, agentId: agent }, internalMemoryAccess);
|
|
509
|
+
const valid = verification?.valid === true;
|
|
510
|
+
let crossPortalStatus = 'provider-configured-unverified';
|
|
511
|
+
if (memoryMode === 'profile-local-fallback') crossPortalStatus = 'blocked-profile-local';
|
|
512
|
+
else if (identityMode === 'profile-derived') crossPortalStatus = 'blocked-profile-derived-identity';
|
|
513
|
+
return {
|
|
514
|
+
version: 1,
|
|
515
|
+
authority: 'diagnostic-only',
|
|
516
|
+
adapter: {
|
|
517
|
+
mode: memoryMode,
|
|
518
|
+
kind: store.kind,
|
|
519
|
+
contractVersion: store.contractVersion,
|
|
520
|
+
},
|
|
521
|
+
identity: { mode: identityMode },
|
|
522
|
+
integrity: { valid },
|
|
523
|
+
crossPortal: {
|
|
524
|
+
status: crossPortalStatus,
|
|
525
|
+
evidence: 'not-run',
|
|
526
|
+
},
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
|
|
500
530
|
return {
|
|
501
531
|
startTurn,
|
|
502
532
|
recordRightsCheck,
|
|
@@ -507,6 +537,7 @@ function createCognitiveTurnLifecycle({
|
|
|
507
537
|
correctFocusObservation,
|
|
508
538
|
withdrawFocusObservation,
|
|
509
539
|
authorizeAttention,
|
|
540
|
+
memoryStatus,
|
|
510
541
|
projectForTurn,
|
|
511
542
|
endTurn,
|
|
512
543
|
read: () => store.read({ tenantId: tenant, agentId: agent }, internalMemoryAccess),
|
|
@@ -552,12 +583,17 @@ function createRuntimeCognitiveTurnLifecycle({
|
|
|
552
583
|
agentName: resolvedAgent,
|
|
553
584
|
})
|
|
554
585
|
: memoryAdapter;
|
|
586
|
+
const memoryMode = memoryAdapter !== undefined
|
|
587
|
+
? 'injected-adapter'
|
|
588
|
+
: resolvedMemoryAdapter === undefined ? 'profile-local-fallback' : 'configured-provider';
|
|
555
589
|
return createCognitiveTurnLifecycle({
|
|
556
590
|
home: resolvedHome,
|
|
557
591
|
tenantId: resolvedTenantId,
|
|
558
592
|
agentId: resolvedAgentId,
|
|
559
593
|
runtimeId,
|
|
560
594
|
memoryAdapter: resolvedMemoryAdapter,
|
|
595
|
+
memoryMode,
|
|
596
|
+
identityMode: hasSharedIdentity ? 'shared-identity' : 'profile-derived',
|
|
561
597
|
now,
|
|
562
598
|
});
|
|
563
599
|
}
|
package/blun.mjs
CHANGED
|
@@ -423357,7 +423357,7 @@ async function handleMemoryCommand(host, args, dependencies) {
|
|
|
423357
423357
|
}
|
|
423358
423358
|
const parsed = parseMemoryCommand(args);
|
|
423359
423359
|
if (parsed === void 0) {
|
|
423360
|
-
host.showStatus("/memory status|on|off|focus|correct <id> <value>|delete <id
|
|
423360
|
+
host.showStatus("/memory status|on|off|focus|core [--json]|correct <id> <value>|delete <id>|acceptance [--json]");
|
|
423361
423361
|
return;
|
|
423362
423362
|
}
|
|
423363
423363
|
const client = (dependencies ?? defaultDependencies()).createClient(host);
|