@unson/brainbase-mcp 0.41.7 → 0.41.9

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.
@@ -253,13 +253,17 @@ function createFoundationCatalogRoute(readers, scopeId, context) {
253
253
  const variables = await readers.store.list('variable', context);
254
254
  const models = await readers.store.list('model', context);
255
255
  const philosophies = await readers.listPhilosophies(context);
256
+ // Philosophies without recorded applicability are still read by the
257
+ // judgment frame catalog; report them separately rather than as absent.
258
+ const philosophiesWithoutApplicability = await readers.listUnscopedPhilosophies(context);
256
259
  return jsonResponse(200, {
257
260
  contractVersion: FOUNDATION_OVERVIEW_CONTRACT_VERSION,
258
261
  scopeId,
259
262
  objectives,
260
263
  variables,
261
264
  models,
262
- philosophies
265
+ philosophies,
266
+ philosophies_without_applicability: philosophiesWithoutApplicability
263
267
  });
264
268
  }
265
269
  };
@@ -25,7 +25,7 @@ export declare const GRAPH_FOUNDATION_LATEST_SQL = "\nSELECT\n history.entity_i
25
25
  * project. The project filter is by canonical project code, which is the
26
26
  * scope identifier selected by the organization BFF.
27
27
  */
28
- 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";
28
+ 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')\n -- A list projects current records only. A retired row is not current, and\n -- reading it would make one replaced record fail the whole list.\n AND current_entity.lifecycle_status = 'active'\nORDER BY current_entity.id, current_entity.entity_type\n";
29
29
  /**
30
30
  * List the newest immutable revision for each philosophy attached to the
31
31
  * selected project. The current Graph row is still the authorization
@@ -33,7 +33,29 @@ export declare const GRAPH_FOUNDATION_LIST_SQL = "\nSELECT\n history.entity_id,
33
33
  * Unlike the Foundation list, this query requires a canonical project join so
34
34
  * a missing project membership cannot be interpreted as a selected-scope hit.
35
35
  */
36
- export declare const GRAPH_PHILOSOPHY_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\nJOIN 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.entity_type = 'philosophy'\n AND projects.code = $1\n AND current_entity.payload ? 'judgmentApplicability'\nORDER BY current_entity.id\n";
36
+ export declare const GRAPH_PHILOSOPHY_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\nJOIN 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.entity_type = 'philosophy'\n AND projects.code = $1\n AND current_entity.lifecycle_status = 'active'\n AND current_entity.payload ? 'judgmentApplicability'\nORDER BY current_entity.id\n";
37
+ /**
38
+ * Current philosophies of the selected project that predate recorded
39
+ * applicability. They are excluded from the verified list above, but the
40
+ * judgment frame catalog still reads them, so the overview must show them as
41
+ * "applicability unrecorded" instead of reporting that none are registered.
42
+ */
43
+ export declare const GRAPH_UNSCOPED_PHILOSOPHY_LIST_SQL = "\nSELECT\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.lifecycle_status AS current_lifecycle_status,\n current_entity.version AS current_version,\n projects.code AS current_project_code\nFROM public.graph_entities AS current_entity\nJOIN public.projects AS projects\n ON projects.id = current_entity.project_id\nWHERE current_entity.entity_type = 'philosophy'\n AND projects.code = $1\n AND current_entity.lifecycle_status = 'active'\n AND NOT (current_entity.payload ? 'judgmentApplicability')\nORDER BY current_entity.id\n";
44
+ /** A current philosophy whose judgment applicability has never been recorded. */
45
+ export interface UnscopedPhilosophyRecord {
46
+ readonly kind: 'philosophy';
47
+ readonly id: string;
48
+ /** Current Graph version; there is no verified foundation revision. */
49
+ readonly version: number;
50
+ readonly applicability: null;
51
+ readonly applicability_state: 'unrecorded';
52
+ readonly payload: {
53
+ readonly title?: string;
54
+ readonly statement: string;
55
+ readonly summary?: string;
56
+ readonly status?: string;
57
+ };
58
+ }
37
59
  /** Flat row shape returned by GRAPH_FOUNDATION_READ_SQL. */
