@unson/brainbase-mcp 0.15.1 → 0.15.2
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/canonical-task-service.d.ts +11 -7
- package/dist/canonical-task-service.js +49 -21
- package/dist/cli.js +39 -3
- package/dist/graph-web-http.d.ts +7 -0
- package/dist/graph-web-http.js +14 -0
- package/dist/graph-web.d.ts +36 -6
- package/dist/graph-web.js +45 -4
- package/dist/judgment-value-proof-review.d.ts +24 -0
- package/dist/judgment-value-proof-review.js +120 -9
- package/dist/local-web-host.d.ts +23 -0
- package/dist/local-web-host.js +26 -7
- package/dist/organization-graph-web.d.ts +74 -0
- package/dist/organization-graph-web.js +275 -0
- package/dist/value-proof-review-http.d.ts +3 -0
- package/dist/value-proof-review-http.js +17 -6
- package/dist/world-extension.d.ts +148 -0
- package/dist/world-extension.js +277 -0
- package/package.json +5 -3
- package/ui/graph-projects-view.js +6 -3
- package/ui/graph-registry-view.js +6 -3
- package/ui/graph-view-shared.css +2 -0
- package/ui/graph-view-shared.js +9 -3
- package/ui/local-web-shell.js +82 -11
- package/ui/value-proof-review.js +61 -6
- package/ui/world/world-placement.js +45 -0
- package/ui/world/world-vendor.LICENSE.txt +23 -0
- package/ui/world/world-vendor.js +4220 -0
- package/ui/world/world-view.css +234 -0
- package/ui/world/world-view.js +1033 -0
|
@@ -9,6 +9,8 @@ export interface CanonicalTaskContext extends JsonRecord {
|
|
|
9
9
|
auditPrincipal?: CanonicalTaskPrincipal | null;
|
|
10
10
|
auditAuthSource?: string | null;
|
|
11
11
|
idempotencyKey?: string | null;
|
|
12
|
+
/** Opaque host-policy scope, not authority or task input. */
|
|
13
|
+
storageScope?: string;
|
|
12
14
|
}
|
|
13
15
|
export interface CanonicalTaskRecord extends JsonRecord {
|
|
14
16
|
id: string;
|
|
@@ -102,13 +104,13 @@ export interface CanonicalTaskAuditEntry extends JsonRecord {
|
|
|
102
104
|
source_refs: unknown[];
|
|
103
105
|
}
|
|
104
106
|
export interface CanonicalTaskRepository {
|
|
105
|
-
list(filters: CanonicalTaskListFilters): Promise<CanonicalTaskPage>;
|
|
106
|
-
search(filters: CanonicalTaskSearchFilters): Promise<CanonicalTaskPage>;
|
|
107
|
-
get(taskId: string): Promise<CanonicalTaskRecord | null>;
|
|
108
|
-
findByIdempotencyKey(key: string): Promise<CanonicalTaskRecord | null>;
|
|
109
|
-
create(input: JsonRecord): Promise<CanonicalTaskRecord>;
|
|
110
|
-
update(taskId: string, patch: JsonRecord): Promise<CanonicalTaskRecord>;
|
|
111
|
-
delete(taskId: string, expectedVersion: number): Promise<void>;
|
|
107
|
+
list(filters: CanonicalTaskListFilters, storageScope?: string): Promise<CanonicalTaskPage>;
|
|
108
|
+
search(filters: CanonicalTaskSearchFilters, storageScope?: string): Promise<CanonicalTaskPage>;
|
|
109
|
+
get(taskId: string, storageScope?: string): Promise<CanonicalTaskRecord | null>;
|
|
110
|
+
findByIdempotencyKey(key: string, storageScope?: string): Promise<CanonicalTaskRecord | null>;
|
|
111
|
+
create(input: JsonRecord, storageScope?: string): Promise<CanonicalTaskRecord>;
|
|
112
|
+
update(taskId: string, patch: JsonRecord, storageScope?: string): Promise<CanonicalTaskRecord>;
|
|
113
|
+
delete(taskId: string, expectedVersion: number, storageScope?: string): Promise<void>;
|
|
112
114
|
}
|
|
113
115
|
export interface CanonicalTaskAuditRepository {
|
|
114
116
|
upsertAuditLog(entry: CanonicalTaskAuditEntry): Promise<void>;
|
|
@@ -159,6 +161,7 @@ export interface CanonicalTaskPreparedDeleteRequest<T> {
|
|
|
159
161
|
versionFingerprint: string;
|
|
160
162
|
versionClaimKey: string;
|
|
161
163
|
principalNamespace: string;
|
|
164
|
+
storageScope?: string;
|
|
162
165
|
prepare: () => Promise<{
|
|
163
166
|
result: T;
|
|
164
167
|
task?: CanonicalTaskRecord | null;
|
|
@@ -237,6 +240,7 @@ export declare class CanonicalTaskService {
|
|
|
237
240
|
private normalizeUpdateInput;
|
|
238
241
|
private normalizeListFilters;
|
|
239
242
|
private normalizeSearchFilters;
|
|
243
|
+
private scopeArgs;
|
|
240
244
|
private normalizeContext;
|
|
241
245
|
private authorize;
|
|
242
246
|
private scopeFilters;
|
|
@@ -147,8 +147,12 @@ function validHttpUrl(value) {
|
|
|
147
147
|
return null;
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
|
-
function
|
|
151
|
-
return
|
|
150
|
+
function scopedNamespace(context) {
|
|
151
|
+
return context.storageScope === undefined ? principalNamespace(context.principal)
|
|
152
|
+
: `v2.${Buffer.from(JSON.stringify({ actor: context.principal, storageScope: context.storageScope })).toString('base64url')}`;
|
|
153
|
+
}
|
|
154
|
+
function operationKey(context, clientKey, prefix) {
|
|
155
|
+
return `${prefix}${scopedNamespace(context)}:${clientKey}`;
|
|
152
156
|
}
|
|
153
157
|
function extractPersistedFingerprint(task) {
|
|
154
158
|
const value = task.payload_fingerprint ?? task._payload_fingerprint;
|
|
@@ -224,7 +228,7 @@ export class CanonicalTaskService {
|
|
|
224
228
|
const normalizedFilters = this.normalizeListFilters(filters);
|
|
225
229
|
await this.authorize('list', normalizedContext, undefined, normalizedFilters);
|
|
226
230
|
const scoped = await this.scopeFilters('list', normalizedContext, normalizedFilters);
|
|
227
|
-
const page = await this.read(() => this.repository.list(scoped));
|
|
231
|
+
const page = await this.read(() => this.repository.list(scoped, ...this.scopeArgs(normalizedContext)));
|
|
228
232
|
return {
|
|
229
233
|
...normalizePage(page, (task) => this.normalizeResponse(task)),
|
|
230
234
|
as_of: this.clock().toISOString(),
|
|
@@ -238,7 +242,7 @@ export class CanonicalTaskService {
|
|
|
238
242
|
if (typeof this.repository.search !== 'function') {
|
|
239
243
|
throw new CanonicalTaskError('task_search_unavailable', 'Canonical Task search is unavailable', 503);
|
|
240
244
|
}
|
|
241
|
-
const page = await this.read(() => this.repository.search(scoped));
|
|
245
|
+
const page = await this.read(() => this.repository.search(scoped, ...this.scopeArgs(normalizedContext)));
|
|
242
246
|
const normalizedPage = normalizePage(page, (task) => this.normalizeResponse(task));
|
|
243
247
|
return {
|
|
244
248
|
...normalizedPage,
|
|
@@ -250,7 +254,7 @@ export class CanonicalTaskService {
|
|
|
250
254
|
async getTask(taskId, context) {
|
|
251
255
|
const normalizedId = normalizeString(taskId, 'task_id', { required: true, max: 200 });
|
|
252
256
|
const normalizedContext = await this.normalizeContext(context);
|
|
253
|
-
const task = await this.read(() => this.repository.get(normalizedId));
|
|
257
|
+
const task = await this.read(() => this.repository.get(normalizedId, ...this.scopeArgs(normalizedContext)));
|
|
254
258
|
if (!task)
|
|
255
259
|
throw new CanonicalTaskError('task_not_found', 'Task was not found', 404, { task_id: normalizedId });
|
|
256
260
|
await this.authorize('read', normalizedContext, task);
|
|
@@ -261,10 +265,10 @@ export class CanonicalTaskService {
|
|
|
261
265
|
const clientKey = this.requireIdempotencyKey(normalizedContext);
|
|
262
266
|
const payload = this.normalizeCreateInput(input);
|
|
263
267
|
await this.authorize('create', normalizedContext, undefined, payload);
|
|
264
|
-
const operation = operationKey(normalizedContext
|
|
268
|
+
const operation = operationKey(normalizedContext, clientKey, 'api:');
|
|
265
269
|
const inputFingerprint = fingerprint(payload);
|
|
266
270
|
const recover = async () => {
|
|
267
|
-
const existing = await this.read(() => this.repository.findByIdempotencyKey(operation));
|
|
271
|
+
const existing = await this.read(() => this.repository.findByIdempotencyKey(operation, ...this.scopeArgs(normalizedContext)));
|
|
268
272
|
if (!existing)
|
|
269
273
|
return { recovered: false };
|
|
270
274
|
const existingFingerprint = extractPersistedFingerprint(existing);
|
|
@@ -288,7 +292,7 @@ export class CanonicalTaskService {
|
|
|
288
292
|
version: 1,
|
|
289
293
|
idempotency_key: operation,
|
|
290
294
|
payload_fingerprint: inputFingerprint,
|
|
291
|
-
}));
|
|
295
|
+
}, ...this.scopeArgs(normalizedContext)));
|
|
292
296
|
return this.normalizeResponse(created);
|
|
293
297
|
};
|
|
294
298
|
const result = await this.executeOperation({
|
|
@@ -299,6 +303,8 @@ export class CanonicalTaskService {
|
|
|
299
303
|
recover,
|
|
300
304
|
run,
|
|
301
305
|
});
|
|
306
|
+
if (normalizedContext.storageScope !== undefined)
|
|
307
|
+
await this.authorize('read', normalizedContext, result);
|
|
302
308
|
const normalized = this.normalizeResponse(result);
|
|
303
309
|
// Audit each invocation, including a replay/recovery. The deterministic
|
|
304
310
|
// entry id lets an upsert sink collapse retries without losing evidence
|
|
@@ -367,9 +373,11 @@ export class CanonicalTaskService {
|
|
|
367
373
|
// operation may legitimately recover after the delete removed the row.
|
|
368
374
|
// The fallback path still performs the ordinary read and authorization
|
|
369
375
|
// checks before invoking the local operation runner.
|
|
376
|
+
if (normalizedContext.storageScope !== undefined)
|
|
377
|
+
await this.authorize('delete', normalizedContext);
|
|
370
378
|
let task = null;
|
|
371
379
|
if (!this.operationRepository?.executePreparedDelete) {
|
|
372
|
-
task = await this.read(() => this.repository.get(normalizedId));
|
|
380
|
+
task = await this.read(() => this.repository.get(normalizedId, ...this.scopeArgs(normalizedContext)));
|
|
373
381
|
if (!task)
|
|
374
382
|
throw new CanonicalTaskError('task_not_found', 'Task was not found', 404, { task_id: normalizedId });
|
|
375
383
|
await this.authorize('delete', normalizedContext, task);
|
|
@@ -378,16 +386,19 @@ export class CanonicalTaskService {
|
|
|
378
386
|
// The client key identifies the delete command. The task/version claim is
|
|
379
387
|
// carried separately so an operation coordinator can atomically claim the
|
|
380
388
|
// exact row version without allowing another actor to replay the command.
|
|
381
|
-
const operation = operationKey(normalizedContext
|
|
382
|
-
const
|
|
389
|
+
const operation = operationKey(normalizedContext, clientKey, 'delete:');
|
|
390
|
+
const scopePrefix = normalizedContext.storageScope === undefined ? '' : `${Buffer.from(normalizedContext.storageScope).toString('base64url')}:`;
|
|
391
|
+
const versionClaimKey = `task-version:${scopePrefix}${normalizedId}:${version}`;
|
|
383
392
|
const versionFingerprint = fingerprint({
|
|
384
393
|
kind: 'delete',
|
|
394
|
+
...(normalizedContext.storageScope === undefined ? {} : { storageScope: normalizedContext.storageScope }),
|
|
385
395
|
taskId: normalizedId,
|
|
386
396
|
expectedVersion: version,
|
|
387
397
|
});
|
|
388
|
-
const actorNamespace =
|
|
398
|
+
const actorNamespace = scopedNamespace(normalizedContext);
|
|
389
399
|
const operationFingerprint = fingerprint({
|
|
390
400
|
kind: 'delete',
|
|
401
|
+
...(normalizedContext.storageScope === undefined ? {} : { storageScope: normalizedContext.storageScope }),
|
|
391
402
|
taskId: normalizedId,
|
|
392
403
|
expectedVersion: version,
|
|
393
404
|
principalNamespace: actorNamespace,
|
|
@@ -398,7 +409,7 @@ export class CanonicalTaskService {
|
|
|
398
409
|
version: version + 1,
|
|
399
410
|
};
|
|
400
411
|
const prepare = async () => {
|
|
401
|
-
const current = await this.read(() => this.repository.get(normalizedId));
|
|
412
|
+
const current = await this.read(() => this.repository.get(normalizedId, ...this.scopeArgs(normalizedContext)));
|
|
402
413
|
if (!current)
|
|
403
414
|
throw new CanonicalTaskError('task_not_found', 'Task was not found', 404, { task_id: normalizedId });
|
|
404
415
|
await this.authorize('delete', normalizedContext, current);
|
|
@@ -408,23 +419,25 @@ export class CanonicalTaskService {
|
|
|
408
419
|
result: {
|
|
409
420
|
...result,
|
|
410
421
|
_audit_source_refs: Array.isArray(current.source_refs) ? current.source_refs : [],
|
|
422
|
+
...(normalizedContext.storageScope === undefined ? {} : { _storage_scope: normalizedContext.storageScope }),
|
|
411
423
|
},
|
|
412
424
|
task: current,
|
|
413
425
|
authorizationSnapshot: {
|
|
414
426
|
task_id: normalizedId,
|
|
415
427
|
task_version: current.version,
|
|
428
|
+
...(normalizedContext.storageScope === undefined ? {} : { storage_scope: normalizedContext.storageScope }),
|
|
416
429
|
actor: normalizedContext.principal,
|
|
417
430
|
auth_source: normalizedContext.authSource ?? null,
|
|
418
431
|
},
|
|
419
432
|
};
|
|
420
433
|
};
|
|
421
|
-
const findTask = async () => this.read(() => this.repository.get(normalizedId)).then((current) => {
|
|
434
|
+
const findTask = async () => this.read(() => this.repository.get(normalizedId, ...this.scopeArgs(normalizedContext))).then((current) => {
|
|
422
435
|
if (current)
|
|
423
436
|
task = current;
|
|
424
437
|
return current;
|
|
425
438
|
});
|
|
426
439
|
const removeTask = async (current) => {
|
|
427
|
-
const taskToRemove = current ?? await this.read(() => this.repository.get(normalizedId));
|
|
440
|
+
const taskToRemove = current ?? await this.read(() => this.repository.get(normalizedId, ...this.scopeArgs(normalizedContext)));
|
|
428
441
|
if (!taskToRemove)
|
|
429
442
|
throw new CanonicalTaskError('task_not_found', 'Task was not found', 404, { task_id: normalizedId });
|
|
430
443
|
task = taskToRemove;
|
|
@@ -433,7 +446,7 @@ export class CanonicalTaskService {
|
|
|
433
446
|
// actor cannot delete a task using a previously prepared authorization.
|
|
434
447
|
await this.authorize('delete', normalizedContext, taskToRemove);
|
|
435
448
|
this.assertVersion(taskToRemove, version);
|
|
436
|
-
await this.read(() => this.repository.delete(normalizedId, version));
|
|
449
|
+
await this.read(() => this.repository.delete(normalizedId, version, ...this.scopeArgs(normalizedContext)));
|
|
437
450
|
};
|
|
438
451
|
let deleted;
|
|
439
452
|
if (this.operationRepository?.executePreparedDelete) {
|
|
@@ -444,6 +457,7 @@ export class CanonicalTaskService {
|
|
|
444
457
|
versionFingerprint,
|
|
445
458
|
versionClaimKey,
|
|
446
459
|
principalNamespace: actorNamespace,
|
|
460
|
+
...(normalizedContext.storageScope === undefined ? {} : { storageScope: normalizedContext.storageScope }),
|
|
447
461
|
prepare,
|
|
448
462
|
findTask,
|
|
449
463
|
removeTask,
|
|
@@ -463,6 +477,9 @@ export class CanonicalTaskService {
|
|
|
463
477
|
},
|
|
464
478
|
});
|
|
465
479
|
}
|
|
480
|
+
if (normalizedContext.storageScope !== undefined && deleted._storage_scope !== normalizedContext.storageScope) {
|
|
481
|
+
throw new CanonicalTaskError('task_not_found', 'Task was not found', 404);
|
|
482
|
+
}
|
|
466
483
|
// A completed replay may return without prepare/findTask, because the
|
|
467
484
|
// durable operation result is sufficient. Keep the audit target stable in
|
|
468
485
|
// that case while avoiding a second task-store read that would turn a
|
|
@@ -494,11 +511,11 @@ export class CanonicalTaskService {
|
|
|
494
511
|
async versionedMutation(input) {
|
|
495
512
|
const taskId = normalizeString(input.taskId, 'task_id', { required: true, max: 200 });
|
|
496
513
|
const expectedVersion = this.requireExpectedVersion(input.expectedVersion);
|
|
497
|
-
const current = await this.read(() => this.repository.get(taskId));
|
|
514
|
+
const current = await this.read(() => this.repository.get(taskId, ...this.scopeArgs(input.context)));
|
|
498
515
|
if (!current)
|
|
499
516
|
throw new CanonicalTaskError('task_not_found', 'Task was not found', 404, { task_id: taskId });
|
|
500
517
|
await this.authorize(input.action, input.context, current, input.patch);
|
|
501
|
-
const operation = operationKey(input.context
|
|
518
|
+
const operation = operationKey(input.context, `${taskId}:${expectedVersion}`, `${input.action}:`);
|
|
502
519
|
const mutationTimestamp = this.clock().toISOString();
|
|
503
520
|
const patch = {
|
|
504
521
|
...input.patch,
|
|
@@ -518,6 +535,7 @@ export class CanonicalTaskService {
|
|
|
518
535
|
patch: input.patch,
|
|
519
536
|
transitionStatus: input.transitionStatus ?? null,
|
|
520
537
|
principal: input.context.principal,
|
|
538
|
+
...(input.context.storageScope === undefined ? {} : { storageScope: input.context.storageScope }),
|
|
521
539
|
});
|
|
522
540
|
const wasApplied = (candidate) => candidate.version === expectedVersion + 1
|
|
523
541
|
&& candidate._last_operation_key === operation
|
|
@@ -532,7 +550,7 @@ export class CanonicalTaskService {
|
|
|
532
550
|
}
|
|
533
551
|
}
|
|
534
552
|
const recover = async () => {
|
|
535
|
-
const latest = await this.read(() => this.repository.get(taskId));
|
|
553
|
+
const latest = await this.read(() => this.repository.get(taskId, ...this.scopeArgs(input.context)));
|
|
536
554
|
if (!latest)
|
|
537
555
|
throw new CanonicalTaskError('task_not_found', 'Task was not found', 404, { task_id: taskId });
|
|
538
556
|
await this.authorize(input.action, input.context, latest, input.patch);
|
|
@@ -548,7 +566,7 @@ export class CanonicalTaskService {
|
|
|
548
566
|
return { recovered: false };
|
|
549
567
|
};
|
|
550
568
|
const run = async () => {
|
|
551
|
-
const latest = await this.read(() => this.repository.get(taskId));
|
|
569
|
+
const latest = await this.read(() => this.repository.get(taskId, ...this.scopeArgs(input.context)));
|
|
552
570
|
if (!latest)
|
|
553
571
|
throw new CanonicalTaskError('task_not_found', 'Task was not found', 404, { task_id: taskId });
|
|
554
572
|
if (wasApplied(latest))
|
|
@@ -564,7 +582,7 @@ export class CanonicalTaskService {
|
|
|
564
582
|
...patch,
|
|
565
583
|
_last_operation_key: operation,
|
|
566
584
|
_last_operation_fingerprint: operationFingerprint,
|
|
567
|
-
}));
|
|
585
|
+
}, ...this.scopeArgs(input.context)));
|
|
568
586
|
return this.normalizeResponse(updated);
|
|
569
587
|
};
|
|
570
588
|
const result = await this.executeOperation({
|
|
@@ -575,6 +593,8 @@ export class CanonicalTaskService {
|
|
|
575
593
|
recover,
|
|
576
594
|
run,
|
|
577
595
|
});
|
|
596
|
+
if (input.context.storageScope !== undefined)
|
|
597
|
+
await this.authorize(input.action, input.context, result, input.patch);
|
|
578
598
|
const normalized = this.normalizeResponse(result);
|
|
579
599
|
const auditAction = input.action === 'update'
|
|
580
600
|
? 'canonical_task.updated'
|
|
@@ -684,6 +704,9 @@ export class CanonicalTaskService {
|
|
|
684
704
|
const limit = normalizeLimit(filters.limit, 20);
|
|
685
705
|
return { ...normalized, q, query: q, tokens, limit };
|
|
686
706
|
}
|
|
707
|
+
scopeArgs(context) {
|
|
708
|
+
return context.storageScope === undefined ? [] : [context.storageScope];
|
|
709
|
+
}
|
|
687
710
|
async normalizeContext(context) {
|
|
688
711
|
if (!context || typeof context !== 'object') {
|
|
689
712
|
throw new CanonicalTaskError('unauthenticated_context', 'A task context is required', 401);
|
|
@@ -702,6 +725,10 @@ export class CanonicalTaskService {
|
|
|
702
725
|
? await this.policy.normalizeContext(normalized)
|
|
703
726
|
: normalized;
|
|
704
727
|
try {
|
|
728
|
+
if (fromPolicy.storageScope !== undefined && (typeof fromPolicy.storageScope !== 'string'
|
|
729
|
+
|| !fromPolicy.storageScope.trim() || /\p{Cc}/u.test(fromPolicy.storageScope))) {
|
|
730
|
+
throw new Error('Storage scope is invalid');
|
|
731
|
+
}
|
|
705
732
|
return { ...fromPolicy, principal: normalizeCanonicalTaskPrincipal(fromPolicy.principal) };
|
|
706
733
|
}
|
|
707
734
|
catch (error) {
|
|
@@ -759,6 +786,7 @@ export class CanonicalTaskService {
|
|
|
759
786
|
actor_type: actor.type,
|
|
760
787
|
actor_principal: actor,
|
|
761
788
|
actor_namespace: principalNamespace(actor),
|
|
789
|
+
...(context.storageScope === undefined ? {} : { storage_scope: context.storageScope }),
|
|
762
790
|
auth_source: context.auditAuthSource ?? context.authSource ?? null,
|
|
763
791
|
action,
|
|
764
792
|
target_type: 'canonical_task',
|
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import { createOrganizationGraphConfig, createOrganizationGraphClient } from './
|
|
|
4
4
|
import { listLocalMemories, memoryListJson, registerSelectedMemories, registrationPreviewJson, renderMemoryList, renderRegistrationPreview, renderRegistrationResult, selectLocalMemories } from './personal-memory-handover.js';
|
|
5
5
|
import { constants, realpathSync } from 'node:fs';
|
|
6
6
|
import { access, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
7
|
-
import { delimiter, dirname, isAbsolute, join } from 'node:path';
|
|
7
|
+
import { delimiter, dirname, isAbsolute, join, resolve } from 'node:path';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
9
|
import { initializePersonalOs, loadPersonalOs, migrateCanonicalGraph, mutatePersonalOs } from './ssot.js';
|
|
10
10
|
import { diagnoseGraph } from './graph-diagnosis.js';
|
|
@@ -21,6 +21,8 @@ import { renderGuidedFirstRun } from './guided-onboarding.js';
|
|
|
21
21
|
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
|
+
import { createWorldExtension, readWorldVocabulary } from './world-extension.js';
|
|
25
|
+
import { createOrganizationGraphSource } from './organization-graph-web.js';
|
|
24
26
|
import { createLocalWebHost, LOCAL_WEB_DEFAULT_PORT } from './local-web-host.js';
|
|
25
27
|
export async function runCli(argv = process.argv.slice(2), io = process) {
|
|
26
28
|
const parsed = parseArgs(argv);
|
|
@@ -1043,7 +1045,38 @@ async function webServe(parsed, io, command) {
|
|
|
1043
1045
|
if (!Number.isInteger(port) || port < 0 || port > 65535) {
|
|
1044
1046
|
throw new Error(`${command} requires --port to be an integer between 0 and 65535`);
|
|
1045
1047
|
}
|
|
1046
|
-
|
|
1048
|
+
// World view (ledger P17): the businesses as a city above the home. Off unless the flag is given.
|
|
1049
|
+
const world = parsed.flags.has('world');
|
|
1050
|
+
// The owner's words for project kinds and statuses (labels, buildings, lifecycle phases).
|
|
1051
|
+
const worldVocabularyPath = first(parsed, 'world-vocabulary');
|
|
1052
|
+
if (worldVocabularyPath !== undefined && !world)
|
|
1053
|
+
throw new Error(`${command} --world-vocabulary requires --world`);
|
|
1054
|
+
const worldVocabulary = worldVocabularyPath === undefined ? undefined : await readWorldVocabulary(resolve(worldVocabularyPath));
|
|
1055
|
+
// Ledger C1: the Graph screens read the owner's organization Graph read only.
|
|
1056
|
+
let organizationGraph = null;
|
|
1057
|
+
if (parsed.flags.has('organization-graph')) {
|
|
1058
|
+
const source = await createOrganizationGraphSource();
|
|
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`);
|
|
1061
|
+
}
|
|
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
|
+
}
|
|
1074
|
+
const { server } = createLocalWebHost({
|
|
1075
|
+
dataDir,
|
|
1076
|
+
journalRoot,
|
|
1077
|
+
extensions: world ? [createWorldExtension(worldVocabulary ? { vocabulary: worldVocabulary } : {})] : [],
|
|
1078
|
+
...(organizationGraph ? { organizationGraph } : {})
|
|
1079
|
+
});
|
|
1047
1080
|
await new Promise((resolveListen, rejectListen) => {
|
|
1048
1081
|
server.once('error', rejectListen);
|
|
1049
1082
|
server.listen(port, '127.0.0.1', () => resolveListen());
|
|
@@ -1057,6 +1090,9 @@ async function webServe(parsed, io, command) {
|
|
|
1057
1090
|
`- 目的と現状: ${origin}/#objectives`,
|
|
1058
1091
|
`- プロジェクトと関係者: ${origin}/#projects`,
|
|
1059
1092
|
`- 情報と関係: ${origin}/#graph`,
|
|
1093
|
+
...(world ? [`- 世界: ${origin}/#world`] : []),
|
|
1094
|
+
...(organizationGraph ? [`組織のGraph(読み取りのみ): ${organizationGraph.server}`] : []),
|
|
1095
|
+
...(organizationGraph?.webUrl ? [`組織版(訂正の入口): ${organizationGraph.webUrl}`] : []),
|
|
1060
1096
|
`データ: ${dataDir}`,
|
|
1061
1097
|
`判断journal: ${journalRoot}`,
|
|
1062
1098
|
'終了: Ctrl+C',
|
|
@@ -1213,7 +1249,7 @@ function usage() {
|
|
|
1213
1249
|
brainbase judgment:install --target codex [--autonomy-mode off|canary|on] [--autonomy-project code] [--dry-run] [--output path]
|
|
1214
1250
|
brainbase judgment:hook [--autonomy-mode off|canary|on] [--autonomy-project code]
|
|
1215
1251
|
brainbase doctor [--dir path] [--judgment-hooks path]
|
|
1216
|
-
brainbase web:serve [--dir path] [--journal path] [--port n]
|
|
1252
|
+
brainbase web:serve [--dir path] [--journal path] [--port n] [--world [--world-vocabulary file]] [--organization-graph [--organization-web https-origin]]
|
|
1217
1253
|
brainbase review:serve [--dir path] [--journal path] [--port n] (web:serveの別名)
|
|
1218
1254
|
`;
|
|
1219
1255
|
}
|
package/dist/graph-web-http.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import { type GraphWebAnySource, type InMemoryGraphReader } from './graph-web.js';
|
|
2
3
|
export declare const GRAPH_WEB_HTTP_VERSION: "graph-web-http.v1";
|
|
3
4
|
export interface GraphWebHttpOptions {
|
|
4
5
|
/** Personal OS data directory chosen by the host. Never taken from the request. */
|
|
@@ -11,6 +12,12 @@ export interface GraphWebHttpOptions {
|
|
|
11
12
|
*/
|
|
12
13
|
readonly assertWriteAllowed?: (request: IncomingMessage) => void | Promise<void>;
|
|
13
14
|
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>>;
|
|
14
21
|
}
|
|
15
22
|
export type GraphWebHttpHandler = (request: IncomingMessage, response: ServerResponse) => Promise<boolean>;
|
|
16
23
|
/**
|
package/dist/graph-web-http.js
CHANGED
|
@@ -52,6 +52,9 @@ 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
|
+
}
|
|
55
58
|
await assertWriteAllowed(options.assertWriteAllowed, request);
|
|
56
59
|
const body = await readJsonBody(request);
|
|
57
60
|
const result = await applyGraphCorrection(dataDir, body, { now: now() });
|
|
@@ -62,6 +65,17 @@ export function createGraphWebHttpHandler(options) {
|
|
|
62
65
|
throw methodNotAllowed('GET');
|
|
63
66
|
const asOf = queryValue(url, 'as_of');
|
|
64
67
|
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
|
+
}
|
|
65
79
|
switch (route.name) {
|
|
66
80
|
case 'status':
|
|
67
81
|
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 } from './types.js';
|
|
4
|
+
import type { CanonicalEdge, CanonicalEntityKind, CoreRelation, GraphFileV2 } from './types.js';
|
|
5
5
|
/**
|
|
6
6
|
* Read views of the canonical local Graph for the local Web screens
|
|
7
7
|
* "プロジェクトと関係者" and "情報と関係".
|
|
@@ -34,7 +34,20 @@ export interface GraphWebOwnerPrivateSource {
|
|
|
34
34
|
graphFormat: 2;
|
|
35
35
|
authority: 'owner_private';
|
|
36
36
|
}
|
|
37
|
-
|
|
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;
|
|
38
51
|
export interface GraphMigrationRequired {
|
|
39
52
|
status: 'migration_required';
|
|
40
53
|
source: GraphWebSource;
|
|
@@ -132,10 +145,10 @@ export interface GraphEdgeView {
|
|
|
132
145
|
};
|
|
133
146
|
digest: string;
|
|
134
147
|
}
|
|
135
|
-
export interface GraphWebStatus {
|
|
148
|
+
export interface GraphWebStatus<S extends GraphWebAnySource = GraphWebSource> {
|
|
136
149
|
status: 'ok';
|
|
137
150
|
version: typeof GRAPH_WEB_VERSION;
|
|
138
|
-
source:
|
|
151
|
+
source: S;
|
|
139
152
|
asOf: string;
|
|
140
153
|
ontology: {
|
|
141
154
|
id: string;
|
|
@@ -289,9 +302,9 @@ export interface GraphObjectiveList<S extends GraphWebAnySource = GraphWebSource
|
|
|
289
302
|
/** True when the foundation was read and holds no objective. */
|
|
290
303
|
absenceConfirmed: boolean;
|
|
291
304
|
}
|
|
292
|
-
export interface GraphOntologySummary {
|
|
305
|
+
export interface GraphOntologySummary<S extends GraphWebAnySource = GraphWebSource> {
|
|
293
306
|
status: 'ok';
|
|
294
|
-
source:
|
|
307
|
+
source: S;
|
|
295
308
|
asOf: string;
|
|
296
309
|
ontology: {
|
|
297
310
|
id: string;
|
|
@@ -361,4 +374,21 @@ export interface OwnerPrivateGraph {
|
|
|
361
374
|
* machine, so a relation's source ID is shown unresolved.
|
|
362
375
|
*/
|
|
363
376
|
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>;
|
|
364
394
|
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 } 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_PROJECT_FIELDS, GRAPH_CORRECTIONS_FILE, graphRecordDigest, readGraphCorrectionHistory } from './graph-corrections.js';
|
|
6
6
|
import { ONTOLOGY_VERSION, portableOntology } from './ontology.js';
|
|
@@ -207,12 +207,13 @@ export async function readGraphOntology(dataDir, options = {}) {
|
|
|
207
207
|
const loaded = await loadForRead(dataDir);
|
|
208
208
|
if ('status' in loaded)
|
|
209
209
|
return loaded;
|
|
210
|
-
|
|
211
|
-
|
|
210
|
+
return ontologyOf(loaded.graph, loaded.source, resolveAsOf(options));
|
|
211
|
+
}
|
|
212
|
+
function ontologyOf(graph, source, asOf) {
|
|
212
213
|
const concepts = new Map(portableOntology.domains.types.concepts.map((concept) => [concept.id, concept.meaning]));
|
|
213
214
|
return {
|
|
214
215
|
status: 'ok',
|
|
215
|
-
source
|
|
216
|
+
source,
|
|
216
217
|
asOf,
|
|
217
218
|
ontology: { ...graph.ontology, currentVersion: ONTOLOGY_VERSION, upToDate: graph.ontology.version === ONTOLOGY_VERSION },
|
|
218
219
|
entityTypes: ENTITY_KINDS.map((kind) => ({
|
|
@@ -304,6 +305,46 @@ export function openOwnerPrivateGraph(bundle, owner) {
|
|
|
304
305
|
}
|
|
305
306
|
});
|
|
306
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
|
+
}
|
|
307
348
|
function decisionRecordView(record) {
|
|
308
349
|
return {
|
|
309
350
|
title: record.title,
|
|
@@ -12,6 +12,11 @@ export declare const JUDGMENT_VALUE_PROOF_FEEDBACK_LAYERS: readonly JudgmentValu
|
|
|
12
12
|
export interface JudgmentValueProofJournalEntry {
|
|
13
13
|
readonly file: string;
|
|
14
14
|
readonly proof: JudgmentValueProof;
|
|
15
|
+
/**
|
|
16
|
+
* For a judgment waiting for the owner: when another turn of the same conversation was first finalized
|
|
17
|
+
* after it, i.e. the conversation moved on (the owner answered there or changed the subject); null otherwise.
|
|
18
|
+
*/
|
|
19
|
+
readonly conversation_moved_on_at?: string | null;
|
|
15
20
|
}
|
|
16
21
|
export interface JudgmentValueProofJournalRejection {
|
|
17
22
|
readonly file: string;
|
|
@@ -88,6 +93,8 @@ export interface JudgmentValueProofReviewItem {
|
|
|
88
93
|
readonly feedback_history: readonly JudgmentValueProofFeedbackRecord[];
|
|
89
94
|
/** The owner's recorded answer to a judgment returned to them; null when there is none. */
|
|
90
95
|
readonly answer?: JudgmentValueProofAnswerRecord | null;
|
|
96
|
+
/** See `JudgmentValueProofJournalEntry.conversation_moved_on_at`. Such a judgment is no longer answered here. */
|
|
97
|
+
readonly conversation_moved_on_at?: string | null;
|
|
91
98
|
}
|
|
92
99
|
export type JudgmentValueProofReviewHome = {
|
|
93
100
|
readonly status: 'unavailable';
|
|
@@ -157,12 +164,29 @@ export interface JudgmentDelegationMap {
|
|
|
157
164
|
readonly continued_after_ask: readonly JudgmentDelegationItemRef[];
|
|
158
165
|
}
|
|
159
166
|
export declare function defaultJudgmentJournalRoot(dataDir?: string): string;
|
|
167
|
+
/**
|
|
168
|
+
* Remembers which value proofs each folder of a judgment journal held, so that a later read lists only
|
|
169
|
+
* the folders that changed. A folder is listed again when its inode, mtime or ctime differs from the
|
|
170
|
+
* listing, or when it was listed within a few seconds of its last change. The judgment Host creates
|
|
171
|
+
* value proofs and finals by linking a new file and never rewrites them, so every new or removed file
|
|
172
|
+
* changes the folder's mtime. It keeps no proof contents; every read still reads each proof.
|
|
173
|
+
* Use one cache per journal.
|
|
174
|
+
*/
|
|
175
|
+
export declare class JudgmentValueProofJournalCache {
|
|
176
|
+
#private;
|
|
177
|
+
/** The value proof files in `folder`, reusing the last listing while the folder is unchanged. */
|
|
178
|
+
valueProofFiles(folder: string): Promise<readonly string[]>;
|
|
179
|
+
/** Forgets folders that are no longer in the journal. */
|
|
180
|
+
retainOnly(folders: ReadonlySet<string>): void;
|
|
181
|
+
}
|
|
160
182
|
/**
|
|
161
183
|
* Reads saved judgment value proofs from `<root>/*.value-proof.json` and
|
|
162
184
|
* `<root>/<session>/*.value-proof.json`. The journal is read-only here.
|
|
185
|
+
* With `cache`, folders unchanged since an earlier read are not listed again; without it every folder is listed.
|
|
163
186
|
*/
|
|
164
187
|
export declare function readJudgmentValueProofJournal(options?: {
|
|
165
188
|
readonly root?: string;
|
|
189
|
+
readonly cache?: JudgmentValueProofJournalCache;
|
|
166
190
|
}): Promise<JudgmentValueProofJournalRead>;
|
|
167
191
|
export declare function classifyJudgmentValueProof(proof: JudgmentValueProof): JudgmentValueProofReviewSection;
|
|
168
192
|
/** Reads all recorded feedback in append order. A missing file means no feedback has been recorded. */
|