@unson/brainbase-mcp 0.7.0 → 0.9.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 +1 -1
- package/contracts/foundation/graph-history.sql +634 -0
- package/contracts/judgment-dag/digest.json +2 -2
- package/contracts/judgment-value-proof/schema.json +43 -1
- package/dist/canonical-task-service.js +16 -5
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +107 -0
- package/dist/company-os-objectives.d.ts +11 -0
- package/dist/company-os-objectives.js +84 -47
- package/dist/foundation-authenticated-tools.d.ts +56 -0
- package/dist/foundation-authenticated-tools.js +347 -0
- package/dist/foundation-graph-http.d.ts +43 -0
- package/dist/foundation-graph-http.js +243 -0
- package/dist/foundation-graph-write.d.ts +90 -0
- package/dist/foundation-graph-write.js +447 -0
- package/dist/foundation-public-provider.d.ts +140 -0
- package/dist/foundation-public-provider.js +192 -0
- package/dist/graph-corrections.d.ts +157 -0
- package/dist/graph-corrections.js +620 -0
- package/dist/graph-foundation-reader.d.ts +60 -0
- package/dist/graph-foundation-reader.js +494 -0
- package/dist/graph-retrieval.d.ts +6 -1
- package/dist/graph-revision-reader.d.ts +80 -0
- package/dist/graph-revision-reader.js +246 -0
- package/dist/graph-web-http.d.ts +26 -0
- package/dist/graph-web-http.js +215 -0
- package/dist/graph-web.d.ts +364 -0
- package/dist/graph-web.js +639 -0
- package/dist/judgment-problem-snapshot.d.ts +30 -2
- package/dist/judgment-problem-snapshot.js +302 -6
- package/dist/judgment-value-proof-review.d.ts +166 -0
- package/dist/judgment-value-proof-review.js +437 -0
- package/dist/judgment-value-proof.d.ts +28 -1
- package/dist/judgment-value-proof.js +53 -8
- package/dist/knowledge-continuation.d.ts +127 -0
- package/dist/knowledge-continuation.js +446 -0
- package/dist/knowledge-lookup.d.ts +178 -0
- package/dist/knowledge-lookup.js +758 -0
- package/dist/local-web-fetch-bridge.d.ts +5 -0
- package/dist/local-web-fetch-bridge.js +37 -0
- package/dist/local-web-host.d.ts +109 -0
- package/dist/local-web-host.js +688 -0
- package/dist/local-web-security.d.ts +25 -0
- package/dist/local-web-security.js +95 -0
- package/dist/organization-graph.d.ts +15 -0
- package/dist/organization-graph.js +24 -4
- package/dist/personal-knowledge.d.ts +12 -0
- package/dist/personal-knowledge.js +29 -0
- package/dist/personal-memory-handover.d.ts +108 -0
- package/dist/personal-memory-handover.js +369 -0
- package/dist/philosophy-revision-reader.d.ts +85 -0
- package/dist/philosophy-revision-reader.js +241 -0
- package/dist/portable-graph.d.ts +24 -0
- package/dist/portable-graph.js +77 -0
- package/dist/server.d.ts +7 -2
- package/dist/server.js +9 -6
- package/dist/value-proof-review-http.d.ts +25 -0
- package/dist/value-proof-review-http.js +185 -0
- package/dist/world-model.d.ts +21 -3
- package/dist/world-model.js +94 -12
- package/package.json +94 -6
- package/ui/brainbase-tokens.css +56 -0
- package/ui/graph-own-share.js +367 -0
- package/ui/graph-projects-view.css +23 -0
- package/ui/graph-projects-view.js +730 -0
- package/ui/graph-registry-view.css +101 -0
- package/ui/graph-registry-view.js +804 -0
- package/ui/graph-view-shared.css +522 -0
- package/ui/graph-view-shared.js +1227 -0
- package/ui/judgment-view.css +119 -45
- package/ui/local-web-shell.css +396 -0
- package/ui/local-web-shell.js +378 -0
- package/ui/objective-editor-http-port.js +154 -0
- package/ui/objective-editor.css +180 -74
- package/ui/objective-editor.js +490 -43
- package/ui/outcome-knowledge.css +50 -14
- package/ui/outcome-knowledge.js +15 -1
- package/ui/value-proof-review.css +504 -0
- package/ui/value-proof-review.js +1104 -0
- package/ui/workspace-kit.css +485 -0
- package/ui/workspace-kit.js +229 -0
- package/ui/world-model-view.css +188 -0
- package/ui/world-model-view.js +486 -0
- package/ui/icons/mana/LICENSE.tabler.txt +0 -21
- package/ui/icons/mana/affiliate.svg +0 -20
- package/ui/icons/mana/brand-github.svg +0 -15
- package/ui/icons/mana/brand-google-drive.svg +0 -17
- package/ui/icons/mana/brand-slack.svg +0 -18
- package/ui/icons/mana/cpu.svg +0 -24
- package/ui/outcome-mana.css +0 -255
- package/ui/outcome-mana.js +0 -2325
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { appendFile, chmod, mkdir, readFile, stat } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { resolveDataDir } from './paths.js';
|
|
5
|
+
import { createPersonalKnowledgeClient, isPersonalKnowledgeTimestamp, normalizePersonalKnowledgeApiUrl, normalizePersonalKnowledgeEvent, PERSONAL_KNOWLEDGE_CONTEXT_VERSION_HEADER, PERSONAL_KNOWLEDGE_CONTRACT_VERSION, PersonalKnowledgeClientError, readLocalPersonalKnowledgeEvents, validatePersonalKnowledgeContext } from './personal-knowledge.js';
|
|
6
|
+
import { loadPersonalOs } from './ssot.js';
|
|
7
|
+
/**
|
|
8
|
+
* Handover of personal memories from OSS Personal to an organization.
|
|
9
|
+
*
|
|
10
|
+
* The owner lists the memories kept on this machine and re-registers only the
|
|
11
|
+
* ones they choose into the organization's personal KG (organization_private,
|
|
12
|
+
* readable by the owner only). Nothing moves automatically: listing and the
|
|
13
|
+
* default preview never touch the network, and only selected memories are sent.
|
|
14
|
+
*/
|
|
15
|
+
/** Local record of what the organization confirmed, relative to the data directory. */
|
|
16
|
+
export const MEMORY_REGISTRATION_RECEIPTS_FILE = join('handover', 'personal-memory-registrations.jsonl');
|
|
17
|
+
const EVENT_ID_PURPOSE = 'brainbase-oss-memory-registration/v1';
|
|
18
|
+
const PREVIEW_LENGTH = 80;
|
|
19
|
+
const REQUEST_TIMEOUT_MS = 10_000;
|
|
20
|
+
const SAFE_ERROR_CODE = /^[a-z0-9_:-]{1,120}$/u;
|
|
21
|
+
export class PersonalMemoryRegistrationError extends Error {
|
|
22
|
+
memoryId;
|
|
23
|
+
registered;
|
|
24
|
+
constructor(memoryId, reason, registered) {
|
|
25
|
+
const done = registered.length > 0
|
|
26
|
+
? `registered in this run before the failure: ${registered.map((memory) => memory.id).join(', ')}`
|
|
27
|
+
: 'no memory was registered in this run';
|
|
28
|
+
super(`personal_memory_registration_failed: ${memoryId}: ${reason}; ${done}; later selections were not sent`);
|
|
29
|
+
this.name = 'PersonalMemoryRegistrationError';
|
|
30
|
+
this.memoryId = memoryId;
|
|
31
|
+
this.registered = registered;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Read both local memory stores and each memory's registration state. Never uses the network. */
|
|
35
|
+
export async function listLocalMemories(dataDirInput) {
|
|
36
|
+
const dataDir = resolveDataDir(dataDirInput);
|
|
37
|
+
const [current, legacy, receipts] = await Promise.all([
|
|
38
|
+
readCurrentStore(dataDir),
|
|
39
|
+
readLegacyStore(dataDir),
|
|
40
|
+
readMemoryRegistrationReceipts(dataDir)
|
|
41
|
+
]);
|
|
42
|
+
return [...current, ...legacy].map((memory) => withStatus(memory, receipts));
|
|
43
|
+
}
|
|
44
|
+
/** Pick exactly the requested memories, in request order, or fail before anything is sent. */
|
|
45
|
+
export function selectLocalMemories(memories, ids) {
|
|
46
|
+
const requested = [...new Set(ids.map((id) => id.trim()).filter(Boolean))];
|
|
47
|
+
if (requested.length === 0)
|
|
48
|
+
throw new Error('personal_memory_selection_required: choose memories with --id (see brainbase memory:list)');
|
|
49
|
+
const unknown = requested.filter((id) => !memories.some((memory) => memory.id === id));
|
|
50
|
+
if (unknown.length > 0) {
|
|
51
|
+
throw new Error(`personal_memory_unknown_id: ${unknown.join(', ')} (see brainbase memory:list); nothing was sent`);
|
|
52
|
+
}
|
|
53
|
+
const ambiguous = requested.filter((id) => memories.filter((memory) => memory.id === id).length > 1);
|
|
54
|
+
if (ambiguous.length > 0) {
|
|
55
|
+
throw new Error(`personal_memory_ambiguous_id: ${ambiguous.join(', ')} appears more than once in the local store; nothing was sent`);
|
|
56
|
+
}
|
|
57
|
+
return requested.map((id) => memories.find((memory) => memory.id === id));
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The organization event ID for a memory. It is derived from the memory's
|
|
61
|
+
* content and origin (memoryDigest) and from the authenticated destination,
|
|
62
|
+
* so re-sending the same memory to the same owner produces the same event,
|
|
63
|
+
* and another owner or organization never collides with it.
|
|
64
|
+
*/
|
|
65
|
+
export function organizationMemoryEventId(context, memoryDigest) {
|
|
66
|
+
const destination = validatePersonalKnowledgeContext(context);
|
|
67
|
+
const identity = canonicalJson({
|
|
68
|
+
purpose: EVENT_ID_PURPOSE,
|
|
69
|
+
organization_id: destination.organization_id,
|
|
70
|
+
owner_person_id: destination.owner_person_id,
|
|
71
|
+
source_id: destination.source_id,
|
|
72
|
+
memory_digest: memoryDigest
|
|
73
|
+
});
|
|
74
|
+
return `pke_oss_${sha256(identity).slice(0, 40)}`;
|
|
75
|
+
}
|
|
76
|
+
/** Send only the selected memories and record a receipt for each one the organization confirmed. */
|
|
77
|
+
export async function registerSelectedMemories(options) {
|
|
78
|
+
const dataDir = resolveDataDir(options.dataDir);
|
|
79
|
+
const selected = selectLocalMemories(await listLocalMemories(dataDir), options.ids);
|
|
80
|
+
const mode = storageMode(options.organization.url);
|
|
81
|
+
const apiUrl = normalizePersonalKnowledgeApiUrl(options.organization.url, mode);
|
|
82
|
+
const fetchImpl = withRequestTimeout(options.fetch ?? ((input, init) => globalThis.fetch(input, init)), options.timeoutMs ?? REQUEST_TIMEOUT_MS);
|
|
83
|
+
const context = await discoverOrganizationContext(apiUrl, options.organization.token, fetchImpl);
|
|
84
|
+
const client = createPersonalKnowledgeClient({
|
|
85
|
+
mode,
|
|
86
|
+
apiUrl,
|
|
87
|
+
expectedContext: context,
|
|
88
|
+
token: options.organization.token,
|
|
89
|
+
fetch: fetchImpl
|
|
90
|
+
});
|
|
91
|
+
const now = options.now ?? (() => new Date());
|
|
92
|
+
const registered = [];
|
|
93
|
+
for (const memory of selected) {
|
|
94
|
+
const eventId = organizationMemoryEventId(context, memory.memoryDigest);
|
|
95
|
+
let confirmed;
|
|
96
|
+
try {
|
|
97
|
+
confirmed = (await client.register({ event_id: eventId, ...memory.content })).event;
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
throw new PersonalMemoryRegistrationError(memory.id, describeFailure(error), registered);
|
|
101
|
+
}
|
|
102
|
+
if (confirmed.event_id !== eventId || confirmed.body_hash !== memory.content.body_hash) {
|
|
103
|
+
throw new PersonalMemoryRegistrationError(memory.id, 'personal_memory_registration_readback_mismatch', registered);
|
|
104
|
+
}
|
|
105
|
+
await appendReceipt(dataDir, {
|
|
106
|
+
schema_version: 1,
|
|
107
|
+
memory_id: memory.id,
|
|
108
|
+
memory_digest: memory.memoryDigest,
|
|
109
|
+
event_id: eventId,
|
|
110
|
+
body_hash: memory.content.body_hash,
|
|
111
|
+
organization_id: context.organization_id,
|
|
112
|
+
owner_person_id: context.owner_person_id,
|
|
113
|
+
registered_at: now().toISOString()
|
|
114
|
+
});
|
|
115
|
+
registered.push({ id: memory.id, eventId, memoryDigest: memory.memoryDigest, bodyHash: memory.content.body_hash });
|
|
116
|
+
}
|
|
117
|
+
return { organizationId: context.organization_id, ownerPersonId: context.owner_person_id, registered };
|
|
118
|
+
}
|
|
119
|
+
export async function readMemoryRegistrationReceipts(dataDirInput) {
|
|
120
|
+
const path = join(resolveDataDir(dataDirInput), MEMORY_REGISTRATION_RECEIPTS_FILE);
|
|
121
|
+
let text;
|
|
122
|
+
try {
|
|
123
|
+
text = await readFile(path, 'utf8');
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
if (error.code === 'ENOENT')
|
|
127
|
+
return [];
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
const receipts = [];
|
|
131
|
+
for (const [index, line] of text.split('\n').entries()) {
|
|
132
|
+
if (!line.trim())
|
|
133
|
+
continue;
|
|
134
|
+
let value;
|
|
135
|
+
try {
|
|
136
|
+
value = JSON.parse(line);
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
value = undefined;
|
|
140
|
+
}
|
|
141
|
+
if (!isReceipt(value))
|
|
142
|
+
throw new Error(`Invalid memory registration receipt in ${MEMORY_REGISTRATION_RECEIPTS_FILE} at line ${index + 1}.`);
|
|
143
|
+
receipts.push(value);
|
|
144
|
+
}
|
|
145
|
+
return receipts;
|
|
146
|
+
}
|
|
147
|
+
export function renderMemoryList(memories) {
|
|
148
|
+
if (memories.length === 0) {
|
|
149
|
+
return 'No local memories were found in personal-knowledge-v1/ or personal-kg.jsonl.\n';
|
|
150
|
+
}
|
|
151
|
+
const lines = memories.map((memory) => `${memory.id} ${memory.store} ${memory.type ?? '-'} ${memory.status} ${memory.preview}`);
|
|
152
|
+
const notes = [
|
|
153
|
+
'A local memory is searchable in the organization only after you register it:',
|
|
154
|
+
' brainbase memory:register --id <id> [--id <id>...] # preview, sends nothing',
|
|
155
|
+
' brainbase memory:register --id <id> [--id <id>...] --write # sends only the selected memories'
|
|
156
|
+
];
|
|
157
|
+
if (memories.some((memory) => memory.status === 'changed')) {
|
|
158
|
+
notes.push('changed: registered before, but the local memory has changed since. Registering again sends the new version as a new event; the earlier one stays.');
|
|
159
|
+
}
|
|
160
|
+
return `${lines.join('\n')}\n\n${notes.join('\n')}\n`;
|
|
161
|
+
}
|
|
162
|
+
export function memoryListJson(memories) {
|
|
163
|
+
return {
|
|
164
|
+
memories: memories.map((memory) => ({
|
|
165
|
+
id: memory.id,
|
|
166
|
+
store: memory.store,
|
|
167
|
+
type: memory.type,
|
|
168
|
+
preview: memory.preview,
|
|
169
|
+
memoryDigest: memory.memoryDigest,
|
|
170
|
+
status: memory.status,
|
|
171
|
+
...(memory.lastRegistration ? {
|
|
172
|
+
lastRegistration: {
|
|
173
|
+
eventId: memory.lastRegistration.event_id,
|
|
174
|
+
organizationId: memory.lastRegistration.organization_id,
|
|
175
|
+
registeredAt: memory.lastRegistration.registered_at
|
|
176
|
+
}
|
|
177
|
+
} : {})
|
|
178
|
+
}))
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
export function registrationPreviewJson(memories) {
|
|
182
|
+
return {
|
|
183
|
+
status: 'preview',
|
|
184
|
+
sent: false,
|
|
185
|
+
memories: memories.map((memory) => ({ id: memory.id, status: memory.status, memoryDigest: memory.memoryDigest, event: memory.content }))
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
export function renderRegistrationPreview(memories) {
|
|
189
|
+
const lines = memories.map((memory) => `- ${memory.id} ${memory.type ?? '-'} ${memory.status}\n ${memory.content.body.replace(/\s+/gu, ' ').trim()}`);
|
|
190
|
+
return [
|
|
191
|
+
'Preview only. Nothing was sent.',
|
|
192
|
+
`With --write, ${countMemories(memories.length)} would be registered in your personal KG in the organization (only you can read them; they do not become organization facts):`,
|
|
193
|
+
...lines,
|
|
194
|
+
'Unselected memories are never sent. The organization event ID is fixed from each memory and your signed-in owner, so sending the same memory again does not create a duplicate.',
|
|
195
|
+
''
|
|
196
|
+
].join('\n');
|
|
197
|
+
}
|
|
198
|
+
export function renderRegistrationResult(result) {
|
|
199
|
+
return [
|
|
200
|
+
`Registered ${countMemories(result.registered.length)} in the personal KG of organization ${result.organizationId} (owner ${result.ownerPersonId}). Only you can read them.`,
|
|
201
|
+
...result.registered.map((memory) => `- ${memory.id} -> ${memory.eventId}`),
|
|
202
|
+
''
|
|
203
|
+
].join('\n');
|
|
204
|
+
}
|
|
205
|
+
async function readCurrentStore(dataDir) {
|
|
206
|
+
const snapshot = await readLocalPersonalKnowledgeEvents({ dataDir });
|
|
207
|
+
return (snapshot?.events ?? []).map((event) => localMemory('personal-knowledge-v1', `v1:${event.event_id}`, {
|
|
208
|
+
body: event.body,
|
|
209
|
+
occurred_at: event.occurred_at,
|
|
210
|
+
captured_at: event.captured_at,
|
|
211
|
+
kind: event.kind,
|
|
212
|
+
type: event.type,
|
|
213
|
+
tags: event.tags,
|
|
214
|
+
sensitivity: event.sensitivity
|
|
215
|
+
}));
|
|
216
|
+
}
|
|
217
|
+
async function readLegacyStore(dataDir) {
|
|
218
|
+
if (!(await exists(join(dataDir, 'personal-kg.jsonl'))))
|
|
219
|
+
return [];
|
|
220
|
+
const os = await loadPersonalOs(dataDir);
|
|
221
|
+
return os.personalKg.map((entry) => {
|
|
222
|
+
const tags = (entry.tags ?? []).map((tag) => tag.trim()).filter(Boolean);
|
|
223
|
+
return localMemory('personal-kg', `legacy:${entry.id}`, {
|
|
224
|
+
body: entry.text,
|
|
225
|
+
type: entry.type,
|
|
226
|
+
tags: tags.length > 0 ? tags : undefined,
|
|
227
|
+
occurred_at: isPersonalKnowledgeTimestamp(entry.updatedAt) ? entry.updatedAt : undefined
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
function localMemory(store, id, fields) {
|
|
232
|
+
const draft = {
|
|
233
|
+
event_id: id,
|
|
234
|
+
source: { system: 'brainbase-oss', store },
|
|
235
|
+
source_pointer: { memory_id: id }
|
|
236
|
+
};
|
|
237
|
+
for (const [field, value] of Object.entries(fields)) {
|
|
238
|
+
if (value !== undefined)
|
|
239
|
+
draft[field] = value;
|
|
240
|
+
}
|
|
241
|
+
let normalized;
|
|
242
|
+
try {
|
|
243
|
+
// Validate against the same v1 event contract the organization applies.
|
|
244
|
+
normalized = normalizePersonalKnowledgeEvent(draft);
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
throw new Error(`Local memory ${id} cannot be registered: ${error instanceof Error ? error.message : String(error)}`);
|
|
248
|
+
}
|
|
249
|
+
const { event_id: _eventId, ...content } = normalized;
|
|
250
|
+
const typed = content;
|
|
251
|
+
return {
|
|
252
|
+
id,
|
|
253
|
+
store,
|
|
254
|
+
type: typed.type ?? null,
|
|
255
|
+
preview: preview(typed.body),
|
|
256
|
+
memoryDigest: `sha256:${sha256(canonicalJson(typed))}`,
|
|
257
|
+
content: typed
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
function withStatus(memory, receipts) {
|
|
261
|
+
const own = receipts.filter((receipt) => receipt.memory_id === memory.id);
|
|
262
|
+
const matching = own.filter((receipt) => receipt.memory_digest === memory.memoryDigest);
|
|
263
|
+
if (matching.length > 0)
|
|
264
|
+
return { ...memory, status: 'registered', lastRegistration: matching[matching.length - 1] };
|
|
265
|
+
if (own.length > 0)
|
|
266
|
+
return { ...memory, status: 'changed', lastRegistration: own[own.length - 1] };
|
|
267
|
+
return { ...memory, status: 'unregistered' };
|
|
268
|
+
}
|
|
269
|
+
async function discoverOrganizationContext(apiUrl, token, fetchImpl) {
|
|
270
|
+
let response;
|
|
271
|
+
try {
|
|
272
|
+
response = await fetchImpl(`${apiUrl}/api/personal-knowledge/context`, {
|
|
273
|
+
method: 'GET',
|
|
274
|
+
redirect: 'error',
|
|
275
|
+
headers: {
|
|
276
|
+
Authorization: `Bearer ${token}`,
|
|
277
|
+
[PERSONAL_KNOWLEDGE_CONTEXT_VERSION_HEADER]: String(PERSONAL_KNOWLEDGE_CONTRACT_VERSION)
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
throw new Error('personal_memory_destination_unavailable: the organization personal KG could not be reached; nothing was sent');
|
|
283
|
+
}
|
|
284
|
+
const body = await response.text().catch(() => '');
|
|
285
|
+
let value;
|
|
286
|
+
try {
|
|
287
|
+
value = body.trim() ? JSON.parse(body) : null;
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
value = null;
|
|
291
|
+
}
|
|
292
|
+
if (!response.ok) {
|
|
293
|
+
const code = errorCode(value);
|
|
294
|
+
throw new Error(`personal_memory_destination_unavailable: organization returned HTTP ${response.status}${code ? ` (${code})` : ''}; nothing was sent`);
|
|
295
|
+
}
|
|
296
|
+
let context;
|
|
297
|
+
try {
|
|
298
|
+
context = validatePersonalKnowledgeContext(isRecord(value) ? value.context : undefined);
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
throw new Error('personal_memory_destination_invalid: the organization returned an invalid personal KG context; nothing was sent');
|
|
302
|
+
}
|
|
303
|
+
if (context.scope !== 'organization_private' || context.organization_id === null) {
|
|
304
|
+
throw new Error('personal_memory_destination_not_organization: the destination is not an organization-private personal KG; nothing was sent');
|
|
305
|
+
}
|
|
306
|
+
return context;
|
|
307
|
+
}
|
|
308
|
+
async function appendReceipt(dataDir, receipt) {
|
|
309
|
+
const path = join(dataDir, MEMORY_REGISTRATION_RECEIPTS_FILE);
|
|
310
|
+
await mkdir(join(dataDir, 'handover'), { recursive: true, mode: 0o700 });
|
|
311
|
+
await appendFile(path, `${JSON.stringify(receipt)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
312
|
+
await chmod(path, 0o600);
|
|
313
|
+
}
|
|
314
|
+
function storageMode(url) {
|
|
315
|
+
const host = new URL(url).hostname.toLowerCase().replace(/^\[|\]$/gu, '');
|
|
316
|
+
return host === 'localhost' || host === '127.0.0.1' || host === '::1' ? 'local' : 'managed_cloud';
|
|
317
|
+
}
|
|
318
|
+
function withRequestTimeout(fetchImpl, timeoutMs) {
|
|
319
|
+
return (input, init) => fetchImpl(input, { ...init, signal: AbortSignal.timeout(timeoutMs) });
|
|
320
|
+
}
|
|
321
|
+
function describeFailure(error) {
|
|
322
|
+
if (error instanceof PersonalKnowledgeClientError) {
|
|
323
|
+
const code = errorCode(error.responseBody);
|
|
324
|
+
return `${error.message}${code ? ` (${code})` : ''}`;
|
|
325
|
+
}
|
|
326
|
+
return error instanceof Error ? error.message : String(error);
|
|
327
|
+
}
|
|
328
|
+
function errorCode(value) {
|
|
329
|
+
const code = isRecord(value) ? value.error : undefined;
|
|
330
|
+
return typeof code === 'string' && SAFE_ERROR_CODE.test(code) ? code : undefined;
|
|
331
|
+
}
|
|
332
|
+
function isReceipt(value) {
|
|
333
|
+
if (!isRecord(value) || value.schema_version !== 1)
|
|
334
|
+
return false;
|
|
335
|
+
return ['memory_id', 'memory_digest', 'event_id', 'body_hash', 'organization_id', 'owner_person_id', 'registered_at']
|
|
336
|
+
.every((field) => typeof value[field] === 'string' && value[field].length > 0);
|
|
337
|
+
}
|
|
338
|
+
function countMemories(count) {
|
|
339
|
+
return `${count} ${count === 1 ? 'memory' : 'memories'}`;
|
|
340
|
+
}
|
|
341
|
+
function preview(body) {
|
|
342
|
+
const flat = body.replace(/\s+/gu, ' ').trim();
|
|
343
|
+
return flat.length > PREVIEW_LENGTH ? `${flat.slice(0, PREVIEW_LENGTH - 1)}…` : flat;
|
|
344
|
+
}
|
|
345
|
+
async function exists(path) {
|
|
346
|
+
try {
|
|
347
|
+
await stat(path);
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
catch (error) {
|
|
351
|
+
if (error.code === 'ENOENT')
|
|
352
|
+
return false;
|
|
353
|
+
throw error;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function sha256(value) {
|
|
357
|
+
return createHash('sha256').update(value, 'utf8').digest('hex');
|
|
358
|
+
}
|
|
359
|
+
function canonicalJson(value) {
|
|
360
|
+
if (Array.isArray(value))
|
|
361
|
+
return `[${value.map((item) => canonicalJson(item)).join(',')}]`;
|
|
362
|
+
if (isRecord(value)) {
|
|
363
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(',')}}`;
|
|
364
|
+
}
|
|
365
|
+
return JSON.stringify(value) ?? 'null';
|
|
366
|
+
}
|
|
367
|
+
function isRecord(value) {
|
|
368
|
+
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
369
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { JudgmentProblemJSONValue, JudgmentProblemReference, JudgmentProblemReferenceResolution, JudgmentProblemReferenceResolutionPhase, JudgmentProblemScope, JudgmentProblemSnapshotAccessContext } from './judgment-problem-snapshot.js';
|
|
2
|
+
import type { FoundationStoreContext } from './foundation-store.js';
|
|
3
|
+
import type { FoundationAcl } from './ontology-foundation.js';
|
|
4
|
+
/** The host owns the canonical philosophy source; this package owns only the read port. */
|
|
5
|
+
export declare const PHILOSOPHY_REVISION_READ_CONTRACT_VERSION: "philosophy-revision-read.v1";
|
|
6
|
+
export type PhilosophyRevisionKind = 'philosophy';
|
|
7
|
+
export type PhilosophyRevisionDigest = `sha256:${string}`;
|
|
8
|
+
export type PhilosophyRevisionScope = JudgmentProblemScope;
|
|
9
|
+
export type PhilosophyRevisionPayload = JudgmentProblemJSONValue;
|
|
10
|
+
export interface PhilosophyRevisionApplicability {
|
|
11
|
+
readonly scope: PhilosophyRevisionScope;
|
|
12
|
+
readonly validFrom: string;
|
|
13
|
+
readonly validUntil?: string;
|
|
14
|
+
}
|
|
15
|
+
/** A snapshot keeps this reference, never a copy of the canonical philosophy. */
|
|
16
|
+
export interface PhilosophyRevisionReference {
|
|
17
|
+
readonly kind: PhilosophyRevisionKind;
|
|
18
|
+
readonly id: string;
|
|
19
|
+
readonly revision: string;
|
|
20
|
+
readonly digest: PhilosophyRevisionDigest;
|
|
21
|
+
readonly scope: PhilosophyRevisionScope;
|
|
22
|
+
readonly valid_from: string;
|
|
23
|
+
readonly valid_to?: string | null;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The host returns the immutable payload with current authorization metadata.
|
|
27
|
+
* `applicability` belongs to the canonical revision and is covered by digest;
|
|
28
|
+
* `currentAcl` and `currentScope` are read-time metadata and are not.
|
|
29
|
+
*/
|
|
30
|
+
export interface PhilosophyRevisionRecord {
|
|
31
|
+
readonly kind: PhilosophyRevisionKind;
|
|
32
|
+
readonly id: string;
|
|
33
|
+
readonly revision: string;
|
|
34
|
+
readonly digest: PhilosophyRevisionDigest;
|
|
35
|
+
readonly payload: PhilosophyRevisionPayload;
|
|
36
|
+
readonly applicability: PhilosophyRevisionApplicability;
|
|
37
|
+
readonly currentAcl: FoundationAcl;
|
|
38
|
+
readonly currentScope: PhilosophyRevisionScope;
|
|
39
|
+
}
|
|
40
|
+
export interface PhilosophyRevisionReadRequest {
|
|
41
|
+
readonly reference: PhilosophyRevisionReference;
|
|
42
|
+
readonly phase: JudgmentProblemReferenceResolutionPhase;
|
|
43
|
+
readonly context: {
|
|
44
|
+
readonly principal: string;
|
|
45
|
+
readonly scope: PhilosophyRevisionScope;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export type PhilosophyRevisionReadResult = {
|
|
49
|
+
readonly status: 'resolved';
|
|
50
|
+
readonly record: PhilosophyRevisionRecord;
|
|
51
|
+
} | {
|
|
52
|
+
readonly status: 'missing' | 'unauthorized' | 'corrupt';
|
|
53
|
+
readonly message?: string;
|
|
54
|
+
};
|
|
55
|
+
/** The organization-specific adapter reads this port from its existing philosophy SSOT. */
|
|
56
|
+
export interface PhilosophyRevisionReader {
|
|
57
|
+
/** Resolve a canonical pin before its digest is known; current authorization still applies. */
|
|
58
|
+
readCanonical?(input: {
|
|
59
|
+
readonly id: string;
|
|
60
|
+
readonly revision: string;
|
|
61
|
+
readonly context: FoundationStoreContext;
|
|
62
|
+
}): PhilosophyRevisionReadResult | Promise<PhilosophyRevisionReadResult>;
|
|
63
|
+
read(input: PhilosophyRevisionReadRequest): PhilosophyRevisionReadResult | Promise<PhilosophyRevisionReadResult>;
|
|
64
|
+
}
|
|
65
|
+
export interface JudgmentProblemPhilosophyReferenceResolverInput {
|
|
66
|
+
readonly reference: JudgmentProblemReference;
|
|
67
|
+
readonly phase: JudgmentProblemReferenceResolutionPhase;
|
|
68
|
+
readonly context: JudgmentProblemSnapshotAccessContext;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Computes the stable identity of a canonical philosophy revision.
|
|
72
|
+
* Current ACL and scope are intentionally excluded so authorization changes do
|
|
73
|
+
* not create a false content revision; they are checked on every read.
|
|
74
|
+
*/
|
|
75
|
+
export declare function philosophyRevisionDigest(input: Pick<PhilosophyRevisionRecord, 'kind' | 'id' | 'revision' | 'payload' | 'applicability'>): PhilosophyRevisionDigest;
|
|
76
|
+
/** Structural check for callers that need to validate a reference before I/O. */
|
|
77
|
+
export declare function isPhilosophyRevisionReference(value: unknown): value is PhilosophyRevisionReference;
|
|
78
|
+
/**
|
|
79
|
+
* Adapt canonical philosophy reads to `JudgmentProblemReferenceProvider.resolveOther`.
|
|
80
|
+
* Unknown reference kinds remain unresolved so the caller can compose other
|
|
81
|
+
* providers explicitly.
|
|
82
|
+
*/
|
|
83
|
+
export declare function createJudgmentProblemPhilosophyReferenceResolver(options: {
|
|
84
|
+
readonly reader: PhilosophyRevisionReader;
|
|
85
|
+
}): (input: JudgmentProblemPhilosophyReferenceResolverInput) => Promise<JudgmentProblemReferenceResolution>;
|