38
60
  export interface GraphFoundationHistoryRow {
39
61
  readonly entity_id?: unknown;
@@ -55,6 +77,7 @@ export interface GraphFoundationHistoryRow {
55
77
  readonly current_role_min?: unknown;
56
78
  readonly current_sensitivity?: unknown;
57
79
  readonly current_lifecycle_status?: unknown;
80
+ readonly current_version?: unknown;
58
81
  readonly current_visible?: unknown;
59
82
  }
60
83
  export interface GraphFoundationQueryResult {
@@ -78,15 +101,21 @@ export interface FoundationObjectiveDetails {
78
101
  readonly evaluator_note?: unknown;
79
102
  readonly current_state?: unknown;
80
103
  }
81
- /** Runtime shape used by the overview contract for Objective records. */
104
+ /** The words a World Model record gives for where it applies. */
105
+ export interface FoundationModelDetails {
106
+ readonly applicability_text?: unknown;
107
+ }
108
+ /** Runtime shape used by the overview contract for Objective and World Model records. */
82
109
  export interface FoundationOverviewObjectiveRecord extends FoundationCatalogRecord {
83
- readonly details?: FoundationObjectiveDetails;
110
+ readonly details?: FoundationObjectiveDetails | FoundationModelDetails;
84
111
  }
85
112
  export interface GraphFoundationReaders {
86
113
  readonly store: Pick<FoundationRevisionStore, 'read' | 'readLatest' | 'list'>;
87
114
  readonly philosophyReader: PhilosophyRevisionReader;
88
115
  /** List latest, current-visible philosophy revisions for the selected project. */
89
116
  readonly listPhilosophies: (context: FoundationStoreContext) => Promise<PhilosophyRevisionRecord[]>;
117
+ /** List current philosophies of the selected project without recorded applicability. */
118
+ readonly listUnscopedPhilosophies: (context: FoundationStoreContext) => Promise<UnscopedPhilosophyRecord[]>;
90
119
  }
91
120
  /**
92
121
  * Create both public read ports over one trusted, transaction-bound context.
@@ -141,6 +141,9 @@ LEFT JOIN LATERAL (
141
141
  WHERE ($1::text IS NULL OR current_entity.entity_type = $1)
142
142
  AND ($2::text IS NULL OR projects.code = $2 OR projects.id IS NULL)
143
143
  AND current_entity.entity_type IN ('objective', 'variable', 'model', 'constraint')
144
+ -- A list projects current records only. A retired row is not current, and
145
+ -- reading it would make one replaced record fail the whole list.
146
+ AND current_entity.lifecycle_status = 'active'
144
147
  ORDER BY current_entity.id, current_entity.entity_type
145
148
  `;
146
149
  /**
@@ -188,9 +191,34 @@ LEFT JOIN LATERAL (
188
191
  ) AS history ON true
189
192
  WHERE current_entity.entity_type = 'philosophy'
190
193
  AND projects.code = $1
194
+ AND current_entity.lifecycle_status = 'active'
191
195
  AND current_entity.payload ? 'judgmentApplicability'
192
196
  ORDER BY current_entity.id
193
197
  `;
198
+ /**
199
+ * Current philosophies of the selected project that predate recorded
200
+ * applicability. They are excluded from the verified list above, but the
201
+ * judgment frame catalog still reads them, so the overview must show them as
202
+ * "applicability unrecorded" instead of reporting that none are registered.
203
+ */
204
+ export const GRAPH_UNSCOPED_PHILOSOPHY_LIST_SQL = `
205
+ SELECT
206
+ current_entity.id AS current_entity_id,
207
+ current_entity.entity_type AS current_entity_type,
208
+ current_entity.payload AS current_payload,
209
+ current_entity.project_id AS current_project_id,
210
+ current_entity.lifecycle_status AS current_lifecycle_status,
211
+ current_entity.version AS current_version,
212
+ projects.code AS current_project_code
213
+ FROM public.graph_entities AS current_entity
214
+ JOIN public.projects AS projects
215
+ ON projects.id = current_entity.project_id
216
+ WHERE current_entity.entity_type = 'philosophy'
217
+ AND projects.code = $1
218
+ AND current_entity.lifecycle_status = 'active'
219
+ AND NOT (current_entity.payload ? 'judgmentApplicability')
220
+ ORDER BY current_entity.id
221
+ `;
194
222
  const FOUNDATION_TYPES = ['objective', 'variable', 'model', 'constraint'];
195
223
  const REVISION_PATTERN = /^[1-9]\d*$/u;
196
224
  const DIGEST_PATTERN = /^sha256:[0-9a-f]{64}$/u;
@@ -284,7 +312,75 @@ export function createGraphFoundationReaders(options) {
284
312
  return createPhilosophyRevisionRecord(validated, id, revision, context.principal);
285
313
  });
286
314
  }
