@unson/brainbase-mcp 0.10.4 → 0.10.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { FOUNDATION_HTTP_CONTRACT_VERSION, createFoundationHttpRouter } from './foundation-http.js';
|
|
1
|
+
import { FOUNDATION_HTTP_CONTRACT_VERSION, createFoundationHttpRouter, createObjectiveFoundationRoute } from './foundation-http.js';
|
|
2
|
+
import { checkObjectiveReadiness } from './company-os-objectives.js';
|
|
2
3
|
import { createFoundationPublicProvider, createFoundationPublicRoute } from './foundation-public-provider.js';
|
|
3
4
|
import { createGraphFoundationReaders } from './graph-foundation-reader.js';
|
|
5
|
+
import { FoundationStoreError } from './foundation-store.js';
|
|
4
6
|
/**
|
|
5
7
|
* Readiness probe for the Graph history contract used by the public adapter.
|
|
6
8
|
*
|
|
@@ -217,16 +219,36 @@ export function createFoundationGraphHttpHandler(options) {
|
|
|
217
219
|
}
|
|
218
220
|
const readers = createGraphFoundationReaders({
|
|
219
221
|
context,
|
|
222
|
+
selectedProjectCode: scopeId,
|
|
220
223
|
query: async (text, values) => ({
|
|
221
224
|
rows: (await client.query(text, values ?? [])).rows
|
|
222
225
|
})
|
|
223
226
|
});
|
|
224
227
|
const provider = createFoundationPublicProvider(readers);
|
|
225
228
|
const route = createFoundationPublicRoute(provider);
|
|
229
|
+
const objectiveRoute = createObjectiveFoundationRoute({
|
|
230
|
+
objectives: {
|
|
231
|
+
async createObjective() {
|
|
232
|
+
throw new FoundationStoreError('unsupported_graph', 'Objective writes are not configured for the Graph read adapter');
|
|
233
|
+
},
|
|
234
|
+
async readObjective(id, objectiveContext, revision) {
|
|
235
|
+
return revision === undefined
|
|
236
|
+
? readers.store.readLatest('objective', id, objectiveContext)
|
|
237
|
+
: readers.store.read({ id, type: 'objective', revision }, objectiveContext);
|
|
238
|
+
},
|
|
239
|
+
async updateObjective() {
|
|
240
|
+
throw new FoundationStoreError('unsupported_graph', 'Objective writes are not configured for the Graph read adapter');
|
|
241
|
+
},
|
|
242
|
+
async checkObjectiveReadiness(id, objectiveContext, revision) {
|
|
243
|
+
return checkObjectiveReadiness(readers.store, id, objectiveContext, revision);
|
|
244
|
+
},
|
|
245
|
+
list: (type, objectiveContext) => readers.store.list(type, objectiveContext)
|
|
246
|
+
}
|
|
247
|
+
});
|
|
226
248
|
const handler = createFoundationHttpRouter({
|
|
227
249
|
resolveContext: () => context,
|
|
228
250
|
csrf: options.csrf,
|
|
229
|
-
routes: [route],
|
|
251
|
+
routes: [route, objectiveRoute],
|
|
230
252
|
bodyLimitBytes
|
|
231
253
|
});
|
|
232
254
|
return handler.handle(request);
|
|
@@ -13,6 +13,18 @@ export declare const GRAPH_FOUNDATION_READ_CONTRACT_VERSION: "graph-foundation-h
|
|
|
13
13
|
* future providers fail closed when they do not prove current-row visibility.
|
|
14
14
|
*/
|
|
15
15
|
export declare const GRAPH_FOUNDATION_READ_SQL = "\nSELECT\n history.entity_id,\n history.entity_type,\n history.revision,\n history.payload,\n history.project_id,\n history.role_min,\n history.sensitivity,\n history.lifecycle_status,\n history.storage_digest,\n history.captured_at,\n (\n history.storage_digest = 'sha256:' ||\n encode(sha256(convert_to(history.payload::text, 'UTF8')), 'hex')\n ) AS storage_digest_valid,\n current_entity.id AS current_entity_id,\n current_entity.entity_type AS current_entity_type,\n current_entity.payload AS current_payload,\n current_entity.project_id AS current_project_id,\n current_entity.role_min AS current_role_min,\n current_entity.sensitivity AS current_sensitivity,\n current_entity.lifecycle_status AS current_lifecycle_status,\n projects.code AS current_project_code,\n true AS current_visible\nFROM public.graph_foundation_revisions AS history\nJOIN public.graph_entities AS current_entity\n ON current_entity.id = history.entity_id\n AND current_entity.entity_type = history.entity_type\nJOIN public.projects AS projects\n ON projects.id = current_entity.project_id\nWHERE history.entity_id = $1\n AND history.entity_type = $2\n AND history.revision = $3\n";
|
|
16
|
+
/**
|
|
17
|
+
* Read the newest immutable revision for one Foundation entity. The current
|
|
18
|
+
* Graph row remains part of the query so RLS and the adapter's current-row
|
|
19
|
+
* checks apply identically to latest and historical reads.
|
|
20
|
+
*/
|
|
21
|
+
export declare const GRAPH_FOUNDATION_LATEST_SQL = "\nSELECT\n history.entity_id,\n history.entity_type,\n history.revision,\n history.payload,\n history.project_id,\n history.role_min,\n history.sensitivity,\n history.lifecycle_status,\n history.storage_digest,\n history.captured_at,\n (\n history.storage_digest = 'sha256:' ||\n encode(sha256(convert_to(history.payload::text, 'UTF8')), 'hex')\n ) AS storage_digest_valid,\n current_entity.id AS current_entity_id,\n current_entity.entity_type AS current_entity_type,\n current_entity.payload AS current_payload,\n current_entity.project_id AS current_project_id,\n current_entity.role_min AS current_role_min,\n current_entity.sensitivity AS current_sensitivity,\n current_entity.lifecycle_status AS current_lifecycle_status,\n projects.code AS current_project_code,\n true AS current_visible\nFROM public.graph_entities AS current_entity\nLEFT JOIN public.projects AS projects\n ON projects.id = current_entity.project_id\nLEFT JOIN LATERAL (\n SELECT history.*\n FROM public.graph_foundation_revisions AS history\n WHERE history.entity_id = current_entity.id\n AND history.entity_type = current_entity.entity_type\n ORDER BY history.revision::numeric DESC\n LIMIT 1\n) AS history ON true\nWHERE current_entity.id = $1\n AND current_entity.entity_type = $2\nORDER BY history.revision::numeric DESC NULLS LAST\nLIMIT 1\n";
|
|
22
|
+
/**
|
|
23
|
+
* List the newest revision of each Foundation entity visible in the selected
|
|
24
|
+
* project. The project filter is by canonical project code, which is the
|
|
25
|
+
* scope identifier selected by the organization BFF.
|
|
26
|
+
*/
|
|
27
|
+
export declare const GRAPH_FOUNDATION_LIST_SQL = "\nSELECT\n history.entity_id,\n history.entity_type,\n history.revision,\n history.payload,\n history.project_id,\n history.role_min,\n history.sensitivity,\n history.lifecycle_status,\n history.storage_digest,\n history.captured_at,\n (\n history.storage_digest = 'sha256:' ||\n encode(sha256(convert_to(history.payload::text, 'UTF8')), 'hex')\n ) AS storage_digest_valid,\n current_entity.id AS current_entity_id,\n current_entity.entity_type AS current_entity_type,\n current_entity.payload AS current_payload,\n current_entity.project_id AS current_project_id,\n current_entity.role_min AS current_role_min,\n current_entity.sensitivity AS current_sensitivity,\n current_entity.lifecycle_status AS current_lifecycle_status,\n projects.code AS current_project_code,\n true AS current_visible\nFROM public.graph_entities AS current_entity\nLEFT JOIN public.projects AS projects\n ON projects.id = current_entity.project_id\nLEFT JOIN LATERAL (\n SELECT history.*\n FROM public.graph_foundation_revisions AS history\n WHERE history.entity_id = current_entity.id\n AND history.entity_type = current_entity.entity_type\n ORDER BY history.revision::numeric DESC\n LIMIT 1\n) AS history ON true\nWHERE ($1::text IS NULL OR current_entity.entity_type = $1)\n AND ($2::text IS NULL OR projects.code = $2 OR projects.id IS NULL)\n AND current_entity.entity_type IN ('objective', 'variable', 'model', 'constraint')\nORDER BY current_entity.id, current_entity.entity_type\n";
|
|
16
28
|
/** Flat row shape returned by GRAPH_FOUNDATION_READ_SQL. */
|
|
17
29
|
export interface GraphFoundationHistoryRow {
|
|
18
30
|
readonly entity_id?: unknown;
|
|
@@ -46,9 +58,11 @@ export interface GraphFoundationReaderOptions {
|
|
|
46
58
|
readonly context: FoundationStoreContext;
|
|
47
59
|
/** Query callback bound to the host's already-authenticated transaction. */
|
|
48
60
|
readonly query: GraphFoundationQuery;
|
|
61
|
+
/** Canonical project code selected by the organization boundary. */
|
|
62
|
+
readonly selectedProjectCode?: string;
|
|
49
63
|
}
|
|
50
64
|
export interface GraphFoundationReaders {
|
|
51
|
-
readonly store: Pick<FoundationRevisionStore, 'read'>;
|
|
65
|
+
readonly store: Pick<FoundationRevisionStore, 'read' | 'readLatest' | 'list'>;
|
|
52
66
|
readonly philosophyReader: PhilosophyRevisionReader;
|
|
53
67
|
}
|
|
54
68
|
/**
|
|
@@ -50,6 +50,98 @@ WHERE history.entity_id = $1
|
|
|
50
50
|
AND history.entity_type = $2
|
|
51
51
|
AND history.revision = $3
|
|
52
52
|
`;
|
|
53
|
+
/**
|
|
54
|
+
* Read the newest immutable revision for one Foundation entity. The current
|
|
55
|
+
* Graph row remains part of the query so RLS and the adapter's current-row
|
|
56
|
+
* checks apply identically to latest and historical reads.
|
|
57
|
+
*/
|
|
58
|
+
export const GRAPH_FOUNDATION_LATEST_SQL = `
|
|
59
|
+
SELECT
|
|
60
|
+
history.entity_id,
|
|
61
|
+
history.entity_type,
|
|
62
|
+
history.revision,
|
|
63
|
+
history.payload,
|
|
64
|
+
history.project_id,
|
|
65
|
+
history.role_min,
|
|
66
|
+
history.sensitivity,
|
|
67
|
+
history.lifecycle_status,
|
|
68
|
+
history.storage_digest,
|
|
69
|
+
history.captured_at,
|
|
70
|
+
(
|
|
71
|
+
history.storage_digest = 'sha256:' ||
|
|
72
|
+
encode(sha256(convert_to(history.payload::text, 'UTF8')), 'hex')
|
|
73
|
+
) AS storage_digest_valid,
|
|
74
|
+
current_entity.id AS current_entity_id,
|
|
75
|
+
current_entity.entity_type AS current_entity_type,
|
|
76
|
+
current_entity.payload AS current_payload,
|
|
77
|
+
current_entity.project_id AS current_project_id,
|
|
78
|
+
current_entity.role_min AS current_role_min,
|
|
79
|
+
current_entity.sensitivity AS current_sensitivity,
|
|
80
|
+
current_entity.lifecycle_status AS current_lifecycle_status,
|
|
81
|
+
projects.code AS current_project_code,
|
|
82
|
+
true AS current_visible
|
|
83
|
+
FROM public.graph_entities AS current_entity
|
|
84
|
+
LEFT JOIN public.projects AS projects
|
|
85
|
+
ON projects.id = current_entity.project_id
|
|
86
|
+
LEFT JOIN LATERAL (
|
|
87
|
+
SELECT history.*
|
|
88
|
+
FROM public.graph_foundation_revisions AS history
|
|
89
|
+
WHERE history.entity_id = current_entity.id
|
|
90
|
+
AND history.entity_type = current_entity.entity_type
|
|
91
|
+
ORDER BY history.revision::numeric DESC
|
|
92
|
+
LIMIT 1
|
|
93
|
+
) AS history ON true
|
|
94
|
+
WHERE current_entity.id = $1
|
|
95
|
+
AND current_entity.entity_type = $2
|
|
96
|
+
ORDER BY history.revision::numeric DESC NULLS LAST
|
|
97
|
+
LIMIT 1
|
|
98
|
+
`;
|
|
99
|
+
/**
|
|
100
|
+
* List the newest revision of each Foundation entity visible in the selected
|
|
101
|
+
* project. The project filter is by canonical project code, which is the
|
|
102
|
+
* scope identifier selected by the organization BFF.
|
|
103
|
+
*/
|
|
104
|
+
export const GRAPH_FOUNDATION_LIST_SQL = `
|
|
105
|
+
SELECT
|
|
106
|
+
history.entity_id,
|
|
107
|
+
history.entity_type,
|
|
108
|
+
history.revision,
|
|
109
|
+
history.payload,
|
|
110
|
+
history.project_id,
|
|
111
|
+
history.role_min,
|
|
112
|
+
history.sensitivity,
|
|
113
|
+
history.lifecycle_status,
|
|
114
|
+
history.storage_digest,
|
|
115
|
+
history.captured_at,
|
|
116
|
+
(
|
|
117
|
+
history.storage_digest = 'sha256:' ||
|
|
118
|
+
encode(sha256(convert_to(history.payload::text, 'UTF8')), 'hex')
|
|
119
|
+
) AS storage_digest_valid,
|
|
120
|
+
current_entity.id AS current_entity_id,
|
|
121
|
+
current_entity.entity_type AS current_entity_type,
|
|
122
|
+
current_entity.payload AS current_payload,
|
|
123
|
+
current_entity.project_id AS current_project_id,
|
|
124
|
+
current_entity.role_min AS current_role_min,
|
|
125
|
+
current_entity.sensitivity AS current_sensitivity,
|
|
126
|
+
current_entity.lifecycle_status AS current_lifecycle_status,
|
|
127
|
+
projects.code AS current_project_code,
|
|
128
|
+
true AS current_visible
|
|
129
|
+
FROM public.graph_entities AS current_entity
|
|
130
|
+
LEFT JOIN public.projects AS projects
|
|
131
|
+
ON projects.id = current_entity.project_id
|
|
132
|
+
LEFT JOIN LATERAL (
|
|
133
|
+
SELECT history.*
|
|
134
|
+
FROM public.graph_foundation_revisions AS history
|
|
135
|
+
WHERE history.entity_id = current_entity.id
|
|
136
|
+
AND history.entity_type = current_entity.entity_type
|
|
137
|
+
ORDER BY history.revision::numeric DESC
|
|
138
|
+
LIMIT 1
|
|
139
|
+
) AS history ON true
|
|
140
|
+
WHERE ($1::text IS NULL OR current_entity.entity_type = $1)
|
|
141
|
+
AND ($2::text IS NULL OR projects.code = $2 OR projects.id IS NULL)
|
|
142
|
+
AND current_entity.entity_type IN ('objective', 'variable', 'model', 'constraint')
|
|
143
|
+
ORDER BY current_entity.id, current_entity.entity_type
|
|
144
|
+
`;
|
|
53
145
|
const FOUNDATION_TYPES = ['objective', 'variable', 'model', 'constraint'];
|
|
54
146
|
const REVISION_PATTERN = /^[1-9]\d*$/u;
|
|
55
147
|
const DIGEST_PATTERN = /^sha256:[0-9a-f]{64}$/u;
|
|
@@ -65,6 +157,7 @@ export function createGraphFoundationReaders(options) {
|
|
|
65
157
|
assertReaderOptions(options);
|
|
66
158
|
const context = cloneContext(options.context);
|
|
67
159
|
const query = options.query;
|
|
160
|
+
const selectedProjectCode = options.selectedProjectCode;
|
|
68
161
|
const store = {
|
|
69
162
|
async read(reference, suppliedContext) {
|
|
70
163
|
assertTrustedPrincipal(context, suppliedContext);
|
|
@@ -75,20 +168,42 @@ export function createGraphFoundationReaders(options) {
|
|
|
75
168
|
if (rows.length !== 1) {
|
|
76
169
|
throw corrupt('Graph foundation history returned multiple rows for one revision');
|
|
77
170
|
}
|
|
171
|
+
return validateFoundationRecord(rows[0], reference, context, selectedProjectCode);
|
|
172
|
+
},
|
|
173
|
+
async readLatest(type, id, suppliedContext) {
|
|
174
|
+
assertTrustedPrincipal(context, suppliedContext);
|
|
175
|
+
assertFoundationType(type);
|
|
176
|
+
assertEntityId(id);
|
|
177
|
+
const rows = await executeLatestRead(query, id, type);
|
|
178
|
+
if (rows.length === 0)
|
|
179
|
+
return null;
|
|
180
|
+
if (rows.length !== 1) {
|
|
181
|
+
throw corrupt('Graph foundation latest query returned multiple rows');
|
|
182
|
+
}
|
|
78
183
|
const row = rows[0];
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
// A history row remains readable only through the current Graph row,
|
|
83
|
-
// including its current project and current scope. The old payload is
|
|
84
|
-
// returned unchanged after these current-row checks.
|
|
85
|
-
if (history.projectId !== current.projectId) {
|
|
86
|
-
throw scopeViolation('Historical foundation project differs from current Graph project');
|
|
184
|
+
const reference = rowReference(row);
|
|
185
|
+
if (reference.id !== id || reference.type !== type) {
|
|
186
|
+
throw corrupt('Graph foundation latest row identity does not match the request');
|
|
87
187
|
}
|
|
88
|
-
return
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
188
|
+
return validateFoundationRecord(row, reference, context, selectedProjectCode, true);
|
|
189
|
+
},
|
|
190
|
+
async list(type, suppliedContext) {
|
|
191
|
+
assertTrustedPrincipal(context, suppliedContext);
|
|
192
|
+
if (type !== undefined)
|
|
193
|
+
assertFoundationType(type);
|
|
194
|
+
const rows = await executeList(query, type, selectedProjectCode);
|
|
195
|
+
const seen = new Set();
|
|
196
|
+
return rows.map((row) => {
|
|
197
|
+
const reference = rowReference(row);
|
|
198
|
+
if (type !== undefined && reference.type !== type) {
|
|
199
|
+
throw corrupt('Graph foundation list returned an unexpected entity type');
|
|
200
|
+
}
|
|
201
|
+
const key = `${reference.type}:${reference.id}`;
|
|
202
|
+
if (seen.has(key))
|
|
203
|
+
throw corrupt('Graph foundation list returned duplicate entity rows');
|
|
204
|
+
seen.add(key);
|
|
205
|
+
return validateFoundationRecord(row, reference, context, selectedProjectCode, true);
|
|
206
|
+
});
|
|
92
207
|
}
|
|
93
208
|
};
|
|
94
209
|
const philosophyReader = {
|
|
@@ -254,6 +369,66 @@ async function executeRead(query, id, type, revision) {
|
|
|
254
369
|
}
|
|
255
370
|
throw corrupt('Graph foundation history query returned an invalid result');
|
|
256
371
|
}
|
|
372
|
+
async function executeLatestRead(query, id, type) {
|
|
373
|
+
let result;
|
|
374
|
+
try {
|
|
375
|
+
result = await query(GRAPH_FOUNDATION_LATEST_SQL, [id, type]);
|
|
376
|
+
}
|
|
377
|
+
catch {
|
|
378
|
+
throw corrupt('Graph foundation latest query failed');
|
|
379
|
+
}
|
|
380
|
+
if (Array.isArray(result))
|
|
381
|
+
return result;
|
|
382
|
+
if (!Array.isArray(result) && 'rows' in result && Array.isArray(result.rows)) {
|
|
383
|
+
return result.rows;
|
|
384
|
+
}
|
|
385
|
+
throw corrupt('Graph foundation latest query returned an invalid result');
|
|
386
|
+
}
|
|
387
|
+
async function executeList(query, type, selectedProjectCode) {
|
|
388
|
+
let result;
|
|
389
|
+
try {
|
|
390
|
+
result = await query(GRAPH_FOUNDATION_LIST_SQL, [type ?? null, selectedProjectCode ?? null]);
|
|
391
|
+
}
|
|
392
|
+
catch {
|
|
393
|
+
throw corrupt('Graph foundation list query failed');
|
|
394
|
+
}
|
|
395
|
+
if (Array.isArray(result))
|
|
396
|
+
return result;
|
|
397
|
+
if (!Array.isArray(result) && 'rows' in result && Array.isArray(result.rows)) {
|
|
398
|
+
return result.rows;
|
|
399
|
+
}
|
|
400
|
+
throw corrupt('Graph foundation list query returned an invalid result');
|
|
401
|
+
}
|
|
402
|
+
function validateFoundationRecord(row, reference, context, selectedProjectCode, requireCurrentRevision = false) {
|
|
403
|
+
const history = validateHistoryRow(row, reference.id, reference.type, reference.revision);
|
|
404
|
+
const current = validateCurrentRow(row, reference.id, reference.type);
|
|
405
|
+
if (requireCurrentRevision && current.projectCode === undefined) {
|
|
406
|
+
throw corrupt('Current Graph project code is missing');
|
|
407
|
+
}
|
|
408
|
+
authorizeCurrentFoundation(current, context, selectedProjectCode);
|
|
409
|
+
// Latest reads must prove that the current Graph payload points at the
|
|
410
|
+
// revision returned by the history query. Otherwise a missing newer history
|
|
411
|
+
// row could silently fall back to an older immutable revision.
|
|
412
|
+
if (requireCurrentRevision && current.foundation?.revision !== reference.revision) {
|
|
413
|
+
throw corrupt('Current Graph foundation revision does not match the latest history revision');
|
|
414
|
+
}
|
|
415
|
+
// A history row remains readable only through the current Graph row,
|
|
416
|
+
// including its current project and current scope. The old payload is
|
|
417
|
+
// returned unchanged after these current-row checks.
|
|
418
|
+
if (history.projectId !== current.projectId) {
|
|
419
|
+
throw scopeViolation('Historical foundation project differs from current Graph project');
|
|
420
|
+
}
|
|
421
|
+
return {
|
|
422
|
+
definition: cloneFoundationDefinition(history.definition),
|
|
423
|
+
digest: digestFoundationDefinition(history.definition)
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
function rowReference(row) {
|
|
427
|
+
const id = requireString(row.entity_id, 'Foundation history entity_id');
|
|
428
|
+
const type = parseFoundationType(row.entity_type);
|
|
429
|
+
const revision = requireRevision(row.revision, 'Foundation history revision');
|
|
430
|
+
return { id, type, revision };
|
|
431
|
+
}
|
|
257
432
|
function validateHistoryRow(row, id, type, revision) {
|
|
258
433
|
assertRowIdentity(row, id, type, revision);
|
|
259
434
|
assertStorageDigest(row);
|
|
@@ -301,21 +476,27 @@ function validateCurrentRow(row, id, type) {
|
|
|
301
476
|
throw new FoundationStoreError('authorization_denied', 'The current Graph row is not active');
|
|
302
477
|
}
|
|
303
478
|
const projectId = requireString(row.current_project_id, 'Current Graph project_id');
|
|
479
|
+
const projectCode = typeof row.current_project_code === 'string' && row.current_project_code.length > 0
|
|
480
|
+
? row.current_project_code
|
|
481
|
+
: undefined;
|
|
304
482
|
const payload = requireObject(row.current_payload, 'Current Graph payload');
|
|
305
483
|
if (type === 'philosophy') {
|
|
306
484
|
const applicability = parseJudgmentApplicability(payload);
|
|
307
|
-
return { payload, projectId, philosophy: { applicability } };
|
|
485
|
+
return { payload, projectId, projectCode, philosophy: { applicability } };
|
|
308
486
|
}
|
|
309
487
|
const definition = requireObject(payload.foundation, 'Current Graph payload.foundation');
|
|
310
488
|
const result = validateFoundationDefinition(definition, { use: 'draft' });
|
|
311
489
|
if (!result.valid || definition.id !== id || definition.type !== type) {
|
|
312
490
|
throw corrupt('Current Graph foundation definition is invalid');
|
|
313
491
|
}
|
|
314
|
-
return { payload, projectId, foundation: definition };
|
|
492
|
+
return { payload, projectId, projectCode, foundation: definition };
|
|
315
493
|
}
|
|
316
|
-
function authorizeCurrentFoundation(current, context) {
|
|
494
|
+
function authorizeCurrentFoundation(current, context, selectedProjectCode) {
|
|
317
495
|
if (!current.foundation)
|
|
318
496
|
throw corrupt('Current Graph foundation definition is missing');
|
|
497
|
+
if (selectedProjectCode !== undefined && current.projectCode !== selectedProjectCode) {
|
|
498
|
+
throw scopeViolation('The current Graph project is outside the selected project scope');
|
|
499
|
+
}
|
|
319
500
|
const definition = current.foundation;
|
|
320
501
|
if (!canRead(definition.acl, context.principal)) {
|
|
321
502
|
throw new FoundationStoreError('authorization_denied', 'The current foundation ACL does not permit this principal');
|
|
@@ -334,6 +515,28 @@ function assertRowIdentity(row, id, type, revision) {
|
|
|
334
515
|
throw corrupt('Foundation history identity does not match the requested revision');
|
|
335
516
|
}
|
|
336
517
|
}
|
|
518
|
+
function parseFoundationType(value) {
|
|
519
|
+
if (typeof value === 'string' && FOUNDATION_TYPES.includes(value)) {
|
|
520
|
+
return value;
|
|
521
|
+
}
|
|
522
|
+
throw corrupt('Foundation history entity_type is invalid');
|
|
523
|
+
}
|
|
524
|
+
function requireRevision(value, label) {
|
|
525
|
+
if (typeof value !== 'string' || !REVISION_PATTERN.test(value)) {
|
|
526
|
+
throw corrupt(`${label} must be a positive revision`);
|
|
527
|
+
}
|
|
528
|
+
return value;
|
|
529
|
+
}
|
|
530
|
+
function assertEntityId(value) {
|
|
531
|
+
if (typeof value !== 'string' || value.trim().length === 0 || value.includes('\0')) {
|
|
532
|
+
throw new FoundationStoreError('invalid_input', 'A foundation entity id is required');
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
function assertFoundationType(value) {
|
|
536
|
+
if (!FOUNDATION_TYPES.includes(value)) {
|
|
537
|
+
throw new FoundationStoreError('invalid_input', 'A supported foundation type is required');
|
|
538
|
+
}
|
|
539
|
+
}
|
|
337
540
|
function assertFoundationReference(reference) {
|
|
338
541
|
if (!isPlainRecord(reference) || typeof reference.id !== 'string' || reference.id.length === 0 ||
|
|
339
542
|
!FOUNDATION_TYPES.includes(reference.type) ||
|
|
@@ -365,6 +568,10 @@ function assertReaderOptions(options) {
|
|
|
365
568
|
if (!isValidFoundationScope(scope))
|
|
366
569
|
throw new TypeError('A trusted Graph foundation scope is invalid');
|
|
367
570
|
}
|
|
571
|
+
if (options.selectedProjectCode !== undefined
|
|
572
|
+
&& (typeof options.selectedProjectCode !== 'string' || options.selectedProjectCode.trim().length === 0)) {
|
|
573
|
+
throw new TypeError('A selected Graph project code is invalid');
|
|
574
|
+
}
|
|
368
575
|
}
|
|
369
576
|
function assertTrustedPrincipal(trusted, supplied) {
|
|
370
577
|
if (!supplied || supplied.principal !== trusted.principal) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unson/brainbase-mcp",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.5",
|
|
4
4
|
"description": "Local-first Brainbase judgment OS that lets supported AI agents reuse a user's approved goals, priorities, decisions, and judgment criteria.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -336,5 +336,5 @@
|
|
|
336
336
|
"engines": {
|
|
337
337
|
"node": ">=20"
|
|
338
338
|
},
|
|
339
|
-
"gitHead": "
|
|
339
|
+
"gitHead": "8cfad04bef7bf934165d96b979e9e5d52dd837dc"
|
|
340
340
|
}
|