@unson/brainbase-mcp 0.39.0 → 0.41.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/foundation-http.d.ts +12 -2
- package/dist/foundation-http.js +183 -8
- package/dist/graph-web.d.ts +2 -0
- package/dist/graph-web.js +10 -1
- package/dist/judgment-history.js +3 -1
- package/dist/local-web-host.js +1 -1
- package/dist/portable-graph.d.ts +2 -2
- package/dist/portable-graph.js +4 -3
- package/dist/types.d.ts +32 -1
- package/package.json +2 -2
- package/ui/foundation-overview.js +17 -1
- package/ui/graph-own-share.js +4 -2
- package/ui/graph-projects-view.js +167 -11
- package/ui/graph-registry-view.js +135 -5
- package/ui/graph-view-shared.js +45 -4
- package/ui/judgment-history.js +5 -1
- package/ui/meeting-minutes.js +7 -3
- package/ui/objective-editor-http-port.js +21 -8
- package/ui/objective-editor.js +186 -24
- package/ui/objective-readback-contract.js +177 -0
- package/ui/outcome-knowledge.css +18 -1
- package/ui/outcome-knowledge.js +523 -55
- package/ui/people-and-companies-view.js +8 -0
- package/ui/project-workspace.js +177 -4
- package/ui/world/world-canvas-ui.css +32 -0
- package/ui/world/world-canvas-ui.js +30 -2
- package/ui/world/world-view.js +5 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { CompanyOsObjectives } from './company-os-objectives.js';
|
|
2
2
|
import { type FoundationStoreContext } from './foundation-store.js';
|
|
3
|
-
import type
|
|
3
|
+
import { type FoundationRef } from './foundation-catalog.js';
|
|
4
4
|
import type { FoundationRevision, ObjectiveDefinition } from './ontology-foundation.js';
|
|
5
|
-
import type { FoundationCatalogRecord } from './types.js';
|
|
5
|
+
import type { FoundationCatalogRecord, ObjectiveReadback } from './types.js';
|
|
6
6
|
/**
|
|
7
7
|
* HTTP boundary shared by the OSS Objective UI and organization adapters.
|
|
8
8
|
*
|
|
@@ -15,6 +15,7 @@ import type { FoundationCatalogRecord } from './types.js';
|
|
|
15
15
|
export declare const FOUNDATION_HTTP_CONTRACT_VERSION: "brainbase.foundation-http.v1";
|
|
16
16
|
export declare const FOUNDATION_HTTP_PREFIX: "/api/foundation";
|
|
17
17
|
export declare const FOUNDATION_OBJECTIVE_ROUTE_PREFIX: "/api/foundation/objectives";
|
|
18
|
+
export declare const FOUNDATION_OBJECTIVE_READBACK_CONTRACT_VERSION: "brainbase.objective-readback.v1";
|
|
18
19
|
export declare const FOUNDATION_OBJECTIVE_ROUTES: readonly string[];
|
|
19
20
|
type MaybePromise<T> = T | Promise<T>;
|
|
20
21
|
export interface FoundationHttpRouteContext {
|
|
@@ -75,6 +76,8 @@ export interface ObjectiveFoundationHttpRouteOptions {
|
|
|
75
76
|
/** Constraint relation reads/writes remain owned by the canonical adapter. */
|
|
76
77
|
listObjectiveConstraintRefs?: (reference: FoundationRevision, context: FoundationStoreContext) => MaybePromise<unknown>;
|
|
77
78
|
replaceObjectiveConstraintRefs?: (reference: FoundationRef, refs: readonly FoundationRevision[], context: FoundationStoreContext) => MaybePromise<unknown>;
|
|
79
|
+
/** Injectable clock for deterministic readback evidence; production uses the real clock. */
|
|
80
|
+
clock?: () => Date;
|
|
78
81
|
bodyLimitBytes?: number;
|
|
79
82
|
}
|
|
80
83
|
/** Typed, explicitly sanitized error for trusted host adapters. */
|
|
@@ -86,6 +89,13 @@ export declare class FoundationHttpError extends Error {
|
|
|
86
89
|
currentRevision?: string;
|
|
87
90
|
});
|
|
88
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Runtime guard for the Objective detail contract. It is exported so a BFF
|
|
94
|
+
* can fail closed when a canonical host returns an old or malformed payload.
|
|
95
|
+
* The guard only accepts source metadata that agrees with the returned
|
|
96
|
+
* FoundationCatalogRecord; it never creates a source from request data.
|
|
97
|
+
*/
|
|
98
|
+
export declare function isObjectiveReadbackPayload(value: unknown): value is ObjectiveReadback;
|
|
89
99
|
/**
|
|
90
100
|
* Compose OSS Company OS routes under one local server boundary. The
|
|
91
101
|
* resolver is called only for a matching route and the resulting trusted
|
package/dist/foundation-http.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FoundationStoreError } from './foundation-store.js';
|
|
2
|
+
import { assertFoundationCatalog, foundationKey } from './foundation-catalog.js';
|
|
2
3
|
/**
|
|
3
4
|
* HTTP boundary shared by the OSS Objective UI and organization adapters.
|
|
4
5
|
*
|
|
@@ -11,6 +12,7 @@ import { FoundationStoreError } from './foundation-store.js';
|
|
|
11
12
|
export const FOUNDATION_HTTP_CONTRACT_VERSION = 'brainbase.foundation-http.v1';
|
|
12
13
|
export const FOUNDATION_HTTP_PREFIX = '/api/foundation';
|
|
13
14
|
export const FOUNDATION_OBJECTIVE_ROUTE_PREFIX = `${FOUNDATION_HTTP_PREFIX}/objectives`;
|
|
15
|
+
export const FOUNDATION_OBJECTIVE_READBACK_CONTRACT_VERSION = 'brainbase.objective-readback.v1';
|
|
14
16
|
export const FOUNDATION_OBJECTIVE_ROUTES = Object.freeze([
|
|
15
17
|
'GET /api/foundation/objectives',
|
|
16
18
|
'GET /api/foundation/objectives/:id',
|
|
@@ -23,6 +25,8 @@ export const FOUNDATION_OBJECTIVE_ROUTES = Object.freeze([
|
|
|
23
25
|
]);
|
|
24
26
|
const DEFAULT_BODY_LIMIT_BYTES = 1024 * 1024;
|
|
25
27
|
const MUTATING_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);
|
|
28
|
+
const OBJECTIVE_READBACK_STATUSES = ['ready', 'error', 'unknown'];
|
|
29
|
+
const FOUNDATION_PROVENANCE_KINDS = new Set(['candidate', 'document', 'observation', 'decision', 'import']);
|
|
26
30
|
const AUTHORITY_FIELDS = new Set([
|
|
27
31
|
'acl',
|
|
28
32
|
'scope',
|
|
@@ -78,6 +82,143 @@ export class FoundationHttpError extends Error {
|
|
|
78
82
|
this.currentRevision = options.currentRevision;
|
|
79
83
|
}
|
|
80
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Runtime guard for the Objective detail contract. It is exported so a BFF
|
|
87
|
+
* can fail closed when a canonical host returns an old or malformed payload.
|
|
88
|
+
* The guard only accepts source metadata that agrees with the returned
|
|
89
|
+
* FoundationCatalogRecord; it never creates a source from request data.
|
|
90
|
+
*/
|
|
91
|
+
export function isObjectiveReadbackPayload(value) {
|
|
92
|
+
const object = asRecord(value);
|
|
93
|
+
if (!object
|
|
94
|
+
|| object.contract_version !== FOUNDATION_OBJECTIVE_READBACK_CONTRACT_VERSION
|
|
95
|
+
|| !OBJECTIVE_READBACK_STATUSES.includes(object.status)
|
|
96
|
+
|| !isCanonicalIsoTimestamp(object.checked_at)) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
if (object.status === 'ready') {
|
|
100
|
+
if (object.reason !== null)
|
|
101
|
+
return false;
|
|
102
|
+
const record = objectiveReadbackRecord(object.record);
|
|
103
|
+
const objective = objectiveReadbackRecord(object.objective);
|
|
104
|
+
const source = objectiveReadbackSourceValue(object.source);
|
|
105
|
+
if (!record || !objective || !source)
|
|
106
|
+
return false;
|
|
107
|
+
return sameObjectiveIdentity(record, objective)
|
|
108
|
+
&& source.source_id === record.definition.id
|
|
109
|
+
&& source.source_version === record.definition.revision
|
|
110
|
+
&& source.digest === record.digest
|
|
111
|
+
&& JSON.stringify(source.provenance) === JSON.stringify(record.definition.provenance);
|
|
112
|
+
}
|
|
113
|
+
return object.source === null
|
|
114
|
+
&& object.record === null
|
|
115
|
+
&& object.objective === null
|
|
116
|
+
&& objectiveReadbackReason(object.reason) !== null;
|
|
117
|
+
}
|
|
118
|
+
function objectiveReadbackSourceFromRecord(record) {
|
|
119
|
+
const normalized = objectiveReadbackRecord(record);
|
|
120
|
+
if (!normalized)
|
|
121
|
+
return null;
|
|
122
|
+
return {
|
|
123
|
+
source_type: 'foundation_revision_store',
|
|
124
|
+
source_id: normalized.definition.id,
|
|
125
|
+
source_version: normalized.definition.revision,
|
|
126
|
+
digest: normalized.digest,
|
|
127
|
+
// Keep the exact canonical provenance array. Do not derive it from the
|
|
128
|
+
// request, a UI label, or a BFF projection.
|
|
129
|
+
provenance: normalized.definition.provenance,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function objectiveReadbackPayload(status, checkedAt, record, reason) {
|
|
133
|
+
const source = status === 'ready' && record ? objectiveReadbackSourceFromRecord(record) : null;
|
|
134
|
+
if (status === 'ready' && (!source || !record)) {
|
|
135
|
+
throw new Error('objective_readback_source_unavailable');
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
contract_version: FOUNDATION_OBJECTIVE_READBACK_CONTRACT_VERSION,
|
|
139
|
+
status,
|
|
140
|
+
source,
|
|
141
|
+
checked_at: checkedAt,
|
|
142
|
+
record: status === 'ready' ? record : null,
|
|
143
|
+
objective: status === 'ready' ? record : null,
|
|
144
|
+
reason: status === 'ready' ? null : reason,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
function objectiveReadbackReason(value) {
|
|
148
|
+
const object = asRecord(value);
|
|
149
|
+
if (!object || typeof object.code !== 'string' || !object.code.trim()
|
|
150
|
+
|| typeof object.message !== 'string' || !object.message.trim())
|
|
151
|
+
return null;
|
|
152
|
+
return { code: object.code, message: object.message };
|
|
153
|
+
}
|
|
154
|
+
function objectiveReadbackRecord(value) {
|
|
155
|
+
const object = asRecord(value);
|
|
156
|
+
const definition = asRecord(object?.definition);
|
|
157
|
+
if (!object || !definition || definition.type !== 'objective'
|
|
158
|
+
|| typeof definition.id !== 'string' || !definition.id.trim())
|
|
159
|
+
return null;
|
|
160
|
+
const logicalKey = foundationKey({ type: 'objective', id: definition.id });
|
|
161
|
+
try {
|
|
162
|
+
// Reuse the canonical catalog guard so the readback cannot accept a
|
|
163
|
+
// partial draft shape, an arbitrary digest, or a non-canonical revision.
|
|
164
|
+
// The catalog guard deliberately validates definitions with `use: draft`:
|
|
165
|
+
// displayable draft/adopted Objectives remain readable without turning
|
|
166
|
+
// this transport check into a judgment/evaluation gate.
|
|
167
|
+
assertFoundationCatalog({
|
|
168
|
+
version: 1,
|
|
169
|
+
records: [object],
|
|
170
|
+
latest: {
|
|
171
|
+
[logicalKey]: {
|
|
172
|
+
id: definition.id,
|
|
173
|
+
type: 'objective',
|
|
174
|
+
revision: definition.revision,
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
relations: [],
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
return object;
|
|
184
|
+
}
|
|
185
|
+
function objectiveReadbackMatchesRequest(record, requestedId, requestedRevision) {
|
|
186
|
+
return record.definition.id === requestedId
|
|
187
|
+
&& (!requestedRevision || record.definition.revision === requestedRevision);
|
|
188
|
+
}
|
|
189
|
+
function objectiveReadbackSourceValue(value) {
|
|
190
|
+
const object = asRecord(value);
|
|
191
|
+
if (!object || object.source_type !== 'foundation_revision_store'
|
|
192
|
+
|| typeof object.source_id !== 'string' || !object.source_id.trim()
|
|
193
|
+
|| typeof object.source_version !== 'string' || !object.source_version.trim()
|
|
194
|
+
|| typeof object.digest !== 'string' || !object.digest.trim()
|
|
195
|
+
|| !Array.isArray(object.provenance)
|
|
196
|
+
|| !object.provenance.every(isFoundationProvenance))
|
|
197
|
+
return null;
|
|
198
|
+
return object;
|
|
199
|
+
}
|
|
200
|
+
function isFoundationProvenance(value) {
|
|
201
|
+
const object = asRecord(value);
|
|
202
|
+
if (!object)
|
|
203
|
+
return false;
|
|
204
|
+
return typeof object.sourceId === 'string'
|
|
205
|
+
&& object.sourceId.trim().length > 0
|
|
206
|
+
&& typeof object.sourceKind === 'string'
|
|
207
|
+
&& FOUNDATION_PROVENANCE_KINDS.has(object.sourceKind)
|
|
208
|
+
&& Array.isArray(object.evidenceIds)
|
|
209
|
+
&& object.evidenceIds.every((id) => typeof id === 'string');
|
|
210
|
+
}
|
|
211
|
+
function sameObjectiveIdentity(left, right) {
|
|
212
|
+
return left.definition.id === right.definition.id
|
|
213
|
+
&& left.definition.revision === right.definition.revision
|
|
214
|
+
&& left.digest === right.digest;
|
|
215
|
+
}
|
|
216
|
+
function isCanonicalIsoTimestamp(value) {
|
|
217
|
+
if (typeof value !== 'string' || !value.trim())
|
|
218
|
+
return false;
|
|
219
|
+
const parsed = new Date(value);
|
|
220
|
+
return !Number.isNaN(parsed.getTime()) && parsed.toISOString() === value;
|
|
221
|
+
}
|
|
81
222
|
/**
|
|
82
223
|
* Compose OSS Company OS routes under one local server boundary. The
|
|
83
224
|
* resolver is called only for a matching route and the resulting trusted
|
|
@@ -225,10 +366,34 @@ export function createObjectiveFoundationRoute(options) {
|
|
|
225
366
|
return methodNotAllowed(['GET', 'PUT']);
|
|
226
367
|
}
|
|
227
368
|
if (method === 'GET') {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
369
|
+
try {
|
|
370
|
+
const record = await options.objectives.readObjective(id, input.context, revision);
|
|
371
|
+
const checkedAt = objectiveReadbackCheckedAt(options.clock);
|
|
372
|
+
if (!record) {
|
|
373
|
+
return jsonResponse(404, objectiveReadbackPayload('unknown', checkedAt, null, { code: 'not_found', message: `Objective ${id} was not found` }));
|
|
374
|
+
}
|
|
375
|
+
const canonicalRecord = objectiveReadbackRecord(record);
|
|
376
|
+
if (!canonicalRecord) {
|
|
377
|
+
return jsonResponse(502, objectiveReadbackPayload('error', checkedAt, null, { code: 'invalid_canonical_record', message: 'The canonical Objective readback is incomplete' }));
|
|
378
|
+
}
|
|
379
|
+
if (!objectiveReadbackMatchesRequest(canonicalRecord, id, revision)) {
|
|
380
|
+
return jsonResponse(502, objectiveReadbackPayload('error', checkedAt, null, { code: 'readback_identity_mismatch', message: 'The canonical Objective readback does not match the requested id or revision' }));
|
|
381
|
+
}
|
|
382
|
+
// Keep the pre-readback `state` field for existing OSS consumers while
|
|
383
|
+
// the canonical envelope is adopted by newer clients.
|
|
384
|
+
return jsonResponse(200, {
|
|
385
|
+
...objectiveReadbackPayload('ready', checkedAt, canonicalRecord, null),
|
|
386
|
+
state: 'ready',
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
const normalized = normalizeHttpError(error);
|
|
391
|
+
const checkedAt = objectiveReadbackCheckedAt(options.clock);
|
|
392
|
+
const status = normalized.status === 404 || normalized.code === 'not_found'
|
|
393
|
+
? 'unknown'
|
|
394
|
+
: 'error';
|
|
395
|
+
return jsonResponse(normalized.status, objectiveReadbackPayload(status, checkedAt, null, { code: normalized.code, message: normalized.message }));
|
|
396
|
+
}
|
|
232
397
|
}
|
|
233
398
|
if (method === 'PUT') {
|
|
234
399
|
const current = await options.objectives.readObjective(id, input.context, revision);
|
|
@@ -252,13 +417,20 @@ export function createObjectiveFoundationRoute(options) {
|
|
|
252
417
|
}
|
|
253
418
|
async function readObjectiveReference(options, id, revision, context) {
|
|
254
419
|
const record = await options.objectives.readObjective(id, context, revision);
|
|
255
|
-
if (!record
|
|
420
|
+
if (!record)
|
|
256
421
|
throw new FoundationHttpError(404, 'not_found', `Objective ${id} was not found`);
|
|
422
|
+
const canonicalRecord = objectiveReadbackRecord(record);
|
|
423
|
+
if (!canonicalRecord) {
|
|
424
|
+
throw new FoundationHttpError(502, 'invalid_canonical_record', 'The canonical Objective readback is incomplete');
|
|
425
|
+
}
|
|
426
|
+
if (!objectiveReadbackMatchesRequest(canonicalRecord, id, revision)) {
|
|
427
|
+
throw new FoundationHttpError(502, 'readback_identity_mismatch', 'The canonical Objective readback does not match the requested id or revision');
|
|
428
|
+
}
|
|
257
429
|
return {
|
|
258
|
-
id:
|
|
430
|
+
id: canonicalRecord.definition.id,
|
|
259
431
|
type: 'objective',
|
|
260
|
-
revision:
|
|
261
|
-
digest:
|
|
432
|
+
revision: canonicalRecord.definition.revision,
|
|
433
|
+
digest: canonicalRecord.digest,
|
|
262
434
|
};
|
|
263
435
|
}
|
|
264
436
|
async function buildObjectiveDefinition(options, payload, input) {
|
|
@@ -337,6 +509,9 @@ function parseConstraintReferences(payload) {
|
|
|
337
509
|
function readinessPayload(readiness) {
|
|
338
510
|
return { state: 'ready', ready: readiness.ready, issues: readiness.issues };
|
|
339
511
|
}
|
|
512
|
+
function objectiveReadbackCheckedAt(clock) {
|
|
513
|
+
return (clock?.() ?? new Date()).toISOString();
|
|
514
|
+
}
|
|
340
515
|
function normalizeCollectionPayload(value, key) {
|
|
341
516
|
if (Array.isArray(value))
|
|
342
517
|
return { state: value.length ? 'ready' : 'empty', [key]: value, absence_confirmed: true };
|
package/dist/graph-web.d.ts
CHANGED
|
@@ -197,6 +197,8 @@ export interface GraphProjectDetail<S extends GraphWebAnySource = GraphWebSource
|
|
|
197
197
|
participants: GraphEdgeView[];
|
|
198
198
|
/** Other relations of the project (governs, owned_by, ...). */
|
|
199
199
|
relations: GraphEdgeView[];
|
|
200
|
+
/** True only after the same Graph v2 was fully read and strictly validated with no participant edge. */
|
|
201
|
+
participantsAbsenceConfirmed: boolean;
|
|
200
202
|
history: GraphCorrectionRecord[];
|
|
201
203
|
issues: GraphReadIssue[];
|
|
202
204
|
}
|
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 } from './canonical-graph.js';
|
|
3
|
+
import { isActiveAt, validateCanonicalGraph } 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_NEW_ENTITY_TYPES, GRAPH_CORRECTION_PROJECT_FIELDS, GRAPH_CORRECTIONS_FILE, graphRecordDigest, isProjectIconDataUrl, readGraphCorrectionHistory } from './graph-corrections.js';
|
|
6
6
|
import { ONTOLOGY_VERSION, portableOntology } from './ontology.js';
|
|
@@ -213,6 +213,14 @@ export async function readGraphProject(dataDir, projectId, options = {}) {
|
|
|
213
213
|
return projectDetailOf(loaded.graph, loaded.source, projectId, asOf, await loadReferences(dataDir));
|
|
214
214
|
}
|
|
215
215
|
function projectDetailOf(graph, source, projectId, asOf, references) {
|
|
216
|
+
try {
|
|
217
|
+
// loadPersonalOs intentionally preserves duplicate entity IDs for audit reads;
|
|
218
|
+
// participant absence must use a strict, same-snapshot Graph validation.
|
|
219
|
+
validateCanonicalGraph(graph);
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
throw new GraphWebError('unavailable', 'graph_unavailable', errorMessage(error));
|
|
223
|
+
}
|
|
216
224
|
const project = graph.entities.find((entity) => entity.id === projectId && entity.type === 'project');
|
|
217
225
|
if (!project)
|
|
218
226
|
throw new GraphWebError('not_found', 'project_not_found', `No project ${projectId}`);
|
|
@@ -232,6 +240,7 @@ function projectDetailOf(graph, source, projectId, asOf, references) {
|
|
|
232
240
|
},
|
|
233
241
|
participants,
|
|
234
242
|
relations,
|
|
243
|
+
participantsAbsenceConfirmed: participants.length === 0,
|
|
235
244
|
history: historyFor(references.history, touched),
|
|
236
245
|
issues: references.issues
|
|
237
246
|
};
|
package/dist/judgment-history.js
CHANGED
|
@@ -447,7 +447,9 @@ function validJournalEvent(value, sessionRef, key, eventKey) {
|
|
|
447
447
|
|| !sha256Text(text(value.input_digest))
|
|
448
448
|
|| !sha256Text(text(value.response_digest))
|
|
449
449
|
|| !sha256Text(text(value.event_fingerprint))
|
|
450
|
-
|| !(
|
|
450
|
+
|| !(value.display_line === undefined
|
|
451
|
+
|| value.display_line === null
|
|
452
|
+
|| text(value.display_line)))
|
|
451
453
|
return false;
|
|
452
454
|
return Boolean(eventKey && /^[a-f0-9]{64}$/u.test(eventKey)
|
|
453
455
|
&& sha256(text(value.tool_use_id)) === eventKey);
|
package/dist/local-web-host.js
CHANGED
|
@@ -586,7 +586,7 @@ export function createObjectiveFoundationModule(context) {
|
|
|
586
586
|
});
|
|
587
587
|
return {
|
|
588
588
|
id: 'foundation-objectives',
|
|
589
|
-
uiFiles: ['objective-editor.js', 'objective-editor.css', 'objective-editor-http-port.js'],
|
|
589
|
+
uiFiles: ['objective-editor.js', 'objective-editor.css', 'objective-editor-http-port.js', 'objective-readback-contract.js'],
|
|
590
590
|
async handle(request, response) {
|
|
591
591
|
const path = requestUrl(request).pathname;
|
|
592
592
|
if (path !== FOUNDATION_HTTP_PREFIX && !path.startsWith(`${FOUNDATION_HTTP_PREFIX}/`))
|
package/dist/portable-graph.d.ts
CHANGED
|
@@ -47,8 +47,8 @@ export interface PortableGraphOwner {
|
|
|
47
47
|
* `applicability`, constraint exception scopes).
|
|
48
48
|
*
|
|
49
49
|
* The result is a read projection, not a bundle to store or send: the input
|
|
50
|
-
* is not changed,
|
|
51
|
-
*
|
|
50
|
+
* is not changed, endpoint-dependent edge IDs are re-derived for the bound
|
|
51
|
+
* projection, and foundation digests are not recomputed. A bundle that
|
|
52
52
|
* already has another record with the owner's person ID is refused rather
|
|
53
53
|
* than merged.
|
|
54
54
|
*/
|
package/dist/portable-graph.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import { validateCanonicalGraph } from './canonical-graph.js';
|
|
2
|
+
import { canonicalEdgeId, validateCanonicalGraph } from './canonical-graph.js';
|
|
3
3
|
import { retrieveGraph } from './graph-retrieval.js';
|
|
4
4
|
/** The public, read-only interchange format for a canonical Graph v2 snapshot. */
|
|
5
5
|
export const PORTABLE_GRAPH_SCHEMA_VERSION = 1;
|
|
@@ -114,8 +114,8 @@ export const PORTABLE_GRAPH_OWNER_PLACEHOLDER = 'self';
|
|
|
114
114
|
* `applicability`, constraint exception scopes).
|
|
115
115
|
*
|
|
116
116
|
* The result is a read projection, not a bundle to store or send: the input
|
|
117
|
-
* is not changed,
|
|
118
|
-
*
|
|
117
|
+
* is not changed, endpoint-dependent edge IDs are re-derived for the bound
|
|
118
|
+
* projection, and foundation digests are not recomputed. A bundle that
|
|
119
119
|
* already has another record with the owner's person ID is refused rather
|
|
120
120
|
* than merged.
|
|
121
121
|
*/
|
|
@@ -160,6 +160,7 @@ export function bindPortableGraphOwner(bundle, owner) {
|
|
|
160
160
|
for (const edge of graph.edges) {
|
|
161
161
|
edge.fromId = swap(edge.fromId);
|
|
162
162
|
edge.toId = swap(edge.toId);
|
|
163
|
+
edge.id = canonicalEdgeId(edge);
|
|
163
164
|
}
|
|
164
165
|
for (const record of graph.foundation?.records ?? []) {
|
|
165
166
|
const definition = record.definition;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FoundationDefinition, FoundationRelationReference, FoundationRevision } from './ontology-foundation.js';
|
|
1
|
+
import type { FoundationDefinition, FoundationRelationReference, FoundationRevision, FoundationProvenance } from './ontology-foundation.js';
|
|
2
2
|
export type EntityKind = 'person' | 'org' | 'project' | 'relationship' | 'decision';
|
|
3
3
|
export type CanonicalEntityKind = 'person' | 'org' | 'project' | 'decision';
|
|
4
4
|
export type CoreRelation = 'member_of' | 'participates_in' | 'accountable_for' | 'owned_by' | 'related_to' | 'governs' | 'supersedes';
|
|
@@ -56,6 +56,37 @@ export interface FoundationCatalogRecord {
|
|
|
56
56
|
definition: FoundationDefinition;
|
|
57
57
|
digest: string;
|
|
58
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* A detail read is useful only when the host can show which canonical record
|
|
61
|
+
* was actually read. This envelope deliberately keeps the source separate
|
|
62
|
+
* from the record so a UI cannot mistake a client supplied proposal for a
|
|
63
|
+
* canonical provenance.
|
|
64
|
+
*/
|
|
65
|
+
export type ObjectiveReadbackStatus = 'ready' | 'error' | 'unknown';
|
|
66
|
+
export interface ObjectiveReadbackReason {
|
|
67
|
+
code: string;
|
|
68
|
+
message: string;
|
|
69
|
+
}
|
|
70
|
+
export interface ObjectiveReadbackSource {
|
|
71
|
+
source_type: 'foundation_revision_store';
|
|
72
|
+
source_id: string;
|
|
73
|
+
source_version: string;
|
|
74
|
+
digest: string;
|
|
75
|
+
provenance: readonly FoundationProvenance[];
|
|
76
|
+
}
|
|
77
|
+
export interface ObjectiveReadback {
|
|
78
|
+
contract_version: 'brainbase.objective-readback.v1';
|
|
79
|
+
status: ObjectiveReadbackStatus;
|
|
80
|
+
/** Null means that no canonical record was established by this read. */
|
|
81
|
+
source: ObjectiveReadbackSource | null;
|
|
82
|
+
/** The time at which the canonical read completed (or failed). */
|
|
83
|
+
checked_at: string;
|
|
84
|
+
record: FoundationCatalogRecord | null;
|
|
85
|
+
/** Compatibility alias retained for existing Objective UI consumers. */
|
|
86
|
+
objective: FoundationCatalogRecord | null;
|
|
87
|
+
/** Ready has no reason; error/unknown must explain the unresolved result. */
|
|
88
|
+
reason: ObjectiveReadbackReason | null;
|
|
89
|
+
}
|
|
59
90
|
export interface FoundationCatalog {
|
|
60
91
|
version: 1;
|
|
61
92
|
records: FoundationCatalogRecord[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unson/brainbase-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.41.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",
|
|
@@ -481,5 +481,5 @@
|
|
|
481
481
|
"engines": {
|
|
482
482
|
"node": ">=20"
|
|
483
483
|
},
|
|
484
|
-
"gitHead": "
|
|
484
|
+
"gitHead": "c460d39f08bd68e8630fcdd000a934017f41203c"
|
|
485
485
|
}
|
|
@@ -148,6 +148,17 @@ export function normalizeFoundationCatalog(value, expectedScopeId) {
|
|
|
148
148
|
if (scopeId !== expectedScopeId) {
|
|
149
149
|
throw error('scope_mismatch', '選択した対象範囲と応答の対象範囲が一致しません。');
|
|
150
150
|
}
|
|
151
|
+
const readStatus = nonEmpty(firstValue(value, 'read_status', 'readStatus'));
|
|
152
|
+
const catalogStatus = nonEmpty(value.status);
|
|
153
|
+
const knownReadableStatuses = new Set(['complete', 'ready', 'resolved', 'empty']);
|
|
154
|
+
const unreadableStatus = (readStatus && !knownReadableStatuses.has(readStatus))
|
|
155
|
+
|| (catalogStatus && !knownReadableStatuses.has(catalogStatus));
|
|
156
|
+
const collections = [value.objectives, value.variables, value.models, value.philosophies];
|
|
157
|
+
const hasRecords = collections.some((records) => Array.isArray(records) && records.length > 0);
|
|
158
|
+
const absenceConfirmed = value.absence_confirmed === true;
|
|
159
|
+
if (unreadableStatus || (!hasRecords && ('absence_confirmed' in value || readStatus || catalogStatus) && !absenceConfirmed)) {
|
|
160
|
+
throw error('unavailable', '目的・哲学・世界モデルの取得状態を確認できません。');
|
|
161
|
+
}
|
|
151
162
|
validateFoundationRecords(value.objectives, 'objective', '目的');
|
|
152
163
|
validateFoundationRecords(value.variables, 'variable', '変数');
|
|
153
164
|
validateFoundationRecords(value.models, 'model', '世界モデル');
|
|
@@ -159,6 +170,10 @@ export function normalizeFoundationCatalog(value, expectedScopeId) {
|
|
|
159
170
|
variables: value.variables,
|
|
160
171
|
models: value.models,
|
|
161
172
|
philosophies: value.philosophies,
|
|
173
|
+
...(readStatus ? { read_status: readStatus } : {}),
|
|
174
|
+
...(catalogStatus ? { status: catalogStatus } : {}),
|
|
175
|
+
...(typeof value.absence_confirmed === 'boolean' ? { absence_confirmed: value.absence_confirmed } : {}),
|
|
176
|
+
...(value.source !== undefined ? { source: value.source } : {}),
|
|
162
177
|
};
|
|
163
178
|
}
|
|
164
179
|
|
|
@@ -818,7 +833,8 @@ export function createFoundationOverview({ root, port, scopeId, document: explic
|
|
|
818
833
|
if (destroyed || requestGeneration !== generation) return state;
|
|
819
834
|
const code = cause?.code === 'scope_mismatch' ? 'scope_mismatch'
|
|
820
835
|
: cause?.code === 'invalid_schema' ? 'invalid'
|
|
821
|
-
: cause?.code === 'authorization_denied' || cause?.code === 'forbidden' ? 'denied'
|
|
836
|
+
: cause?.code === 'authorization_denied' || cause?.code === 'forbidden' ? 'denied'
|
|
837
|
+
: cause?.code === 'unavailable' ? 'unavailable' : 'error';
|
|
822
838
|
state.status = code;
|
|
823
839
|
state.error = {
|
|
824
840
|
code,
|
package/ui/graph-own-share.js
CHANGED
|
@@ -80,7 +80,8 @@ export function graphOwnShareSource(option, { fetcher } = {}) {
|
|
|
80
80
|
export function ownShareHandoverText(handover) {
|
|
81
81
|
if (!isRecord(handover) || !textOrNull(handover.graphId)) return null;
|
|
82
82
|
const where = textOrNull(handover.projectCode);
|
|
83
|
-
|
|
83
|
+
const digest = textOrNull(handover.digest) ?? '未確認';
|
|
84
|
+
return `束 ${handover.graphId}(${where ? `置き場所 ${where}、` : ''}digest ${digest}、${formatDateTime(handover.createdAt)} に送信)`;
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
/**
|
|
@@ -216,7 +217,8 @@ export function ownEntityDetail(doc, detail, { onClose, onRetry, onOpen } = {})
|
|
|
216
217
|
...(entity.type === 'project' ? [['目的', textOrNull(entity.goal)], ['状態', textOrNull(entity.status)]] : []),
|
|
217
218
|
]),
|
|
218
219
|
}));
|
|
219
|
-
|
|
220
|
+
const decisionBlock = decisionRecordBlock(doc, entity, loaded.decisionRecord);
|
|
221
|
+
if (decisionBlock) wrap.append(decisionBlock);
|
|
220
222
|
for (const [key, title, empty] of [['outgoing', '出る関係', 'この記録から出る関係はありません。'], ['incoming', '入る関係', 'この記録へ入る関係はありません。']]) {
|
|
221
223
|
const edges = Array.isArray(loaded[key]) ? loaded[key] : [];
|
|
222
224
|
wrap.append(workspaceRailBlock(doc, {
|