287
- return { store, philosophyReader, listPhilosophies };
315
+ async function listUnscopedPhilosophies(suppliedContext) {
316
+ assertTrustedPrincipal(context, suppliedContext);
317
+ if (selectedProjectCode === undefined) {
318
+ throw scopeViolation('A selected project is required to list philosophies');
319
+ }
320
+ let result;
321
+ try {
322
+ result = await query(GRAPH_UNSCOPED_PHILOSOPHY_LIST_SQL, [selectedProjectCode]);
323
+ }
324
+ catch {
325
+ throw corrupt('Graph unscoped philosophy list query failed');
326
+ }
327
+ const rows = Array.isArray(result) ? result
328
+ : 'rows' in result && Array.isArray(result.rows) ? result.rows
329
+ : null;
330
+ if (!rows)
331
+ throw corrupt('Graph unscoped philosophy list query returned an invalid result');
332
+ const seen = new Set();
333
+ const records = [];
334
+ for (const row of rows) {
335
+ const id = requireString(row.current_entity_id, 'Current philosophy id');
336
+ if (row.current_entity_type !== 'philosophy')
337
+ throw corrupt('Graph philosophy list returned another entity type');
338
+ if (row.current_project_code !== selectedProjectCode) {
339
+ throw scopeViolation('The current philosophy project is outside the selected project scope');
340
+ }
341
+ if (seen.has(id))
342
+ throw corrupt('Philosophy list returned duplicate entities');
343
+ seen.add(id);
344
+ const version = Number(row.current_version);
345
+ if (!Number.isSafeInteger(version) || version < 1)
346
+ throw corrupt('Current philosophy version is invalid');
347
+ const payload = requireObject(row.current_payload, 'Current philosophy payload');
348
+ if (Object.hasOwn(payload, 'judgmentApplicability')) {
349
+ throw corrupt('Unscoped philosophy list returned a philosophy with applicability');
350
+ }
351
+ // Private philosophies always carry applicability; an owner-only ACL here
352
+ // is still enforced rather than silently shown.
353
+ assertPrivatePhilosophyRead(payload, context.principal);
354
+ // Mirror the judgment frame catalog: superseded or text-less records are
355
+ // not used for judgment, so they are not shown as usable either.
356
+ const status = optionalText(payload.status);
357
+ if (status && /^(superseded|retired|rejected)/u.test(status))
358
+ continue;
359
+ const statement = optionalText(payload.statement);
360
+ if (!statement)
361
+ continue;
362
+ const title = optionalText(payload.display_name) ?? optionalText(payload.title);
363
+ const summary = optionalText(payload.summary);
364
+ records.push({
365
+ kind: 'philosophy',
366
+ id,
367
+ version,
368
+ applicability: null,
369
+ applicability_state: 'unrecorded',
370
+ payload: {
371
+ ...(title ? { title } : {}),
372
+ statement,
373
+ ...(summary ? { summary } : {}),
374
+ ...(status ? { status } : {})
375
+ }
376
+ });
377
+ }
378
+ return records;
379
+ }
380
+ return { store, philosophyReader, listPhilosophies, listUnscopedPhilosophies };
381
+ }
382
+ function optionalText(value) {
383
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
288
384
  }
