@unson/brainbase-mcp 0.15.7 → 0.17.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/dist/cli.js +6 -23
- package/dist/graph-corrections.d.ts +7 -1
- package/dist/graph-corrections.js +89 -4
- package/dist/graph-web-http.d.ts +0 -7
- package/dist/graph-web-http.js +3 -15
- package/dist/graph-web.d.ts +4 -32
- package/dist/graph-web.js +16 -44
- package/dist/local-web-host.d.ts +0 -23
- package/dist/local-web-host.js +4 -15
- package/dist/organization-graph-web.d.ts +5 -27
- package/dist/organization-graph-web.js +4 -128
- package/dist/world-extension.d.ts +4 -19
- package/dist/world-extension.js +15 -75
- package/package.json +3 -2
- package/ui/graph-projects-view.js +4 -3
- package/ui/graph-view-shared.css +49 -0
- package/ui/graph-view-shared.js +156 -46
- package/ui/local-web-shell.js +3 -53
- package/ui/project-icon.js +155 -0
- package/ui/workspace-kit.css +11 -0
- package/ui/workspace-kit.js +5 -2
- package/ui/world/world-view.js +5 -19
package/dist/cli.js
CHANGED
|
@@ -22,7 +22,6 @@ import { blockedJudgmentOutput, processJudgmentHook } from './judgment-host.js';
|
|
|
22
22
|
import { applyCanonicalWrites, buildCanonicalEdge } from './canonical-edge-builder.js';
|
|
23
23
|
import { defaultJudgmentJournalRoot } from './judgment-value-proof-review.js';
|
|
24
24
|
import { createWorldExtension, readWorldVocabulary } from './world-extension.js';
|
|
25
|
-
import { createOrganizationGraphSource } from './organization-graph-web.js';
|
|
26
25
|
import { createLocalWebHost, LOCAL_WEB_DEFAULT_PORT } from './local-web-host.js';
|
|
27
26
|
export async function runCli(argv = process.argv.slice(2), io = process) {
|
|
28
27
|
const parsed = parseArgs(argv);
|
|
@@ -1052,30 +1051,16 @@ async function webServe(parsed, io, command) {
|
|
|
1052
1051
|
if (worldVocabularyPath !== undefined && !world)
|
|
1053
1052
|
throw new Error(`${command} --world-vocabulary requires --world`);
|
|
1054
1053
|
const worldVocabulary = worldVocabularyPath === undefined ? undefined : await readWorldVocabulary(resolve(worldVocabularyPath));
|
|
1055
|
-
//
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
if ('reason' in source) {
|
|
1060
|
-
throw new Error(`${command} --organization-graph cannot read the organization Graph (${source.reason}); sign in with brainbase auth first`);
|
|
1054
|
+
// The organization Graph is shown by the organization web, not by this local host (C1 ended 2026-10-05).
|
|
1055
|
+
for (const removed of ['organization-graph', 'organization-web']) {
|
|
1056
|
+
if (parsed.flags.has(removed) || first(parsed, removed) !== undefined) {
|
|
1057
|
+
throw new Error(`${command} --${removed} was removed; open the organization web to see the organization Graph`);
|
|
1061
1058
|
}
|
|
1062
|
-
organizationGraph = source;
|
|
1063
|
-
}
|
|
1064
|
-
// Where the organization's records are corrected; the read-only screens link to it.
|
|
1065
|
-
const organizationWeb = first(parsed, 'organization-web');
|
|
1066
|
-
if (organizationWeb !== undefined) {
|
|
1067
|
-
if (!organizationGraph)
|
|
1068
|
-
throw new Error(`${command} --organization-web requires --organization-graph`);
|
|
1069
|
-
if (!/^https:\/\/[^/?#@\s]+$/u.test(organizationWeb)) {
|
|
1070
|
-
throw new Error(`${command} --organization-web must be an https origin such as https://org.example.com`);
|
|
1071
|
-
}
|
|
1072
|
-
organizationGraph = { ...organizationGraph, webUrl: organizationWeb };
|
|
1073
1059
|
}
|
|
1074
1060
|
const { server } = createLocalWebHost({
|
|
1075
1061
|
dataDir,
|
|
1076
1062
|
journalRoot,
|
|
1077
|
-
extensions: world ? [createWorldExtension(worldVocabulary ? { vocabulary: worldVocabulary } : {})] : []
|
|
1078
|
-
...(organizationGraph ? { organizationGraph } : {})
|
|
1063
|
+
extensions: world ? [createWorldExtension(worldVocabulary ? { vocabulary: worldVocabulary } : {})] : []
|
|
1079
1064
|
});
|
|
1080
1065
|
await new Promise((resolveListen, rejectListen) => {
|
|
1081
1066
|
server.once('error', rejectListen);
|
|
@@ -1091,8 +1076,6 @@ async function webServe(parsed, io, command) {
|
|
|
1091
1076
|
`- プロジェクトと関係者: ${origin}/#projects`,
|
|
1092
1077
|
`- 情報と関係: ${origin}/#graph`,
|
|
1093
1078
|
...(world ? [`- 世界: ${origin}/#world`] : []),
|
|
1094
|
-
...(organizationGraph ? [`組織のGraph(読み取りのみ): ${organizationGraph.server}`] : []),
|
|
1095
|
-
...(organizationGraph?.webUrl ? [`組織版(訂正の入口): ${organizationGraph.webUrl}`] : []),
|
|
1096
1079
|
`データ: ${dataDir}`,
|
|
1097
1080
|
`判断journal: ${journalRoot}`,
|
|
1098
1081
|
'終了: Ctrl+C',
|
|
@@ -1249,7 +1232,7 @@ function usage() {
|
|
|
1249
1232
|
brainbase judgment:install --target codex [--autonomy-mode off|canary|on] [--autonomy-project code] [--dry-run] [--output path]
|
|
1250
1233
|
brainbase judgment:hook [--autonomy-mode off|canary|on] [--autonomy-project code]
|
|
1251
1234
|
brainbase doctor [--dir path] [--judgment-hooks path]
|
|
1252
|
-
brainbase web:serve [--dir path] [--journal path] [--port n] [--world [--world-vocabulary file]]
|
|
1235
|
+
brainbase web:serve [--dir path] [--journal path] [--port n] [--world [--world-vocabulary file]]
|
|
1253
1236
|
brainbase review:serve [--dir path] [--journal path] [--port n] (web:serveの別名)
|
|
1254
1237
|
`;
|
|
1255
1238
|
}
|
|
@@ -11,11 +11,13 @@ export declare const GRAPH_CORRECTION_SCHEMA: "brainbase-graph-correction.v1";
|
|
|
11
11
|
/** Relative to the data directory. Committed as a sidecar of the canonical SSOT transaction. */
|
|
12
12
|
export declare const GRAPH_CORRECTIONS_FILE = "evidence/graph-corrections.jsonl";
|
|
13
13
|
export declare const GRAPH_CORRECTION_ENTITY_FIELDS: readonly ["name", "aliases", "summary", "validFrom", "validTo"];
|
|
14
|
-
export declare const GRAPH_CORRECTION_PROJECT_FIELDS: readonly ["goal", "status"];
|
|
14
|
+
export declare const GRAPH_CORRECTION_PROJECT_FIELDS: readonly ["goal", "status", "icon"];
|
|
15
15
|
export declare const GRAPH_CORRECTION_EDGE_FIELDS: readonly ["role", "context", "validTo"];
|
|
16
16
|
export declare const GRAPH_CORRECTION_NEW_EDGE_RELATIONS: readonly ["participates_in", "accountable_for", "member_of"];
|
|
17
17
|
/** MCP tools that read the same graph.json on every call and therefore see a saved correction next time. */
|
|
18
18
|
export declare const GRAPH_CORRECTION_APPLIES_TO: readonly ["search", "get_context", "resolve_entity"];
|
|
19
|
+
/** Decoded bytes, shared with the browser-side project-icon helper. */
|
|
20
|
+
export declare const PROJECT_ICON_MAX_BYTES: number;
|
|
19
21
|
export type GraphCorrectionKind = 'update_entity' | 'update_edge' | 'create_edge';
|
|
20
22
|
export type GraphNewEdgeRelation = typeof GRAPH_CORRECTION_NEW_EDGE_RELATIONS[number];
|
|
21
23
|
export interface GraphEntityCorrectionChanges {
|
|
@@ -28,6 +30,8 @@ export interface GraphEntityCorrectionChanges {
|
|
|
28
30
|
goal?: string | null;
|
|
29
31
|
/** Projects only. Stored as `metadata.status`. */
|
|
30
32
|
status?: string | null;
|
|
33
|
+
/** Projects only. Stored as `metadata.icon` as a bounded PNG/JPEG/WebP data URL. */
|
|
34
|
+
icon?: string | null;
|
|
31
35
|
}
|
|
32
36
|
export interface GraphEdgeCorrectionChanges {
|
|
33
37
|
role?: string | null;
|
|
@@ -155,3 +159,5 @@ export declare function readGraphCorrectionHistory(dataDir: string): Promise<{
|
|
|
155
159
|
records: GraphCorrectionRecord[];
|
|
156
160
|
issues: GraphCorrectionHistoryIssue[];
|
|
157
161
|
}>;
|
|
162
|
+
/** Returns true only for a bounded, signature-checked project icon data URL. */
|
|
163
|
+
export declare function isProjectIconDataUrl(value: unknown): value is string;
|
|
@@ -18,7 +18,7 @@ export const GRAPH_CORRECTION_SCHEMA = 'brainbase-graph-correction.v1';
|
|
|
18
18
|
/** Relative to the data directory. Committed as a sidecar of the canonical SSOT transaction. */
|
|
19
19
|
export const GRAPH_CORRECTIONS_FILE = 'evidence/graph-corrections.jsonl';
|
|
20
20
|
export const GRAPH_CORRECTION_ENTITY_FIELDS = ['name', 'aliases', 'summary', 'validFrom', 'validTo'];
|
|
21
|
-
export const GRAPH_CORRECTION_PROJECT_FIELDS = ['goal', 'status'];
|
|
21
|
+
export const GRAPH_CORRECTION_PROJECT_FIELDS = ['goal', 'status', 'icon'];
|
|
22
22
|
export const GRAPH_CORRECTION_EDGE_FIELDS = ['role', 'context', 'validTo'];
|
|
23
23
|
export const GRAPH_CORRECTION_NEW_EDGE_RELATIONS = ['participates_in', 'accountable_for', 'member_of'];
|
|
24
24
|
/** MCP tools that read the same graph.json on every call and therefore see a saved correction next time. */
|
|
@@ -27,6 +27,8 @@ const MAX_REASON_LENGTH = 500;
|
|
|
27
27
|
const MAX_NAME_LENGTH = 200;
|
|
28
28
|
const MAX_TEXT_LENGTH = 2000;
|
|
29
29
|
const MAX_ALIASES = 50;
|
|
30
|
+
/** Decoded bytes, shared with the browser-side project-icon helper. */
|
|
31
|
+
export const PROJECT_ICON_MAX_BYTES = 256 * 1024;
|
|
30
32
|
const DIGEST_PATTERN = /^sha256:[0-9a-f]{64}$/u;
|
|
31
33
|
const PROJECTED_RELATIONS = new Set(['participates_in', 'accountable_for']);
|
|
32
34
|
export class GraphCorrectionError extends Error {
|
|
@@ -213,8 +215,8 @@ function planEntityUpdate(graph, relationships, correction, at) {
|
|
|
213
215
|
const before = matches[0];
|
|
214
216
|
assertExpectedDigest('entity', before, correction.expectedDigest);
|
|
215
217
|
const changes = correction.changes;
|
|
216
|
-
if ((changes.goal !== undefined || changes.status !== undefined) && before.type !== 'project') {
|
|
217
|
-
invalid('correction_field_not_allowed', 'goal and
|
|
218
|
+
if ((changes.goal !== undefined || changes.status !== undefined || changes.icon !== undefined) && before.type !== 'project') {
|
|
219
|
+
invalid('correction_field_not_allowed', 'goal, status and icon can be corrected only on a project');
|
|
218
220
|
}
|
|
219
221
|
const after = structuredClone(before);
|
|
220
222
|
const diff = {};
|
|
@@ -228,7 +230,7 @@ function planEntityUpdate(graph, relationships, correction, at) {
|
|
|
228
230
|
setField(after, 'validFrom', changes.validFrom, before, diff);
|
|
229
231
|
if (changes.validTo !== undefined)
|
|
230
232
|
setField(after, 'validTo', changes.validTo, before, diff);
|
|
231
|
-
if (changes.goal !== undefined || changes.status !== undefined) {
|
|
233
|
+
if (changes.goal !== undefined || changes.status !== undefined || changes.icon !== undefined) {
|
|
232
234
|
const metadata = { ...(before.metadata ?? {}) };
|
|
233
235
|
for (const key of ['goal', 'status']) {
|
|
234
236
|
const next = changes[key];
|
|
@@ -243,6 +245,18 @@ function planEntityUpdate(graph, relationships, correction, at) {
|
|
|
243
245
|
metadata[key] = next;
|
|
244
246
|
diff[`metadata.${key}`] = { before: previous, after: next };
|
|
245
247
|
}
|
|
248
|
+
if (changes.icon !== undefined) {
|
|
249
|
+
const previous = typeof metadata.icon === 'string' ? metadata.icon : null;
|
|
250
|
+
const next = changes.icon;
|
|
251
|
+
if (previous !== next) {
|
|
252
|
+
if (next === null)
|
|
253
|
+
delete metadata.icon;
|
|
254
|
+
else
|
|
255
|
+
metadata.icon = next;
|
|
256
|
+
// Keep image bytes and the long data URL out of append-only history.
|
|
257
|
+
diff['metadata.icon'] = { before: projectIconSummary(previous), after: projectIconSummary(next) };
|
|
258
|
+
}
|
|
259
|
+
}
|
|
246
260
|
if (Object.keys(metadata).length > 0)
|
|
247
261
|
after.metadata = metadata;
|
|
248
262
|
else
|
|
@@ -488,6 +502,8 @@ function parseEntityChanges(value) {
|
|
|
488
502
|
changes.goal = optionalText(value.goal, 'changes.goal', MAX_TEXT_LENGTH);
|
|
489
503
|
if (value.status !== undefined)
|
|
490
504
|
changes.status = optionalText(value.status, 'changes.status', MAX_NAME_LENGTH);
|
|
505
|
+
if (value.icon !== undefined)
|
|
506
|
+
changes.icon = parseProjectIcon(value.icon);
|
|
491
507
|
return changes;
|
|
492
508
|
}
|
|
493
509
|
function parseEdgeChanges(value) {
|
|
@@ -577,6 +593,75 @@ function optionalDateTime(value, field) {
|
|
|
577
593
|
}
|
|
578
594
|
return value;
|
|
579
595
|
}
|
|
596
|
+
const PROJECT_ICON_DATA_URL = /^data:(image\/(?:png|jpeg|webp));base64,([A-Za-z0-9+/]*={0,2})(?![\s\S])/u;
|
|
597
|
+
function parseProjectIcon(value) {
|
|
598
|
+
if (value === null)
|
|
599
|
+
return null;
|
|
600
|
+
if (typeof value !== 'string')
|
|
601
|
+
invalid('correction_invalid', 'changes.icon must be a PNG, JPEG, WebP data URL or null');
|
|
602
|
+
const match = PROJECT_ICON_DATA_URL.exec(value);
|
|
603
|
+
if (!match || value.length === 0)
|
|
604
|
+
invalid('correction_invalid', 'changes.icon must be a PNG, JPEG, WebP data URL or null');
|
|
605
|
+
const payload = match[2];
|
|
606
|
+
if (!payload || payload.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/u.test(payload)) {
|
|
607
|
+
invalid('correction_invalid', 'changes.icon must contain canonical base64 data');
|
|
608
|
+
}
|
|
609
|
+
let decoded;
|
|
610
|
+
try {
|
|
611
|
+
decoded = Buffer.from(payload, 'base64');
|
|
612
|
+
}
|
|
613
|
+
catch {
|
|
614
|
+
invalid('correction_invalid', 'changes.icon contains invalid base64 data');
|
|
615
|
+
}
|
|
616
|
+
if (decoded.length === 0 || decoded.toString('base64') !== payload) {
|
|
617
|
+
invalid('correction_invalid', 'changes.icon contains invalid base64 data');
|
|
618
|
+
}
|
|
619
|
+
if (decoded.length > PROJECT_ICON_MAX_BYTES) {
|
|
620
|
+
invalid('correction_invalid', `changes.icon must be at most ${PROJECT_ICON_MAX_BYTES} decoded bytes`);
|
|
621
|
+
}
|
|
622
|
+
const mimeType = match[1];
|
|
623
|
+
const validSignature = mimeType === 'image/png'
|
|
624
|
+
? decoded.length >= 8 && Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]).equals(decoded.subarray(0, 8))
|
|
625
|
+
: mimeType === 'image/jpeg'
|
|
626
|
+
? decoded.length >= 4 && decoded[0] === 0xff && decoded[1] === 0xd8 && decoded[2] === 0xff
|
|
627
|
+
&& decoded[decoded.length - 2] === 0xff && decoded[decoded.length - 1] === 0xd9
|
|
628
|
+
: decoded.length >= 12 && decoded.subarray(0, 4).toString('ascii') === 'RIFF'
|
|
629
|
+
&& decoded.subarray(8, 12).toString('ascii') === 'WEBP';
|
|
630
|
+
if (!validSignature)
|
|
631
|
+
invalid('correction_invalid', 'changes.icon image signature does not match its MIME type');
|
|
632
|
+
return value;
|
|
633
|
+
}
|
|
634
|
+
/** Returns true only for a bounded, signature-checked project icon data URL. */
|
|
635
|
+
export function isProjectIconDataUrl(value) {
|
|
636
|
+
if (value === null)
|
|
637
|
+
return false;
|
|
638
|
+
try {
|
|
639
|
+
return parseProjectIcon(value) !== null;
|
|
640
|
+
}
|
|
641
|
+
catch (error) {
|
|
642
|
+
if (error instanceof GraphCorrectionError && error.kind === 'invalid')
|
|
643
|
+
return false;
|
|
644
|
+
throw error;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
function projectIconSummary(value) {
|
|
648
|
+
if (value === null || value === undefined)
|
|
649
|
+
return null;
|
|
650
|
+
if (typeof value !== 'string')
|
|
651
|
+
return { present: true, valid: false };
|
|
652
|
+
const match = PROJECT_ICON_DATA_URL.exec(value);
|
|
653
|
+
if (!match)
|
|
654
|
+
return { present: true, valid: false };
|
|
655
|
+
if (!isProjectIconDataUrl(value))
|
|
656
|
+
return { present: true, valid: false };
|
|
657
|
+
try {
|
|
658
|
+
const decoded = Buffer.from(match[2], 'base64');
|
|
659
|
+
return { present: true, valid: true, mimeType: match[1], bytes: decoded.length };
|
|
660
|
+
}
|
|
661
|
+
catch {
|
|
662
|
+
return { present: true, valid: false };
|
|
663
|
+
}
|
|
664
|
+
}
|
|
580
665
|
function requireId(value, field) {
|
|
581
666
|
if (typeof value !== 'string' || value.trim() === '' || value.length > 500)
|
|
582
667
|
invalid('correction_invalid', `${field} must be a non-empty id`);
|
package/dist/graph-web-http.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
-
import { type GraphWebAnySource, type InMemoryGraphReader } from './graph-web.js';
|
|
3
2
|
export declare const GRAPH_WEB_HTTP_VERSION: "graph-web-http.v1";
|
|
4
3
|
export interface GraphWebHttpOptions {
|
|
5
4
|
/** Personal OS data directory chosen by the host. Never taken from the request. */
|
|
@@ -12,12 +11,6 @@ export interface GraphWebHttpOptions {
|
|
|
12
11
|
*/
|
|
13
12
|
readonly assertWriteAllowed?: (request: IncomingMessage) => void | Promise<void>;
|
|
14
13
|
readonly now?: () => Date;
|
|
15
|
-
/**
|
|
16
|
-
* Ledger C1: read every route from a Graph the host holds in memory (the
|
|
17
|
-
* owner's organization Graph) instead of the data directory. Corrections
|
|
18
|
-
* are refused as read only.
|
|
19
|
-
*/
|
|
20
|
-
readonly readGraph?: () => Promise<InMemoryGraphReader<GraphWebAnySource>>;
|
|
21
14
|
}
|
|
22
15
|
export type GraphWebHttpHandler = (request: IncomingMessage, response: ServerResponse) => Promise<boolean>;
|
|
23
16
|
/**
|
package/dist/graph-web-http.js
CHANGED
|
@@ -2,7 +2,9 @@ import { applyGraphCorrection, GraphCorrectionError } from './graph-corrections.
|
|
|
2
2
|
import { GraphWebError, listGraphProjects, readGraphEntity, readGraphOntology, readGraphProject, readGraphWebStatus, searchGraphEntities } from './graph-web.js';
|
|
3
3
|
export const GRAPH_WEB_HTTP_VERSION = 'graph-web-http.v1';
|
|
4
4
|
const DEFAULT_BASE_PATH = '/api/graph';
|
|
5
|
-
|
|
5
|
+
// A 256KiB decoded image becomes roughly 350KiB as base64 in JSON. Keep a
|
|
6
|
+
// bounded request envelope while allowing the project-icon correction through.
|
|
7
|
+
const MAX_BODY_BYTES = 512 * 1024;
|
|
6
8
|
class HttpError extends Error {
|
|
7
9
|
statusCode;
|
|
8
10
|
code;
|
|
@@ -52,9 +54,6 @@ export function createGraphWebHttpHandler(options) {
|
|
|
52
54
|
if (route.name === 'corrections') {
|
|
53
55
|
if (request.method !== 'POST')
|
|
54
56
|
throw methodNotAllowed('POST');
|
|
55
|
-
if (options.readGraph) {
|
|
56
|
-
throw new HttpError(403, 'organization_graph_read_only', 'This host reads the organization Graph read only; correct it in the organization edition');
|
|
57
|
-
}
|
|
58
57
|
await assertWriteAllowed(options.assertWriteAllowed, request);
|
|
59
58
|
const body = await readJsonBody(request);
|
|
60
59
|
const result = await applyGraphCorrection(dataDir, body, { now: now() });
|
|
@@ -65,17 +64,6 @@ export function createGraphWebHttpHandler(options) {
|
|
|
65
64
|
throw methodNotAllowed('GET');
|
|
66
65
|
const asOf = queryValue(url, 'as_of');
|
|
67
66
|
const readOptions = { ...(asOf !== undefined ? { asOf } : {}), now: now() };
|
|
68
|
-
if (options.readGraph) {
|
|
69
|
-
const reader = await options.readGraph();
|
|
70
|
-
const body = route.name === 'status' ? reader.status(readOptions)
|
|
71
|
-
: route.name === 'projects' ? reader.listProjects(readOptions)
|
|
72
|
-
: route.name === 'project' ? reader.readProject(route.id, readOptions)
|
|
73
|
-
: route.name === 'search' ? reader.search({ q: queryValue(url, 'q'), type: queryValue(url, 'type'), asOf, limit: parseLimit(queryValue(url, 'limit')), now: readOptions.now })
|
|
74
|
-
: route.name === 'entity' ? reader.readEntity(route.id, readOptions)
|
|
75
|
-
: reader.ontology(readOptions);
|
|
76
|
-
writeJson(response, 200, body);
|
|
77
|
-
return true;
|
|
78
|
-
}
|
|
79
67
|
switch (route.name) {
|
|
80
68
|
case 'status':
|
|
81
69
|
writeJson(response, 200, await readGraphWebStatus(dataDir, readOptions));
|
package/dist/graph-web.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { GRAPH_CORRECTIONS_FILE, type GraphCorrectionRecord } from './graph-corrections.js';
|
|
2
2
|
import { type PortableGraphOwner } from './portable-graph.js';
|
|
3
3
|
import type { FoundationAdoptionState, FoundationOperator } from './ontology-foundation.js';
|
|
4
|
-
import type { CanonicalEdge, CanonicalEntityKind, CoreRelation
|
|
4
|
+
import type { CanonicalEdge, CanonicalEntityKind, CoreRelation } from './types.js';
|
|
5
5
|
/**
|
|
6
6
|
* Read views of the canonical local Graph for the local Web screens
|
|
7
7
|
* "プロジェクトと関係者" and "情報と関係".
|
|
@@ -34,20 +34,7 @@ export interface GraphWebOwnerPrivateSource {
|
|
|
34
34
|
graphFormat: 2;
|
|
35
35
|
authority: 'owner_private';
|
|
36
36
|
}
|
|
37
|
-
|
|
38
|
-
* The owner's organization Graph read through the host (ledger C1): read only,
|
|
39
|
-
* held in memory, never written to the data directory.
|
|
40
|
-
*/
|
|
41
|
-
export interface GraphWebOrganizationSource {
|
|
42
|
-
dataDir: null;
|
|
43
|
-
graphFormat: 2;
|
|
44
|
-
authority: 'organization_graph';
|
|
45
|
-
/** Organization server origin, without credentials. */
|
|
46
|
-
server: string;
|
|
47
|
-
/** When the host last read the organization Graph. */
|
|
48
|
-
readAt: string;
|
|
49
|
-
}
|
|
50
|
-
export type GraphWebAnySource = GraphWebSource | GraphWebOwnerPrivateSource | GraphWebOrganizationSource;
|
|
37
|
+
export type GraphWebAnySource = GraphWebSource | GraphWebOwnerPrivateSource;
|
|
51
38
|
export interface GraphMigrationRequired {
|
|
52
39
|
status: 'migration_required';
|
|
53
40
|
source: GraphWebSource;
|
|
@@ -87,6 +74,8 @@ export interface GraphEntityView {
|
|
|
87
74
|
digest: string;
|
|
88
75
|
goal?: string | null;
|
|
89
76
|
status?: string | null;
|
|
77
|
+
/** Projects only. A validated PNG/JPEG/WebP data URL, or null when unset. */
|
|
78
|
+
icon?: string | null;
|
|
90
79
|
}
|
|
91
80
|
export type GraphProvenanceReference = {
|
|
92
81
|
status: 'none';
|
|
@@ -374,21 +363,4 @@ export interface OwnerPrivateGraph {
|
|
|
374
363
|
* machine, so a relation's source ID is shown unresolved.
|
|
375
364
|
*/
|
|
376
365
|
export declare function openOwnerPrivateGraph(bundle: unknown, owner: PortableGraphOwner): OwnerPrivateGraph;
|
|
377
|
-
/** Read views of one in-memory Graph v2 held by the host (no data directory, no corrections). */
|
|
378
|
-
export interface InMemoryGraphReader<S extends GraphWebAnySource> {
|
|
379
|
-
readonly source: S;
|
|
380
|
-
status(options?: GraphWebReadOptions): GraphWebStatus<S>;
|
|
381
|
-
listProjects(options?: GraphWebReadOptions): GraphProjectList<S>;
|
|
382
|
-
readProject(projectId: string, options?: GraphWebReadOptions): GraphProjectDetail<S>;
|
|
383
|
-
search(input?: GraphEntitySearchInput): GraphEntitySearchResult<S>;
|
|
384
|
-
readEntity(entityId: string, options?: GraphWebReadOptions): GraphEntityDetail<S>;
|
|
385
|
-
ontology(options?: GraphWebReadOptions): GraphOntologySummary<S>;
|
|
386
|
-
}
|
|
387
|
-
/**
|
|
388
|
-
* The same read views as the local Graph routes over a Graph v2 the host
|
|
389
|
-
* holds in memory (the organization Graph read under C1). The Graph is
|
|
390
|
-
* checked with the canonical validator first; nothing is written, and there
|
|
391
|
-
* are no correction records or relation sources to resolve.
|
|
392
|
-
*/
|
|
393
|
-
export declare function openInMemoryGraph<S extends GraphWebAnySource>(graph: GraphFileV2, source: S): InMemoryGraphReader<S>;
|
|
394
366
|
export {};
|
package/dist/graph-web.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { access, readdir, readFile } from 'node:fs/promises';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import { isActiveAt
|
|
3
|
+
import { isActiveAt } from './canonical-graph.js';
|
|
4
4
|
import { assertFoundationCatalog, findFoundationRecord } from './foundation-catalog.js';
|
|
5
|
-
import { GRAPH_CORRECTION_EDGE_FIELDS, GRAPH_CORRECTION_ENTITY_FIELDS, GRAPH_CORRECTION_NEW_EDGE_RELATIONS, GRAPH_CORRECTION_PROJECT_FIELDS, GRAPH_CORRECTIONS_FILE, graphRecordDigest, readGraphCorrectionHistory } from './graph-corrections.js';
|
|
5
|
+
import { GRAPH_CORRECTION_EDGE_FIELDS, GRAPH_CORRECTION_ENTITY_FIELDS, GRAPH_CORRECTION_NEW_EDGE_RELATIONS, GRAPH_CORRECTION_PROJECT_FIELDS, GRAPH_CORRECTIONS_FILE, graphRecordDigest, isProjectIconDataUrl, readGraphCorrectionHistory } from './graph-corrections.js';
|
|
6
6
|
import { ONTOLOGY_VERSION, portableOntology } from './ontology.js';
|
|
7
7
|
import { canonicalRelationRegistry, getCanonicalRelation } from './relation-registry.js';
|
|
8
8
|
import { loadPersonalOs } from './ssot.js';
|
|
@@ -305,46 +305,6 @@ export function openOwnerPrivateGraph(bundle, owner) {
|
|
|
305
305
|
}
|
|
306
306
|
});
|
|
307
307
|
}
|
|
308
|
-
/**
|
|
309
|
-
* The same read views as the local Graph routes over a Graph v2 the host
|
|
310
|
-
* holds in memory (the organization Graph read under C1). The Graph is
|
|
311
|
-
* checked with the canonical validator first; nothing is written, and there
|
|
312
|
-
* are no correction records or relation sources to resolve.
|
|
313
|
-
*/
|
|
314
|
-
export function openInMemoryGraph(graph, source) {
|
|
315
|
-
try {
|
|
316
|
-
validateCanonicalGraph(graph);
|
|
317
|
-
}
|
|
318
|
-
catch (error) {
|
|
319
|
-
throw new GraphWebError('unavailable', 'graph_invalid', errorMessage(error));
|
|
320
|
-
}
|
|
321
|
-
return Object.freeze({
|
|
322
|
-
source,
|
|
323
|
-
status: (options = {}) => {
|
|
324
|
-
const asOf = resolveAsOf(options);
|
|
325
|
-
const entities = Object.fromEntries(ENTITY_KINDS.map((kind) => [kind, graph.entities.filter((entity) => entity.type === kind).length]));
|
|
326
|
-
return {
|
|
327
|
-
status: 'ok',
|
|
328
|
-
version: GRAPH_WEB_VERSION,
|
|
329
|
-
source,
|
|
330
|
-
asOf,
|
|
331
|
-
ontology: { ...graph.ontology, currentVersion: ONTOLOGY_VERSION },
|
|
332
|
-
counts: {
|
|
333
|
-
entities: { ...entities, total: graph.entities.length },
|
|
334
|
-
edges: { total: graph.edges.length, active: graph.edges.filter((edge) => isActiveAt(edge, asOf)).length },
|
|
335
|
-
relationships: 0,
|
|
336
|
-
corrections: 0
|
|
337
|
-
},
|
|
338
|
-
issues: []
|
|
339
|
-
};
|
|
340
|
-
},
|
|
341
|
-
listProjects: (options = {}) => projectListOf(graph, source, resolveAsOf(options)),
|
|
342
|
-
readProject: (projectId, options = {}) => projectDetailOf(graph, source, projectId, resolveAsOf(options), NO_REFERENCES),
|
|
343
|
-
search: (input = {}) => entitySearchOf(graph, source, parseSearchInput(input)),
|
|
344
|
-
readEntity: (entityId, options = {}) => entityDetailOf(graph, source, entityId, resolveAsOf(options), NO_REFERENCES),
|
|
345
|
-
ontology: (options = {}) => ontologyOf(graph, source, resolveAsOf(options))
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
308
|
function decisionRecordView(record) {
|
|
349
309
|
return {
|
|
350
310
|
title: record.title,
|
|
@@ -574,11 +534,23 @@ function entityView(entity, asOf) {
|
|
|
574
534
|
validTo: entity.validTo ?? null,
|
|
575
535
|
active: isActiveAt(entity, asOf),
|
|
576
536
|
digest: graphRecordDigest(entity),
|
|
577
|
-
...(entity.type === 'project'
|
|
537
|
+
...(entity.type === 'project'
|
|
538
|
+
? { goal: metadataString(entity, 'goal'), status: metadataString(entity, 'status'), icon: projectIconString(entity) }
|
|
539
|
+
: {})
|
|
578
540
|
};
|
|
579
541
|
}
|
|
580
542
|
function projectView(project, asOf) {
|
|
581
|
-
return {
|
|
543
|
+
return {
|
|
544
|
+
...entityView(project, asOf),
|
|
545
|
+
type: 'project',
|
|
546
|
+
goal: metadataString(project, 'goal'),
|
|
547
|
+
status: metadataString(project, 'status'),
|
|
548
|
+
icon: projectIconString(project),
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
function projectIconString(entity) {
|
|
552
|
+
const value = entity.metadata?.icon;
|
|
553
|
+
return isProjectIconDataUrl(value) ? value : null;
|
|
582
554
|
}
|
|
583
555
|
function historyFor(records, ids) {
|
|
584
556
|
return records.filter((record) => ids.has(record.target.id)).reverse();
|
package/dist/local-web-host.d.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { type IncomingMessage, type Server, type ServerResponse } from 'node:http';
|
|
2
2
|
import { type ObjectiveDefinitionBuilder } from './foundation-http.js';
|
|
3
|
-
import type { GraphWebAnySource, InMemoryGraphReader } from './graph-web.js';
|
|
4
3
|
import { JudgmentValueProofJournalCache } from './judgment-value-proof-review.js';
|
|
5
|
-
import type { GraphFileV2 } from './types.js';
|
|
6
4
|
/**
|
|
7
5
|
* General local single-owner Web host (`brainbase web:serve`).
|
|
8
6
|
*
|
|
@@ -41,8 +39,6 @@ export interface LocalWebModuleContext {
|
|
|
41
39
|
readonly journalRoot: string;
|
|
42
40
|
readonly token: string;
|
|
43
41
|
readonly now: () => Date;
|
|
44
|
-
/** Present when the host reads the organization Graph (C1). */
|
|
45
|
-
readonly organizationGraph?: LocalWebOrganizationGraph;
|
|
46
42
|
/** The host's one listing cache for the judgment journal, shared by every screen that reads it. */
|
|
47
43
|
readonly journalCache: JudgmentValueProofJournalCache;
|
|
48
44
|
}
|
|
@@ -80,25 +76,6 @@ export interface LocalWebHostOptions {
|
|
|
80
76
|
readonly uiDir?: string;
|
|
81
77
|
readonly now?: () => Date;
|
|
82
78
|
readonly extensions?: readonly LocalWebExtension[];
|
|
83
|
-
/**
|
|
84
|
-
* Ledger C1: the owner's organization Graph, read only. When given, the Graph
|
|
85
|
-
* screens read it instead of the data directory and the status reports it.
|
|
86
|
-
*/
|
|
87
|
-
readonly organizationGraph?: LocalWebOrganizationGraph;
|
|
88
|
-
}
|
|
89
|
-
export interface LocalWebOrganizationGraph {
|
|
90
|
-
/** Organization server origin shown as the source, without credentials. */
|
|
91
|
-
readonly server: string;
|
|
92
|
-
/** The organization's own web (where its records are corrected), when the owner configured it. */
|
|
93
|
-
readonly webUrl?: string;
|
|
94
|
-
read(): Promise<InMemoryGraphReader<GraphWebAnySource>>;
|
|
95
|
-
/** The same Graph as plain records (the world reads project metadata from it). */
|
|
96
|
-
readGraphFile?(): Promise<GraphFileV2>;
|
|
97
|
-
/**
|
|
98
|
-
* The organization's words for field values (glossary terms with `payload.vocabulary`), or null when
|
|
99
|
-
* they could not be read. The rest of the Graph is read even when these are not.
|
|
100
|
-
*/
|
|
101
|
-
readVocabularyTerms?(): Promise<readonly GraphVocabularyTerm[] | null>;
|
|
102
79
|
}
|
|
103
80
|
/** One organization term that names a field value, e.g. project.kind = product → プロダクト. */
|
|
104
81
|
export interface GraphVocabularyTerm {
|
package/dist/local-web-host.js
CHANGED
|
@@ -138,9 +138,7 @@ export function createLocalStatusModule(context) {
|
|
|
138
138
|
writeJson(response, 405, { error: { code: 'method_not_allowed', message: 'Use GET' } });
|
|
139
139
|
return true;
|
|
140
140
|
}
|
|
141
|
-
const
|
|
142
|
-
// Under C1 the Graph screens read the organization Graph, so they are not gated on the local file.
|
|
143
|
-
const graph = context.organizationGraph ? { ...local, status: 'ready', format: 'v2', message: undefined } : local;
|
|
141
|
+
const graph = await readLocalGraphState(context.dataDir);
|
|
144
142
|
writeJson(response, 200, {
|
|
145
143
|
version: LOCAL_WEB_HOST_VERSION,
|
|
146
144
|
data_dir: context.dataDir,
|
|
@@ -150,15 +148,7 @@ export function createLocalStatusModule(context) {
|
|
|
150
148
|
format: graph.format,
|
|
151
149
|
...(graph.message ? { message: graph.message } : {}),
|
|
152
150
|
commands: graphCommands(graph, context.dataDir)
|
|
153
|
-
}
|
|
154
|
-
organization_graph: context.organizationGraph
|
|
155
|
-
? {
|
|
156
|
-
status: 'connected',
|
|
157
|
-
server: context.organizationGraph.server,
|
|
158
|
-
mode: 'read_only',
|
|
159
|
-
...(context.organizationGraph.webUrl ? { web_url: context.organizationGraph.webUrl } : {})
|
|
160
|
-
}
|
|
161
|
-
: { status: 'not_connected' }
|
|
151
|
+
}
|
|
162
152
|
});
|
|
163
153
|
return true;
|
|
164
154
|
}
|
|
@@ -683,7 +673,6 @@ export function createGraphWebModule(context) {
|
|
|
683
673
|
dataDir: context.dataDir,
|
|
684
674
|
basePath: LOCAL_WEB_GRAPH_PREFIX,
|
|
685
675
|
now: context.now,
|
|
686
|
-
...(context.organizationGraph ? { readGraph: context.organizationGraph.read } : {}),
|
|
687
676
|
assertWriteAllowed(request) {
|
|
688
677
|
if (isTrustedLocalWrite(request, context.token))
|
|
689
678
|
return;
|
|
@@ -694,6 +683,7 @@ export function createGraphWebModule(context) {
|
|
|
694
683
|
return {
|
|
695
684
|
id: 'graph',
|
|
696
685
|
uiFiles: [
|
|
686
|
+
'project-icon.js',
|
|
697
687
|
'graph-view-shared.js',
|
|
698
688
|
'graph-view-shared.css',
|
|
699
689
|
'graph-projects-view.js',
|
|
@@ -782,8 +772,7 @@ export function createLocalWebHost(options = {}) {
|
|
|
782
772
|
journalRoot,
|
|
783
773
|
token,
|
|
784
774
|
now: options.now ?? (() => new Date()),
|
|
785
|
-
journalCache: new JudgmentValueProofJournalCache()
|
|
786
|
-
...(options.organizationGraph ? { organizationGraph: options.organizationGraph } : {})
|
|
775
|
+
journalCache: new JudgmentValueProofJournalCache()
|
|
787
776
|
};
|
|
788
777
|
const modules = defaultLocalWebModules(context);
|
|
789
778
|
const extensions = [...options.extensions ?? []];
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type { GraphVocabularyTerm
|
|
1
|
+
import type { GraphVocabularyTerm } from './local-web-host.js';
|
|
2
2
|
import type { CanonicalEntityKind, GraphFileV2 } from './types.js';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Projects organization Graph records (as an organization host reads them for
|
|
5
|
+
* its member) into a Graph v2, so shared parts such as the world draw them the
|
|
6
|
+
* same way as a local Graph. This module never reads a network or a token: the
|
|
7
|
+
* host that owns the records passes them in (`projectOrganizationWorld`).
|
|
8
8
|
*
|
|
9
9
|
* Relations come only from what the organization Graph states:
|
|
10
10
|
* - `member_of` person → project → `participates_in` (role = role_code)
|
|
@@ -55,26 +55,4 @@ export declare function projectOrganizationGraph(records: OrganizationGraphRecor
|
|
|
55
55
|
id: string;
|
|
56
56
|
name?: string;
|
|
57
57
|
}): OrganizationGraphProjection;
|
|
58
|
-
interface OrganizationAccess {
|
|
59
|
-
readonly server: string;
|
|
60
|
-
readonly token: string;
|
|
61
|
-
}
|
|
62
|
-
/** The owner's Brainbase server and token, read at request time (tokens rotate hourly). Never logged or returned. */
|
|
63
|
-
export declare function readOrganizationAccess(home?: string): Promise<OrganizationAccess | {
|
|
64
|
-
readonly reason: string;
|
|
65
|
-
}>;
|
|
66
|
-
export interface OrganizationGraphSourceOptions {
|
|
67
|
-
readonly home?: string;
|
|
68
|
-
readonly fetch?: typeof fetch;
|
|
69
|
-
readonly now?: () => Date;
|
|
70
|
-
readonly cacheMs?: number;
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* The organization Graph for the local host (C1). Reads are cached briefly and
|
|
74
|
-
* shared while in flight; a failure is reported as `unavailable`, never as an
|
|
75
|
-
* empty Graph.
|
|
76
|
-
*/
|
|
77
|
-
export declare function createOrganizationGraphSource(options?: OrganizationGraphSourceOptions): Promise<LocalWebOrganizationGraph | {
|
|
78
|
-
readonly reason: string;
|
|
79
|
-
}>;
|
|
80
58
|
export {};
|