@unson/brainbase-mcp 0.23.4 → 0.23.6
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,3 +1,4 @@
|
|
|
1
|
+
import { assertPrivatePhilosophyRead, privatePhilosophyAcl } from './private-philosophy-graph.js';
|
|
1
2
|
import { cloneFoundationDefinition, digestFoundationDefinition } from './foundation-catalog.js';
|
|
2
3
|
import { canonicalPortableJson } from './portable-graph.js';
|
|
3
4
|
import { FoundationStoreError } from './foundation-store.js';
|
|
@@ -295,7 +296,7 @@ function createPhilosophyRevisionRecord(validated, id, revision, principal) {
|
|
|
295
296
|
applicability: validated.history.applicability,
|
|
296
297
|
// This is a read-time projection of the current Graph permission. It
|
|
297
298
|
// does not mutate the canonical philosophy payload or Graph ACL.
|
|
298
|
-
currentAcl: privateCurrentAcl(principal),
|
|
299
|
+
currentAcl: privatePhilosophyAcl(validated.current.payload) ?? privateCurrentAcl(principal),
|
|
299
300
|
currentScope: cloneJudgmentScope(validated.history.applicability.scope)
|
|
300
301
|
};
|
|
301
302
|
}
|
|
@@ -337,7 +338,7 @@ async function readCanonicalPhilosophyRevision(input, trustedContext, query) {
|
|
|
337
338
|
applicability: validated.history.applicability,
|
|
338
339
|
// This is a read-time projection of the current Graph permission. It
|
|
339
340
|
// does not mutate the canonical philosophy payload or Graph ACL.
|
|
340
|
-
currentAcl: privateCurrentAcl(trustedContext.principal),
|
|
341
|
+
currentAcl: privatePhilosophyAcl(validated.current.payload) ?? privateCurrentAcl(trustedContext.principal),
|
|
341
342
|
currentScope: cloneJudgmentScope(validated.history.applicability.scope)
|
|
342
343
|
}
|
|
343
344
|
};
|
|
@@ -398,7 +399,7 @@ async function readPhilosophyRevision(input, trustedContext, query) {
|
|
|
398
399
|
applicability: validated.history.applicability,
|
|
399
400
|
// This is a read-time projection of the current Graph permission. It
|
|
400
401
|
// does not mutate the canonical philosophy payload or Graph ACL.
|
|
401
|
-
currentAcl: privateCurrentAcl(trustedContext.principal),
|
|
402
|
+
currentAcl: privatePhilosophyAcl(validated.current.payload) ?? privateCurrentAcl(trustedContext.principal),
|
|
402
403
|
currentScope: cloneJudgmentScope(reference.scope)
|
|
403
404
|
}
|
|
404
405
|
};
|
|
@@ -414,6 +415,10 @@ async function readPhilosophyRevision(input, trustedContext, query) {
|
|
|
414
415
|
function validatePhilosophyHistoryAccess(row, id, revision, trustedContext) {
|
|
415
416
|
const history = validatePhilosophyHistoryRow(row, { id, revision });
|
|
416
417
|
const current = validateCurrentRow(row, id, 'philosophy');
|
|
418
|
+
const historicalAcl = assertPrivatePhilosophyRead(history.payload, trustedContext.principal);
|
|
419
|
+
const currentAcl = assertPrivatePhilosophyRead(current.payload, trustedContext.principal);
|
|
420
|
+
if (historicalAcl && !currentAcl)
|
|
421
|
+
throw new FoundationStoreError('authorization_denied', 'Private philosophy history cannot become legacy');
|
|
417
422
|
const philosophy = current.philosophy;
|
|
418
423
|
if (!philosophy) {
|
|
419
424
|
throw corrupt('Current Graph philosophy applicability is missing');
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { FoundationAcl } from './ontology-foundation.js';
|
|
2
|
+
/** An explicit ACL opts a philosophy into owner-only storage; absence is legacy. */
|
|
3
|
+
export declare function privatePhilosophyAcl(payload: unknown): FoundationAcl | undefined;
|
|
4
|
+
export declare function assertPrivatePhilosophyRead(payload: unknown, principal: string): FoundationAcl | undefined;
|
|
5
|
+
/** Validate the existing Graph writer, without changing legacy organization philosophy. */
|
|
6
|
+
export declare function assertPrivatePhilosophyWrite(input: {
|
|
7
|
+
payload: Record<string, unknown>;
|
|
8
|
+
currentPayload?: Record<string, unknown>;
|
|
9
|
+
principal: string;
|
|
10
|
+
projectCode: string;
|
|
11
|
+
currentVersion: number;
|
|
12
|
+
expectedVersion?: number;
|
|
13
|
+
}): void;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { FoundationStoreError } from './foundation-store.js';
|
|
2
|
+
import { canonicalPortableJson } from './portable-graph.js';
|
|
3
|
+
import { philosophyRevisionDigest } from './philosophy-revision-reader.js';
|
|
4
|
+
const record = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
5
|
+
const present = (value) => Object.hasOwn(value, 'acl');
|
|
6
|
+
const fail = (code, message) => {
|
|
7
|
+
throw new FoundationStoreError(code, message);
|
|
8
|
+
};
|
|
9
|
+
/** An explicit ACL opts a philosophy into owner-only storage; absence is legacy. */
|
|
10
|
+
export function privatePhilosophyAcl(payload) {
|
|
11
|
+
if (!record(payload))
|
|
12
|
+
return fail('invalid_input', 'Philosophy payload must be an object');
|
|
13
|
+
if (!present(payload))
|
|
14
|
+
return undefined;
|
|
15
|
+
const acl = payload.acl;
|
|
16
|
+
if (!record(acl) || typeof acl.ownerId !== 'string' || !acl.ownerId.trim()
|
|
17
|
+
|| acl.visibility !== 'private' || !Array.isArray(acl.readerIds) || acl.readerIds.length !== 0
|
|
18
|
+
|| !Array.isArray(acl.writerIds) || acl.writerIds.length !== 0) {
|
|
19
|
+
return fail('invalid_input', 'Private philosophy requires one owner and no additional readers or writers');
|
|
20
|
+
}
|
|
21
|
+
return { ownerId: acl.ownerId, visibility: 'private', readerIds: [], writerIds: [] };
|
|
22
|
+
}
|
|
23
|
+
export function assertPrivatePhilosophyRead(payload, principal) {
|
|
24
|
+
const acl = privatePhilosophyAcl(payload);
|
|
25
|
+
if (acl && acl.ownerId !== principal)
|
|
26
|
+
fail('authorization_denied', 'Private philosophy is owner-only');
|
|
27
|
+
return acl;
|
|
28
|
+
}
|
|
29
|
+
/** Validate the existing Graph writer, without changing legacy organization philosophy. */
|
|
30
|
+
export function assertPrivatePhilosophyWrite(input) {
|
|
31
|
+
const currentAcl = input.currentPayload ? privatePhilosophyAcl(input.currentPayload) : undefined;
|
|
32
|
+
const nextAcl = privatePhilosophyAcl(input.payload);
|
|
33
|
+
if (!currentAcl && !nextAcl)
|
|
34
|
+
return;
|
|
35
|
+
if (!nextAcl || (currentAcl && currentAcl.ownerId !== input.principal)
|
|
36
|
+
|| nextAcl.ownerId !== input.principal)
|
|
37
|
+
fail('authorization_denied', 'Private philosophy owner cannot change');
|
|
38
|
+
if (!Number.isSafeInteger(input.expectedVersion) || input.expectedVersion !== input.currentVersion) {
|
|
39
|
+
fail('revision_conflict', 'Private philosophy requires the exact current Graph version');
|
|
40
|
+
}
|
|
41
|
+
if (currentAcl && canonicalPortableJson(currentAcl) !== canonicalPortableJson(nextAcl)) {
|
|
42
|
+
fail('authorization_denied', 'Private philosophy ACL cannot change');
|
|
43
|
+
}
|
|
44
|
+
if (typeof input.payload.statement !== 'string' || !input.payload.statement.trim()) {
|
|
45
|
+
fail('invalid_input', 'Private philosophy statement is required');
|
|
46
|
+
}
|
|
47
|
+
const applicability = input.payload.judgmentApplicability;
|
|
48
|
+
if (!record(applicability) || !record(applicability.scope)
|
|
49
|
+
|| applicability.scope.type !== 'project' || applicability.scope.id !== input.projectCode) {
|
|
50
|
+
fail('authorization_denied', 'Private philosophy must remain in its authenticated project');
|
|
51
|
+
}
|
|
52
|
+
// Reuse the immutable revision reader's strict applicability/date contract.
|
|
53
|
+
philosophyRevisionDigest({ kind: 'philosophy', id: 'private-philosophy-validation', revision: '1',
|
|
54
|
+
payload: input.payload, applicability: applicability });
|
|
55
|
+
if (currentAcl && canonicalPortableJson(input.currentPayload.judgmentApplicability)
|
|
56
|
+
!== canonicalPortableJson(applicability))
|
|
57
|
+
fail('authorization_denied', 'Private philosophy scope cannot change');
|
|
58
|
+
if (input.currentPayload && !currentAcl)
|
|
59
|
+
fail('authorization_denied', 'Legacy philosophy privacy requires a reviewed migration');
|
|
60
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unson/brainbase-mcp",
|
|
3
|
-
"version": "0.23.
|
|
3
|
+
"version": "0.23.6",
|
|
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",
|
|
@@ -347,6 +347,10 @@
|
|
|
347
347
|
"./meeting-minutes-storage-http": {
|
|
348
348
|
"types": "./dist/meeting-minutes-storage-http.d.ts",
|
|
349
349
|
"import": "./dist/meeting-minutes-storage-http.js"
|
|
350
|
+
},
|
|
351
|
+
"./private-philosophy-graph": {
|
|
352
|
+
"types": "./dist/private-philosophy-graph.d.ts",
|
|
353
|
+
"import": "./dist/private-philosophy-graph.js"
|
|
350
354
|
}
|
|
351
355
|
},
|
|
352
356
|
"bin": {
|
|
@@ -419,5 +423,5 @@
|
|
|
419
423
|
"engines": {
|
|
420
424
|
"node": ">=20"
|
|
421
425
|
},
|
|
422
|
-
"gitHead": "
|
|
426
|
+
"gitHead": "d475d6282eda01482255c0a234999e194560d3c8"
|
|
423
427
|
}
|
package/ui/judgment-history.js
CHANGED
|
@@ -606,7 +606,9 @@ function aggregateNormalJudgmentHistory(home, { now = new Date(), period = PERIO
|
|
|
606
606
|
confirmedOutcomes: null,
|
|
607
607
|
},
|
|
608
608
|
visibleCount: 0,
|
|
609
|
-
|
|
609
|
+
retrievedCount: 0,
|
|
610
|
+
countComplete: false,
|
|
611
|
+
totalCount: home.status === 'available' && home.coverage?.complete === true ? nonNegativeInteger(home.coverage?.total) : null,
|
|
610
612
|
period: normalizedPeriod(period),
|
|
611
613
|
query: text(query),
|
|
612
614
|
status: home.status,
|
|
@@ -660,7 +662,10 @@ function aggregateNormalJudgmentHistory(home, { now = new Date(), period = PERIO
|
|
|
660
662
|
}
|
|
661
663
|
}
|
|
662
664
|
const metricsUnconfirmed = home.status === 'partial' && rows.length === 0;
|
|
663
|
-
|
|
665
|
+
// A page length is a retrieved count, never proof of the whole range.
|
|
666
|
+
const countComplete = home.status === 'available' && home.coverage?.complete === true
|
|
667
|
+
&& nonNegativeInteger(home.coverage?.total) !== null && !needle;
|
|
668
|
+
const judgments = countComplete ? home.coverage.total : null;
|
|
664
669
|
const referenceCount = metricsUnconfirmed || referencesUnconfirmed ? null : references.size;
|
|
665
670
|
const confirmedOutcomes = metricsUnconfirmed
|
|
666
671
|
? null
|
|
@@ -680,7 +685,9 @@ function aggregateNormalJudgmentHistory(home, { now = new Date(), period = PERIO
|
|
|
680
685
|
confirmedOutcomes,
|
|
681
686
|
},
|
|
682
687
|
visibleCount: rows.length,
|
|
683
|
-
|
|
688
|
+
retrievedCount: rows.length,
|
|
689
|
+
countComplete,
|
|
690
|
+
totalCount: home.status === 'available' && home.coverage?.complete === true ? nonNegativeInteger(home.coverage?.total) : null,
|
|
684
691
|
};
|
|
685
692
|
}
|
|
686
693
|
|
|
@@ -1328,7 +1335,7 @@ function paginationControls(doc, aggregate, onNext) {
|
|
|
1328
1335
|
const total = aggregate?.totalCount;
|
|
1329
1336
|
if (!pagination?.hasNext && total === null) return null;
|
|
1330
1337
|
const wrap = makeWorkspaceElement(doc, 'div', { className: 'bb-jh-pagination' });
|
|
1331
|
-
const count = total === null ? '同じ範囲の件数は未確認' :
|
|
1338
|
+
const count = total === null ? '同じ範囲の件数は未確認' : `${aggregate.query ? '検索前の全件数' : '同じ範囲の件数'}: ${total}件`;
|
|
1332
1339
|
wrap.append(makeWorkspaceElement(doc, 'span', { className: 'bb-jh-pagination-count', text: count }));
|
|
1333
1340
|
if (pagination?.hasNext) {
|
|
1334
1341
|
wrap.append(workspaceButton(doc, { text: '次のページ', variant: 'quiet', onClick: onNext, attrs: { 'data-action': 'next-page' } }));
|
|
@@ -1709,7 +1716,12 @@ export function createJudgmentHistoryUI({
|
|
|
1709
1716
|
const stats = aggregate.stats ?? {};
|
|
1710
1717
|
const metricCount = (value, suffix) => value === null || value === undefined ? '未確認' : `${value}${suffix}`;
|
|
1711
1718
|
wrapper.append(workspaceMetrics(doc, normalMode() ? [
|
|
1712
|
-
{
|
|
1719
|
+
{
|
|
1720
|
+
label: aggregate.query ? '表示対象' : aggregate.countComplete ? '判断件数' : '取得済み',
|
|
1721
|
+
value: metricCount(aggregate.countComplete ? stats.judgments : aggregate.retrievedCount, '件'),
|
|
1722
|
+
note: aggregate.countComplete ? 'この期間・絞り込みの全件数'
|
|
1723
|
+
: aggregate.query ? '取得済み履歴の検索結果・全件数未確認' : '全件数未確認',
|
|
1724
|
+
},
|
|
1713
1725
|
{ label: '使った参照', value: metricCount(stats.references, '件'), note: '当時の記録にある一意の参照' },
|
|
1714
1726
|
{ label: '確認済みの結果', value: metricCount(stats.confirmedOutcomes, '件'), note: '成果が確認済みと記録された判断' },
|
|
1715
1727
|
] : [
|
|
@@ -1717,8 +1729,8 @@ export function createJudgmentHistoryUI({
|
|
|
1717
1729
|
{ label: 'そこで使った参照', value: metricCount(stats.references, '件'), note: '代理判断に含まれる一意の参照' },
|
|
1718
1730
|
{ label: '判断回数にすると', value: metricCount(stats.equivalent, '回分相当'), note: '代理判断1件を1回分として表示' },
|
|
1719
1731
|
], { ariaLabel: '判断履歴の集計' }));
|
|
1720
|
-
const emptyText = state.home?.status === 'partial'
|
|
1721
|
-
? '
|
|
1732
|
+
const emptyText = state.home?.status === 'partial' || (normalMode() && !aggregate.countComplete)
|
|
1733
|
+
? 'この期間に取得できた判断履歴はありません。全件数が未確認のため0件とは確認できません。'
|
|
1722
1734
|
: 'この期間の判断履歴はありません。';
|
|
1723
1735
|
wrapper.append(workspaceLedger(doc, {
|
|
1724
1736
|
className: 'bb-jh-ledger',
|