@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.4",
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": "985c9ff2304a73b0010f86049d3420429cb2e50d"
426
+ "gitHead": "d475d6282eda01482255c0a234999e194560d3c8"
423
427
  }
@@ -606,7 +606,9 @@ function aggregateNormalJudgmentHistory(home, { now = new Date(), period = PERIO
606
606
  confirmedOutcomes: null,
607
607
  },
608
608
  visibleCount: 0,
609
- totalCount: home.coverage?.total ?? (home.coverage?.complete === true && home.records.length === 0 ? 0 : null),
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
- const judgments = metricsUnconfirmed ? null : rows.length;
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
- totalCount: home.coverage?.total ?? (home.coverage?.complete === true && home.records.length === 0 ? 0 : null),
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 ? '同じ範囲の件数は未確認' : `同じ範囲の件数: ${total}件`;
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
- { label: '判断件数', value: metricCount(stats.judgments, '件'), note: 'この期間・絞り込みの履歴' },
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
- ? 'この期間に取得できた判断履歴はありません。未接続・一部取得のため0件とは確認できません。'
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',