@unson/brainbase-mcp 0.15.7 → 0.16.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-web-http.d.ts +0 -7
- package/dist/graph-web-http.js +0 -14
- package/dist/graph-web.d.ts +2 -32
- package/dist/graph-web.js +1 -41
- package/dist/local-web-host.d.ts +0 -23
- package/dist/local-web-host.js +3 -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 +2 -2
- package/ui/graph-view-shared.js +0 -39
- package/ui/local-web-shell.js +3 -53
- 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
|
}
|
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
|
@@ -52,9 +52,6 @@ export function createGraphWebHttpHandler(options) {
|
|
|
52
52
|
if (route.name === 'corrections') {
|
|
53
53
|
if (request.method !== 'POST')
|
|
54
54
|
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
55
|
await assertWriteAllowed(options.assertWriteAllowed, request);
|
|
59
56
|
const body = await readJsonBody(request);
|
|
60
57
|
const result = await applyGraphCorrection(dataDir, body, { now: now() });
|
|
@@ -65,17 +62,6 @@ export function createGraphWebHttpHandler(options) {
|
|
|
65
62
|
throw methodNotAllowed('GET');
|
|
66
63
|
const asOf = queryValue(url, 'as_of');
|
|
67
64
|
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
65
|
switch (route.name) {
|
|
80
66
|
case 'status':
|
|
81
67
|
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;
|
|
@@ -374,21 +361,4 @@ export interface OwnerPrivateGraph {
|
|
|
374
361
|
* machine, so a relation's source ID is shown unresolved.
|
|
375
362
|
*/
|
|
376
363
|
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
364
|
export {};
|
package/dist/graph-web.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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
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';
|
|
6
6
|
import { ONTOLOGY_VERSION, portableOntology } from './ontology.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,
|
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;
|
|
@@ -782,8 +771,7 @@ export function createLocalWebHost(options = {}) {
|
|
|
782
771
|
journalRoot,
|
|
783
772
|
token,
|
|
784
773
|
now: options.now ?? (() => new Date()),
|
|
785
|
-
journalCache: new JudgmentValueProofJournalCache()
|
|
786
|
-
...(options.organizationGraph ? { organizationGraph: options.organizationGraph } : {})
|
|
774
|
+
journalCache: new JudgmentValueProofJournalCache()
|
|
787
775
|
};
|
|
788
776
|
const modules = defaultLocalWebModules(context);
|
|
789
777
|
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 {};
|
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises';
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
import { GraphWebError, openInMemoryGraph } from './graph-web.js';
|
|
5
1
|
import { canonicalEdgeId } from './canonical-graph.js';
|
|
6
2
|
import { canonicalGraphOntologyRelease } from './templates.js';
|
|
7
3
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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`).
|
|
12
8
|
*
|
|
13
9
|
* Relations come only from what the organization Graph states:
|
|
14
10
|
* - `member_of` person → project → `participates_in` (role = role_code)
|
|
@@ -19,8 +15,6 @@ import { canonicalGraphOntologyRelease } from './templates.js';
|
|
|
19
15
|
*/
|
|
20
16
|
export const ORGANIZATION_GRAPH_WEB_VERSION = 'organization-graph-web.v0';
|
|
21
17
|
const ENTITY_TYPES = ['project', 'person', 'org', 'decision'];
|
|
22
|
-
const FETCH_TIMEOUT_MS = 15_000;
|
|
23
|
-
const CACHE_MS = 60_000;
|
|
24
18
|
function isRecord(value) {
|
|
25
19
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
26
20
|
}
|
|
@@ -190,121 +184,3 @@ export function projectOrganizationGraph(records, owner) {
|
|
|
190
184
|
excluded: { inactive, unnamed, danglingRelations }
|
|
191
185
|
};
|
|
192
186
|
}
|
|
193
|
-
/** The owner's Brainbase server and token, read at request time (tokens rotate hourly). Never logged or returned. */
|
|
194
|
-
export async function readOrganizationAccess(home = homedir()) {
|
|
195
|
-
try {
|
|
196
|
-
const config = JSON.parse(await readFile(join(home, '.brainbase', 'config.json'), 'utf8'));
|
|
197
|
-
const tokens = JSON.parse(await readFile(join(home, '.brainbase', 'tokens.json'), 'utf8'));
|
|
198
|
-
const server = isRecord(config) ? text(config.server_url) : null;
|
|
199
|
-
const token = isRecord(tokens) ? text(tokens.access_token) : null;
|
|
200
|
-
if (!server || !/^https:\/\/[^/?#@]+$/u.test(server))
|
|
201
|
-
return { reason: 'server_url_missing_or_invalid' };
|
|
202
|
-
if (!token)
|
|
203
|
-
return { reason: 'access_token_missing' };
|
|
204
|
-
return { server, token };
|
|
205
|
-
}
|
|
206
|
-
catch {
|
|
207
|
-
return { reason: 'brainbase_auth_files_unreadable' };
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
async function getRecords(request, access, path) {
|
|
211
|
-
const controller = new AbortController();
|
|
212
|
-
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
213
|
-
try {
|
|
214
|
-
const response = await request(`${access.server}/api/info/graph/${path}`, {
|
|
215
|
-
method: 'GET',
|
|
216
|
-
headers: { Authorization: `Bearer ${access.token}`, Accept: 'application/json' },
|
|
217
|
-
redirect: 'error',
|
|
218
|
-
signal: controller.signal
|
|
219
|
-
});
|
|
220
|
-
if (response.status === 401 || response.status === 403) {
|
|
221
|
-
throw new GraphWebError('unavailable', 'organization_graph_auth_failed', `The organization Graph refused the owner's token (HTTP ${response.status}); sign in again`);
|
|
222
|
-
}
|
|
223
|
-
if (!response.ok)
|
|
224
|
-
throw new GraphWebError('unavailable', 'organization_graph_unavailable', `The organization Graph answered HTTP ${response.status} for ${path.split('?')[0]}`);
|
|
225
|
-
const body = await response.json();
|
|
226
|
-
if (!isRecord(body) || !Array.isArray(body.records))
|
|
227
|
-
throw new GraphWebError('unavailable', 'organization_graph_unavailable', 'The organization Graph response has no records');
|
|
228
|
-
return body.records;
|
|
229
|
-
}
|
|
230
|
-
catch (error) {
|
|
231
|
-
if (error instanceof GraphWebError)
|
|
232
|
-
throw error;
|
|
233
|
-
const reason = error instanceof Error && error.name === 'AbortError' ? 'timed out' : 'request failed';
|
|
234
|
-
throw new GraphWebError('unavailable', 'organization_graph_unavailable', `The organization Graph ${reason}`);
|
|
235
|
-
}
|
|
236
|
-
finally {
|
|
237
|
-
clearTimeout(timer);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
/**
|
|
241
|
-
* The organization Graph for the local host (C1). Reads are cached briefly and
|
|
242
|
-
* shared while in flight; a failure is reported as `unavailable`, never as an
|
|
243
|
-
* empty Graph.
|
|
244
|
-
*/
|
|
245
|
-
export async function createOrganizationGraphSource(options = {}) {
|
|
246
|
-
const home = options.home ?? homedir();
|
|
247
|
-
const initial = await readOrganizationAccess(home);
|
|
248
|
-
if ('reason' in initial)
|
|
249
|
-
return initial;
|
|
250
|
-
const request = options.fetch ?? fetch;
|
|
251
|
-
const now = options.now ?? (() => new Date());
|
|
252
|
-
const cacheMs = options.cacheMs ?? CACHE_MS;
|
|
253
|
-
let cached = null;
|
|
254
|
-
let inFlight = null;
|
|
255
|
-
async function load() {
|
|
256
|
-
const access = await readOrganizationAccess(home);
|
|
257
|
-
if ('reason' in access)
|
|
258
|
-
throw new GraphWebError('unavailable', 'organization_graph_not_connected', `The organization Graph cannot be read (${access.reason})`);
|
|
259
|
-
// The organization's words are read alongside; failing to read them never fails the Graph.
|
|
260
|
-
const termsRead = getRecords(request, access, 'entities?type=glossary_term&limit=500')
|
|
261
|
-
.then((records) => projectVocabularyTerms(records))
|
|
262
|
-
.catch(() => null);
|
|
263
|
-
const [project, person, org, decision, raci, memberOf, assignedTo] = await Promise.all([
|
|
264
|
-
getRecords(request, access, 'entities?type=project&limit=500'),
|
|
265
|
-
getRecords(request, access, 'entities?type=person&limit=500'),
|
|
266
|
-
getRecords(request, access, 'entities?type=org&limit=500'),
|
|
267
|
-
getRecords(request, access, 'entities?type=decision&limit=500'),
|
|
268
|
-
getRecords(request, access, 'entities?type=raci_assignment&limit=500'),
|
|
269
|
-
getRecords(request, access, 'edges?type=member_of'),
|
|
270
|
-
getRecords(request, access, 'edges?type=assigned_to')
|
|
271
|
-
]);
|
|
272
|
-
const projection = projectOrganizationGraph({
|
|
273
|
-
entities: {
|
|
274
|
-
project: project,
|
|
275
|
-
person: person,
|
|
276
|
-
org: org,
|
|
277
|
-
decision: decision,
|
|
278
|
-
raci_assignment: raci
|
|
279
|
-
},
|
|
280
|
-
memberOf: memberOf,
|
|
281
|
-
assignedTo: assignedTo
|
|
282
|
-
});
|
|
283
|
-
const source = { dataDir: null, graphFormat: 2, authority: 'organization_graph', server: access.server, readAt: now().toISOString() };
|
|
284
|
-
return { reader: openInMemoryGraph(projection.graph, source), graph: projection.graph, terms: await termsRead };
|
|
285
|
-
}
|
|
286
|
-
async function current() {
|
|
287
|
-
const at = now().getTime();
|
|
288
|
-
if (cached && at - cached.at < cacheMs)
|
|
289
|
-
return cached.loaded;
|
|
290
|
-
inFlight ??= load().then((loaded) => {
|
|
291
|
-
cached = { at: now().getTime(), loaded };
|
|
292
|
-
return loaded;
|
|
293
|
-
}).finally(() => {
|
|
294
|
-
inFlight = null;
|
|
295
|
-
});
|
|
296
|
-
return inFlight;
|
|
297
|
-
}
|
|
298
|
-
return {
|
|
299
|
-
server: initial.server,
|
|
300
|
-
async read() {
|
|
301
|
-
return (await current()).reader;
|
|
302
|
-
},
|
|
303
|
-
async readGraphFile() {
|
|
304
|
-
return (await current()).graph;
|
|
305
|
-
},
|
|
306
|
-
async readVocabularyTerms() {
|
|
307
|
-
return (await current()).terms;
|
|
308
|
-
}
|
|
309
|
-
};
|
|
310
|
-
}
|
|
@@ -131,11 +131,11 @@ export declare function resolveWorldVocabulary(config: WorldVocabularyConfig | u
|
|
|
131
131
|
/** Reads and checks a vocabulary file; an invalid file is an error, never silently ignored. */
|
|
132
132
|
export declare function readWorldVocabulary(path: string): Promise<WorldVocabularyConfig>;
|
|
133
133
|
/**
|
|
134
|
-
* The businesses from the same Graph the other screens read
|
|
135
|
-
*
|
|
136
|
-
* be read is a state with its reason, never zero businesses.
|
|
134
|
+
* The businesses from the same Graph the other screens read (this Mac's Graph).
|
|
135
|
+
* An organization host draws its own records with `projectOrganizationWorld`.
|
|
136
|
+
* A Graph that cannot be read is a state with its reason, never zero businesses.
|
|
137
137
|
*/
|
|
138
|
-
export declare function readWorldBusinesses(context: Pick<LocalWebModuleContext, 'dataDir'
|
|
138
|
+
export declare function readWorldBusinesses(context: Pick<LocalWebModuleContext, 'dataDir'>, options?: {
|
|
139
139
|
readonly vocabulary?: WorldVocabularyConfig;
|
|
140
140
|
readonly now?: () => Date;
|
|
141
141
|
}): Promise<WorldBusinessesResponse>;
|
|
@@ -174,21 +174,6 @@ export declare function readJudgmentPlaces(journalRoot: string, options?: {
|
|
|
174
174
|
readonly places: readonly WorldJudgmentPlace[];
|
|
175
175
|
readonly reason?: string;
|
|
176
176
|
}>;
|
|
177
|
-
/**
|
|
178
|
-
* The judgments of one organization, for a world drawn from that organization's Graph. A judgment
|
|
179
|
-
* belongs to the organization only when the repository it was worked in is one of the organization's
|
|
180
|
-
* businesses (a registered repository or the business code, the same rule as the world's placement).
|
|
181
|
-
* Every other judgment (another company's work, or one whose repository was not recorded) is withheld:
|
|
182
|
-
* only their count leaves the host, never their questions or repositories.
|
|
183
|
-
*/
|
|
184
|
-
export declare function readOrganizationJudgments(context: Pick<LocalWebModuleContext, 'journalRoot' | 'journalCache' | 'dataDir' | 'now'>, businesses: readonly Pick<WorldBusiness, 'code' | 'repositories'>[], workspaces?: Map<string, string | null>): Promise<{
|
|
185
|
-
readonly status: 'available' | 'unavailable';
|
|
186
|
-
readonly scope: 'organization';
|
|
187
|
-
readonly withheld: number;
|
|
188
|
-
readonly places: readonly WorldJudgmentPlace[];
|
|
189
|
-
readonly home: unknown;
|
|
190
|
-
readonly reason?: string;
|
|
191
|
-
}>;
|
|
192
177
|
export declare function createWorldExtension(options?: {
|
|
193
178
|
readonly vocabulary?: WorldVocabularyConfig;
|
|
194
179
|
}): LocalWebExtension;
|
package/dist/world-extension.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
|
-
import {
|
|
4
|
-
import { publicHome } from './value-proof-review-http.js';
|
|
3
|
+
import { readJudgmentValueProofJournal, } from './judgment-value-proof-review.js';
|
|
5
4
|
import { projectOrganizationGraph, projectVocabularyTerms } from './organization-graph-web.js';
|
|
6
5
|
import { readLocalGraphState } from './local-web-host.js';
|
|
7
6
|
import { loadPersonalOs } from './ssot.js';
|
|
@@ -224,37 +223,26 @@ export async function readWorldVocabulary(path) {
|
|
|
224
223
|
return { kinds, statuses };
|
|
225
224
|
}
|
|
226
225
|
/**
|
|
227
|
-
* The businesses from the same Graph the other screens read
|
|
228
|
-
*
|
|
229
|
-
* be read is a state with its reason, never zero businesses.
|
|
226
|
+
* The businesses from the same Graph the other screens read (this Mac's Graph).
|
|
227
|
+
* An organization host draws its own records with `projectOrganizationWorld`.
|
|
228
|
+
* A Graph that cannot be read is a state with its reason, never zero businesses.
|
|
230
229
|
*/
|
|
231
230
|
export async function readWorldBusinesses(context, options = {}) {
|
|
232
231
|
const now = (options.now ?? (() => new Date()))();
|
|
233
232
|
let graph;
|
|
234
233
|
let source;
|
|
235
|
-
let terms;
|
|
236
234
|
try {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
graph = await context.organizationGraph.readGraphFile();
|
|
242
|
-
source = { authority: 'organization_graph', server: context.organizationGraph.server };
|
|
243
|
-
terms = context.organizationGraph.readVocabularyTerms ? await context.organizationGraph.readVocabularyTerms().catch(() => null) : null;
|
|
244
|
-
}
|
|
245
|
-
else {
|
|
246
|
-
const state = await readLocalGraphState(context.dataDir);
|
|
247
|
-
if (state.status === 'not_initialized' || state.status === 'migration_required') {
|
|
248
|
-
return { status: state.status, version: WORLD_EXTENSION_VERSION, reason: state.status };
|
|
249
|
-
}
|
|
250
|
-
if (state.status !== 'ready')
|
|
251
|
-
return { status: 'unavailable', version: WORLD_EXTENSION_VERSION, reason: state.message ?? 'local_graph_unreadable' };
|
|
252
|
-
const os = await loadPersonalOs(context.dataDir);
|
|
253
|
-
if (os.graph.version !== 2)
|
|
254
|
-
return { status: 'migration_required', version: WORLD_EXTENSION_VERSION, reason: 'migration_required' };
|
|
255
|
-
graph = os.graph;
|
|
256
|
-
source = { authority: 'local', dataDir: context.dataDir };
|
|
235
|
+
const state = await readLocalGraphState(context.dataDir);
|
|
236
|
+
if (state.status === 'not_initialized' || state.status === 'migration_required') {
|
|
237
|
+
return { status: state.status, version: WORLD_EXTENSION_VERSION, reason: state.status };
|
|
257
238
|
}
|
|
239
|
+
if (state.status !== 'ready')
|
|
240
|
+
return { status: 'unavailable', version: WORLD_EXTENSION_VERSION, reason: state.message ?? 'local_graph_unreadable' };
|
|
241
|
+
const os = await loadPersonalOs(context.dataDir);
|
|
242
|
+
if (os.graph.version !== 2)
|
|
243
|
+
return { status: 'migration_required', version: WORLD_EXTENSION_VERSION, reason: 'migration_required' };
|
|
244
|
+
graph = os.graph;
|
|
245
|
+
source = { authority: 'local', dataDir: context.dataDir };
|
|
258
246
|
}
|
|
259
247
|
catch (error) {
|
|
260
248
|
return { status: 'unavailable', version: WORLD_EXTENSION_VERSION, reason: error instanceof Error ? error.message : 'graph_unreadable' };
|
|
@@ -265,7 +253,7 @@ export async function readWorldBusinesses(context, options = {}) {
|
|
|
265
253
|
version: WORLD_EXTENSION_VERSION,
|
|
266
254
|
source,
|
|
267
255
|
as_of: now.toISOString(),
|
|
268
|
-
vocabulary: resolveWorldVocabulary(options.vocabulary, projection.businesses
|
|
256
|
+
vocabulary: resolveWorldVocabulary(options.vocabulary, projection.businesses),
|
|
269
257
|
...projection,
|
|
270
258
|
};
|
|
271
259
|
}
|
|
@@ -315,44 +303,6 @@ export async function readJudgmentPlaces(journalRoot, options = {}) {
|
|
|
315
303
|
}));
|
|
316
304
|
return { status: 'available', places };
|
|
317
305
|
}
|
|
318
|
-
/**
|
|
319
|
-
* The judgments of one organization, for a world drawn from that organization's Graph. A judgment
|
|
320
|
-
* belongs to the organization only when the repository it was worked in is one of the organization's
|
|
321
|
-
* businesses (a registered repository or the business code, the same rule as the world's placement).
|
|
322
|
-
* Every other judgment (another company's work, or one whose repository was not recorded) is withheld:
|
|
323
|
-
* only their count leaves the host, never their questions or repositories.
|
|
324
|
-
*/
|
|
325
|
-
export async function readOrganizationJudgments(context, businesses, workspaces = new Map()) {
|
|
326
|
-
const journal = await readJudgmentValueProofJournal({ root: context.journalRoot, cache: context.journalCache });
|
|
327
|
-
if (journal.status !== 'available')
|
|
328
|
-
return { status: 'unavailable', scope: 'organization', withheld: 0, places: [], home: null, reason: 'journal_unreadable' };
|
|
329
|
-
const belongs = (workspace) => workspace !== null
|
|
330
|
-
&& businesses.some((business) => business.repositories.includes(workspace) || business.code === workspace);
|
|
331
|
-
const kept = [];
|
|
332
|
-
const places = [];
|
|
333
|
-
for (const entry of journal.entries) {
|
|
334
|
-
const workspace = await entryWorkspace(entry.file, workspaces);
|
|
335
|
-
if (!belongs(workspace))
|
|
336
|
-
continue;
|
|
337
|
-
kept.push(entry);
|
|
338
|
-
places.push({ decision_attempt_id: entry.proof.decision_attempt_id, workspace });
|
|
339
|
-
}
|
|
340
|
-
const [feedback, answers] = await Promise.all([
|
|
341
|
-
readJudgmentValueProofFeedback({ dataDir: context.dataDir }),
|
|
342
|
-
readJudgmentValueProofAnswers({ dataDir: context.dataDir }),
|
|
343
|
-
]);
|
|
344
|
-
// Rejected files are left out too: their names would say nothing about the organization they belong to.
|
|
345
|
-
const home = buildJudgmentValueProofReviewHome({ ...journal, entries: kept, rejected: [] }, feedback, { now: context.now(), answers });
|
|
346
|
-
return { status: 'available', scope: 'organization', withheld: journal.entries.length - kept.length, places, home: publicHome(home) };
|
|
347
|
-
}
|
|
348
|
-
async function entryWorkspace(valueProofFile, workspaces) {
|
|
349
|
-
const turnInputFile = `${valueProofFile.slice(0, -VALUE_PROOF_SUFFIX.length)}.turn-input.json`;
|
|
350
|
-
if (!workspaces.has(turnInputFile)) {
|
|
351
|
-
const turnInput = await readJsonFile(turnInputFile);
|
|
352
|
-
workspaces.set(turnInputFile, isRecord(turnInput) ? text(turnInput.project_code) : null);
|
|
353
|
-
}
|
|
354
|
-
return workspaces.get(turnInputFile) ?? null;
|
|
355
|
-
}
|
|
356
306
|
async function readJsonFile(path) {
|
|
357
307
|
try {
|
|
358
308
|
return JSON.parse(await readFile(path, 'utf8'));
|
|
@@ -375,16 +325,6 @@ function createWorldModule(context, read) {
|
|
|
375
325
|
writeJson(response, 200, await read());
|
|
376
326
|
return true;
|
|
377
327
|
}
|
|
378
|
-
if (path === `/api/extensions/${WORLD_EXTENSION_ID}/organization-judgments`) {
|
|
379
|
-
response.setHeader('Cache-Control', 'no-store');
|
|
380
|
-
const world = await read();
|
|
381
|
-
if (world.status !== 'ok' || world.source.authority !== 'organization_graph') {
|
|
382
|
-
writeJson(response, 200, { version: WORLD_EXTENSION_VERSION, status: 'unavailable', scope: 'organization', withheld: 0, places: [], home: null, reason: 'not_reading_organization_graph' });
|
|
383
|
-
return true;
|
|
384
|
-
}
|
|
385
|
-
writeJson(response, 200, { version: WORLD_EXTENSION_VERSION, ...(await readOrganizationJudgments(context, world.businesses, workspaces)) });
|
|
386
|
-
return true;
|
|
387
|
-
}
|
|
388
328
|
if (path === `/api/extensions/${WORLD_EXTENSION_ID}/judgment-places`) {
|
|
389
329
|
response.setHeader('Cache-Control', 'no-store');
|
|
390
330
|
writeJson(response, 200, { version: WORLD_EXTENSION_VERSION, ...(await readJudgmentPlaces(context.journalRoot, { cache: context.journalCache, workspaces })) });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unson/brainbase-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
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",
|
|
@@ -358,5 +358,5 @@
|
|
|
358
358
|
"engines": {
|
|
359
359
|
"node": ">=20"
|
|
360
360
|
},
|
|
361
|
-
"gitHead": "
|
|
361
|
+
"gitHead": "a0914d025914d4ada329b730aeddd6488f28bcfd"
|
|
362
362
|
}
|
package/ui/graph-view-shared.js
CHANGED
|
@@ -469,45 +469,6 @@ export function hostSourceNotice(sourceNotice, subject) {
|
|
|
469
469
|
return { ...sourceNotice, text: sourceNotice.text(subject ?? null) };
|
|
470
470
|
}
|
|
471
471
|
|
|
472
|
-
// ---------------------------------------------------------------------------
|
|
473
|
-
// The organization's web (Inspector UX-20261004-01)
|
|
474
|
-
|
|
475
|
-
const ORGANIZATION_RECORD_ID = /^[A-Za-z0-9_.:-]{1,200}$/u;
|
|
476
|
-
|
|
477
|
-
/** The organization web's origin the host was told about, or null. */
|
|
478
|
-
export function organizationWebOrigin(organizationGraph) {
|
|
479
|
-
if (organizationGraph?.status !== 'connected') return null;
|
|
480
|
-
const origin = organizationGraph.web_url;
|
|
481
|
-
return typeof origin === 'string' && /^https:\/\/[^/?#@\s]+$/u.test(origin) ? origin : null;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
/**
|
|
485
|
-
* Where a record opens in the organization web, `{ href, label }`, or null when it has no usable id. The
|
|
486
|
-
* organization web opens 情報と関係 by record id and プロジェクトと関係者 by project code, and keeps that record
|
|
487
|
-
* across sign-in. A project opens by its own code only: a sub-project's scope code is its parent's and
|
|
488
|
-
* would open the parent, so without its own code the record opens by id.
|
|
489
|
-
*/
|
|
490
|
-
export function organizationRecordLink(origin, screenId, record) {
|
|
491
|
-
const id = typeof record?.id === 'string' && ORGANIZATION_RECORD_ID.test(record.id) ? record.id : null;
|
|
492
|
-
const own = typeof record?.metadata?.code === 'string' ? record.metadata.code.trim() : '';
|
|
493
|
-
const code = own && ORGANIZATION_RECORD_ID.test(own) ? own : null;
|
|
494
|
-
if (screenId === 'projects' && record?.type === 'project' && code) {
|
|
495
|
-
return { href: `${origin}/?screen=projects&project=${encodeURIComponent(code)}`, label: '「プロジェクトと関係者」' };
|
|
496
|
-
}
|
|
497
|
-
return id ? { href: `${origin}/?screen=graph&entity_id=${encodeURIComponent(id)}`, label: '「情報と関係」' } : null;
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
export const ORGANIZATION_RECORD_LINK_TEXT = '組織版でこの記録を開く';
|
|
501
|
-
|
|
502
|
-
/** A link that opens `target` from organizationRecordLink in a new tab. */
|
|
503
|
-
export function organizationRecordAnchor(doc, target, className) {
|
|
504
|
-
return makeElement(doc, 'a', {
|
|
505
|
-
...(className ? { className } : {}),
|
|
506
|
-
text: ORGANIZATION_RECORD_LINK_TEXT,
|
|
507
|
-
attrs: { href: target.href, target: '_blank', rel: 'noopener noreferrer' },
|
|
508
|
-
});
|
|
509
|
-
}
|
|
510
|
-
|
|
511
472
|
/**
|
|
512
473
|
* A host's note that corrections are not available here, or null when it passed none. The host may
|
|
513
474
|
* pass a function of the selected record (`{ id, type, metadata }`) returning a string or an element,
|
package/ui/local-web-shell.js
CHANGED
|
@@ -21,7 +21,7 @@ import { createObjectiveEditorController } from './objective-editor.js';
|
|
|
21
21
|
import { createObjectiveEditorHttpPort } from './objective-editor-http-port.js';
|
|
22
22
|
import { createValueProofReviewUI } from './value-proof-review.js';
|
|
23
23
|
import { createWorldModelView } from './world-model-view.js';
|
|
24
|
-
import { createGraphClient
|
|
24
|
+
import { createGraphClient } from './graph-view-shared.js';
|
|
25
25
|
|
|
26
26
|
export const LOCAL_WEB_SHELL_CONTRACT_VERSION = 'brainbase.local-web-shell.v1';
|
|
27
27
|
|
|
@@ -203,58 +203,11 @@ function mountObjectives(container, context) {
|
|
|
203
203
|
return { editor, worldModel };
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
-
const ORGANIZATION_READ_ONLY_NOTE = '組織のGraphを読み取り専用で表示しています。登録の訂正は組織版で行ってください。';
|
|
207
|
-
// What the owner needs before leaving for the organization web (Inspector UX-20261004-01): a link to this
|
|
208
|
-
// very record there, who may correct it, and what happens after saving.
|
|
209
|
-
export { organizationRecordLink };
|
|
210
|
-
const organizationReadOnlyNoteLinked = (doc, origin, screenId) => (record) => {
|
|
211
|
-
const target = organizationRecordLink(origin, screenId, record);
|
|
212
|
-
if (!target) return ORGANIZATION_READ_ONLY_NOTE;
|
|
213
|
-
const note = makeElement(doc, 'span');
|
|
214
|
-
note.append(
|
|
215
|
-
makeElement(doc, 'span', { text: [
|
|
216
|
-
'組織のGraphを読み取り専用で表示しています。ここでは直せません。訂正の手順:',
|
|
217
|
-
`① 下のリンク(上の「出典」にも同じリンクがあります)から、組織版の${target.label}でこの記録を開きます。ログインしていなければSlackでログインすると、そのままこの記録に戻ります。`,
|
|
218
|
-
'② 直せるのは、組織版での役割がgmかceoで、このプロジェクトが属する事業(事業そのものなら、その事業)を許可されている人です。直せるのはプロジェクトの記録(名前・別名・要約・期間・目的・状態)だけで、人物の記録と、関係者の追加・終了は組織版でもまだ直せません。',
|
|
219
|
-
'③ 保存すると、理由といっしょに組織のGraphへ書き込まれ、組織版がその場で読み戻して表示します。この画面には、1分ほどたってから再読み込みすると反映されます。',
|
|
220
|
-
'',
|
|
221
|
-
].join('\n') }),
|
|
222
|
-
organizationRecordAnchor(doc, target),
|
|
223
|
-
);
|
|
224
|
-
return note;
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* The 出典 notice of a screen reading the organization Graph. Once a record is selected its link opens
|
|
229
|
-
* that record in the organization web; before that, the screen there.
|
|
230
|
-
*/
|
|
231
|
-
const organizationSourceText = (doc, origin, screenId, sourceText) => (record) => {
|
|
232
|
-
const target = record ? organizationRecordLink(origin, screenId, record) : null;
|
|
233
|
-
const link = target
|
|
234
|
-
? organizationRecordAnchor(doc, target)
|
|
235
|
-
: makeElement(doc, 'a', {
|
|
236
|
-
text: '組織版で開いて訂正する',
|
|
237
|
-
attrs: { href: `${origin}/?screen=${encodeURIComponent(screenId)}`, target: '_blank', rel: 'noopener noreferrer' },
|
|
238
|
-
});
|
|
239
|
-
const source = makeElement(doc, 'span');
|
|
240
|
-
source.append(makeElement(doc, 'span', { text: `${sourceText} ` }), link);
|
|
241
|
-
return source;
|
|
242
|
-
};
|
|
243
|
-
|
|
244
206
|
function mountGraphScreen(screenId, createView) {
|
|
245
207
|
return (container, context) => {
|
|
246
208
|
const viewRoot = makeElement(context.document, 'div');
|
|
247
209
|
makePage(container, context).append(viewRoot);
|
|
248
|
-
// Ledger C1: when the host reads the organization Graph, nothing here can be corrected.
|
|
249
|
-
const readOnly = context.organizationGraph?.status === 'connected';
|
|
250
|
-
const origin = readOnly ? organizationWebOrigin(context.organizationGraph) : null;
|
|
251
|
-
const sourceText = `組織のGraph(${context.organizationGraph?.server ?? '接続先不明'})を読み取り専用で表示しています。このMacのGraphではありません。`;
|
|
252
210
|
return createView({
|
|
253
|
-
...(readOnly ? {
|
|
254
|
-
canCorrect: false,
|
|
255
|
-
readOnlyNote: origin ? organizationReadOnlyNoteLinked(context.document, origin, screenId) : ORGANIZATION_READ_ONLY_NOTE,
|
|
256
|
-
sourceNotice: { label: '出典', text: origin ? organizationSourceText(context.document, origin, screenId, sourceText) : sourceText },
|
|
257
|
-
} : {}),
|
|
258
211
|
root: viewRoot,
|
|
259
212
|
rail: context.rail,
|
|
260
213
|
page: context.page,
|
|
@@ -334,7 +287,6 @@ function renderSource(doc, bar, status, onRecheck) {
|
|
|
334
287
|
bar.append(
|
|
335
288
|
fact('データ', data.data_dir),
|
|
336
289
|
fact('Graph', graphLabel, data.graph.status === 'ready' ? '' : 'is-warning'),
|
|
337
|
-
fact('組織のGraph', data.organization_graph?.status === 'connected' ? '読んでいます(読み取りのみ)' : '読んでいません'),
|
|
338
290
|
);
|
|
339
291
|
}
|
|
340
292
|
|
|
@@ -446,10 +398,8 @@ export function createLocalWebShell({
|
|
|
446
398
|
slot.replaceChildren();
|
|
447
399
|
const rail = rails.get(screen.id) ?? null;
|
|
448
400
|
rail?.replaceChildren();
|
|
449
|
-
const
|
|
450
|
-
const
|
|
451
|
-
const page = Object.freeze({ crumbs: Object.freeze(['あなたのBrainbase', screen.label]), source: readsOrganization ? '組織のGraph(読み取りのみ)' : screen.source ?? null });
|
|
452
|
-
const view = screen.mount(slot, Object.freeze({ ...context, rail, page, initialEntityId: entityId, organizationGraph })) ?? true;
|
|
401
|
+
const page = Object.freeze({ crumbs: Object.freeze(['あなたのBrainbase', screen.label]), source: screen.source ?? null });
|
|
402
|
+
const view = screen.mount(slot, Object.freeze({ ...context, rail, page, initialEntityId: entityId })) ?? true;
|
|
453
403
|
mounted.set(screen.id, view);
|
|
454
404
|
if (entityId && screen.id === 'today' && typeof view?.openDecision === 'function') view.openDecision(entityId);
|
|
455
405
|
if (entityId && screen.id === 'projects' && typeof view?.openProject === 'function') void view.openProject(entityId);
|
package/ui/world/world-view.js
CHANGED
|
@@ -25,7 +25,6 @@ import {
|
|
|
25
25
|
workspaceDetailEmpty,
|
|
26
26
|
workspaceButton,
|
|
27
27
|
} from '../../workspace-kit.js';
|
|
28
|
-
import { organizationRecordAnchor, organizationRecordLink, organizationWebOrigin } from '../../graph-view-shared.js';
|
|
29
28
|
|
|
30
29
|
export const WORLD_VIEW_CONTRACT_VERSION = 'brainbase.world-view.v0';
|
|
31
30
|
|
|
@@ -1431,7 +1430,7 @@ const JUDGMENT_UNAVAILABLE_TEXT = Object.freeze({
|
|
|
1431
1430
|
* engagement opens in the host's プロジェクトと関係者. The local web opens `#projects?project=<id>`; an
|
|
1432
1431
|
* organization web passes its own route.
|
|
1433
1432
|
*/
|
|
1434
|
-
export function createWorldView({ root, rail, page, document: explicitDocument, fetcher,
|
|
1433
|
+
export function createWorldView({ root, rail, page, document: explicitDocument, fetcher, projectHref = (project) => `#projects?project=${encodeURIComponent(project.id)}` }) {
|
|
1435
1434
|
const doc = explicitDocument ?? globalThis.document;
|
|
1436
1435
|
const reducedMotion = Boolean(globalThis.matchMedia?.('(prefers-reduced-motion: reduce)').matches);
|
|
1437
1436
|
const wrap = el(doc, 'div', { className: 'bb-world' });
|
|
@@ -1502,22 +1501,9 @@ export function createWorldView({ root, rail, page, document: explicitDocument,
|
|
|
1502
1501
|
return el(doc, 'a', { className: 'bb-world-rail-link', text: label, attrs: { href: projectHref(project) } });
|
|
1503
1502
|
}
|
|
1504
1503
|
|
|
1505
|
-
/**
|
|
1506
|
-
* The same project in the organization web, where it is corrected (Inspector UX-20261004-01). Only when
|
|
1507
|
-
* the host reads the organization Graph and was told where its web is.
|
|
1508
|
-
*/
|
|
1509
|
-
function organizationLink(project) {
|
|
1510
|
-
const origin = organizationWebOrigin(organizationGraph);
|
|
1511
|
-
if (!origin) return null;
|
|
1512
|
-
// A business without its own code carries its id as its code; that is not a code the organization web knows.
|
|
1513
|
-
const code = project.code && project.code !== project.id ? project.code : null;
|
|
1514
|
-
const target = organizationRecordLink(origin, 'projects', { id: project.id, type: 'project', metadata: { code } });
|
|
1515
|
-
return target ? organizationRecordAnchor(doc, target, 'bb-world-rail-link') : null;
|
|
1516
|
-
}
|
|
1517
|
-
|
|
1518
|
-
/** 詳しく見る: プロジェクトと関係者 here, and the organization web when there is one. */
|
|
1504
|
+
/** 詳しく見る: the same project in プロジェクトと関係者. */
|
|
1519
1505
|
function detailBlock(project, label) {
|
|
1520
|
-
const content = [projectLink(project, label)
|
|
1506
|
+
const content = [projectLink(project, label)];
|
|
1521
1507
|
return workspaceRailBlock(doc, { title: '詳しく見る', content });
|
|
1522
1508
|
}
|
|
1523
1509
|
|
|
@@ -1780,11 +1766,11 @@ export const screen = Object.freeze({
|
|
|
1780
1766
|
label: '世界',
|
|
1781
1767
|
usesGraph: false,
|
|
1782
1768
|
rail: true,
|
|
1783
|
-
source: '
|
|
1769
|
+
source: '手元のGraph・判断journal(読み取りのみ)',
|
|
1784
1770
|
mount(container, context) {
|
|
1785
1771
|
const root = context.document.createElement('div');
|
|
1786
1772
|
root.className = 'bb-shell-page';
|
|
1787
1773
|
container.append(root);
|
|
1788
|
-
return createWorldView({ root, rail: context.rail, page: context.page, document: context.document, fetcher: context.fetcher
|
|
1774
|
+
return createWorldView({ root, rail: context.rail, page: context.page, document: context.document, fetcher: context.fetcher });
|
|
1789
1775
|
},
|
|
1790
1776
|
});
|