289
385
  function createPhilosophyRevisionRecord(validated, id, revision, principal) {
290
386
  return {
@@ -525,12 +621,10 @@ function validateFoundationRecord(row, reference, context, selectedProjectCode,
525
621
  definition: cloneFoundationDefinition(history.definition),
526
622
  digest: digestFoundationDefinition(history.definition)
527
623
  };
528
- if (reference.type === 'objective') {
529
- const details = projectObjectiveDetails(history.payload);
530
- if (details !== undefined)
531
- return { ...record, details };
532
- }
533
- return record;
624
+ const details = reference.type === 'objective'
625
+ ? projectDetails(history.payload, OBJECTIVE_DETAIL_KEYS)
626
+ : reference.type === 'model' ? projectDetails(history.payload, MODEL_DETAIL_KEYS) : undefined;
627
+ return details === undefined ? record : { ...record, details };
534
628
  }
535
629
  const OBJECTIVE_DETAIL_KEYS = [
536
630
  'title',
@@ -540,15 +634,18 @@ const OBJECTIVE_DETAIL_KEYS = [
540
634
  'evaluator_note',
541
635
  'current_state'
542
636
  ];
543
- function projectObjectiveDetails(payload) {
637
+ // A model's foundation scope carries ids and a registration time; its own words for where it
638
+ // applies live beside the definition.
639
+ const MODEL_DETAIL_KEYS = ['applicability_text'];
640
+ function projectDetails(payload, keys) {
544
641
  const nestedDetails = isPlainRecord(payload.details) ? payload.details : undefined;
545
642
  const foundation = isPlainRecord(payload.foundation) ? payload.foundation : undefined;
546
643
  // Graphify payloads historically carried these descriptive fields at the
547
644
  // payload root. Keep the two known nested forms compatible while exposing
548
- // only the six fields in the overview contract.
645
+ // only the listed fields in the overview contract.
549
646
  const candidates = [payload, nestedDetails, foundation].filter((candidate) => candidate !== undefined);
550
647
  const projected = {};
551
- for (const key of OBJECTIVE_DETAIL_KEYS) {
648
+ for (const key of keys) {
552
649
  const source = candidates.find((candidate) => Object.prototype.hasOwnProperty.call(candidate, key));
553
650
  if (source !== undefined && source[key] !== undefined)
554
651
  projected[key] = source[key];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unson/brainbase-mcp",
3
- "version": "0.41.7",
3
+ "version": "0.41.9",
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",
@@ -483,5 +483,5 @@
483
483
  "engines": {
484
484
  "node": ">=20"
485
485
  },
486
- "gitHead": "218515bb85c16915c1e509f608f83aa06af3a5d1"
486
+ "gitHead": "76bf4ebe18b116379d4687272baecdc4a74dbb35"
487
487
  }
@@ -151,14 +151,6 @@
151
151
  line-height: 1.55;
152
152
  }
153
153
 
154
- .bb-fov-record-id {
155
- display: block;
156
- margin-top: 7px;
157
- color: var(--bb-color-quiet);
158
- font-family: var(--bb-font-mono);
159
- font-size: 10px;
160
- overflow-wrap: anywhere;
161
- }
162
154
 
163
155
  .bb-fov-ref-list {
164
156
  display: grid;
@@ -254,4 +246,5 @@
254
246
  @media (max-width: 760px) { .bb-fov-objective-ledger { overflow-x: auto; } .bb-fov-objective-ledger .bb-ws-ledger-row { min-width: 760px; } }
255
247
  @media (max-width: 480px) { .bb-fov-reading .bb-ws-definition { grid-template-columns: 1fr; gap: 4px; } .bb-fov-reading dd { margin-bottom: 12px; } }
256
248
  .bb-fov-philosophy-card .bb-fov-original { border-top: 0; padding-top: 0; }
249
+ .bb-fov-model-card .bb-fov-original { margin-top: 12px; padding-top: 10px; overflow-wrap: anywhere; }
257
250
  .bb-fov-objective-ledger .bb-ws-ledger-row:focus-visible { outline: 2px solid var(--bb-color-focus); outline-offset: 2px; }