@unson/brainbase-mcp 0.32.0 → 0.34.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/graph-maintenance-engine.d.ts +44 -0
- package/dist/graph-maintenance-engine.js +804 -0
- package/dist/memory-scope-policy.d.ts +43 -0
- package/dist/memory-scope-policy.js +159 -0
- package/dist/ontology-kernel.d.ts +102 -0
- package/dist/ontology-kernel.js +681 -0
- package/dist/ontology-publication.d.ts +6 -0
- package/dist/ontology-publication.js +44 -0
- package/dist/ontology-registry.d.ts +32 -0
- package/dist/ontology-registry.js +202 -0
- package/dist/ontology-release-trust.d.ts +4 -0
- package/dist/ontology-release-trust.js +121 -0
- package/package.json +26 -2
- package/ui/world/world-view.js +24 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const ONTOLOGY_PUBLICATION_RECEIPT_SCHEMA_VERSION = "1.0.0";
|
|
2
|
+
export declare function sha256(bytes: any): string;
|
|
3
|
+
export declare function canonicalize(value: any): any;
|
|
4
|
+
export declare function canonicalJson(value: any): string;
|
|
5
|
+
export declare function publicationReceiptContractErrors(receipt: any): string[];
|
|
6
|
+
export declare function verifyPublicationReceipt(receipt: any, publicKeyPem: any): boolean;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Moved from brainbase-unson server/services/ontology-publication.js without changing behavior.
|
|
2
|
+
// Only type annotations were added; values stay open JSON objects as in the original.
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
4
|
+
import { createHash, verify } from 'node:crypto';
|
|
5
|
+
export const ONTOLOGY_PUBLICATION_RECEIPT_SCHEMA_VERSION = '1.0.0';
|
|
6
|
+
export function sha256(bytes) {
|
|
7
|
+
return createHash('sha256').update(bytes).digest('hex');
|
|
8
|
+
}
|
|
9
|
+
export function canonicalize(value) {
|
|
10
|
+
if (Array.isArray(value))
|
|
11
|
+
return value.map(canonicalize);
|
|
12
|
+
if (value && typeof value === 'object') {
|
|
13
|
+
return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonicalize(value[key])]));
|
|
14
|
+
}
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
export function canonicalJson(value) {
|
|
18
|
+
return JSON.stringify(canonicalize(value));
|
|
19
|
+
}
|
|
20
|
+
export function publicationReceiptContractErrors(receipt) {
|
|
21
|
+
const errors = [];
|
|
22
|
+
if (receipt?.payload?.schema_version !== ONTOLOGY_PUBLICATION_RECEIPT_SCHEMA_VERSION) {
|
|
23
|
+
errors.push('schema_version');
|
|
24
|
+
}
|
|
25
|
+
if (typeof receipt?.payload?.actor_entity_id !== 'string' || !receipt.payload.actor_entity_id.trim()) {
|
|
26
|
+
errors.push('actor_entity_id');
|
|
27
|
+
}
|
|
28
|
+
const issuedAt = receipt?.payload?.issued_at;
|
|
29
|
+
if (typeof issuedAt !== 'string'
|
|
30
|
+
|| !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(issuedAt)
|
|
31
|
+
|| Number.isNaN(Date.parse(issuedAt))
|
|
32
|
+
|| new Date(issuedAt).toISOString() !== issuedAt) {
|
|
33
|
+
errors.push('issued_at');
|
|
34
|
+
}
|
|
35
|
+
return errors;
|
|
36
|
+
}
|
|
37
|
+
export function verifyPublicationReceipt(receipt, publicKeyPem) {
|
|
38
|
+
if (receipt?.signature_algorithm !== 'ed25519' || !receipt?.payload || !receipt?.signature || !receipt?.key_id) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
if (publicationReceiptContractErrors(receipt).length)
|
|
42
|
+
return false;
|
|
43
|
+
return verify(null, Buffer.from(canonicalJson(receipt.payload)), publicKeyPem, Buffer.from(receipt.signature, 'base64'));
|
|
44
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { OntologyKernel } from './ontology-kernel.js';
|
|
2
|
+
export declare class OntologyRegistry {
|
|
3
|
+
[key: string]: any;
|
|
4
|
+
constructor({ rootDir, configDir, publicKeyPem }?: {
|
|
5
|
+
rootDir?: string | undefined;
|
|
6
|
+
configDir?: string | undefined;
|
|
7
|
+
publicKeyPem?: string | undefined;
|
|
8
|
+
});
|
|
9
|
+
hasCurrent(): boolean;
|
|
10
|
+
resolve({ version, asOf }?: any): {
|
|
11
|
+
kernel: OntologyKernel;
|
|
12
|
+
digest: string;
|
|
13
|
+
entry: any;
|
|
14
|
+
publicationVerification: {
|
|
15
|
+
status: string;
|
|
16
|
+
key_id: any;
|
|
17
|
+
signature_algorithm: any;
|
|
18
|
+
trust_source: string;
|
|
19
|
+
receipt_digest: any;
|
|
20
|
+
reason?: undefined;
|
|
21
|
+
} | {
|
|
22
|
+
status: string;
|
|
23
|
+
reason: any;
|
|
24
|
+
key_id?: undefined;
|
|
25
|
+
signature_algorithm?: undefined;
|
|
26
|
+
trust_source?: undefined;
|
|
27
|
+
receipt_digest?: undefined;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
interpretHistory(snapshot?: any, { version, asOf }?: any): any;
|
|
31
|
+
unverifiedHistory(snapshot: any, asOf: any, reason: any): any;
|
|
32
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// Moved from brainbase-unson server/services/ontology-registry.js without changing behavior.
|
|
2
|
+
// Only type annotations were added; values stay open JSON objects as in the original.
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
import { readFileSync } from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { OntologyError, OntologyKernel } from './ontology-kernel.js';
|
|
8
|
+
import { hasCompleteReceiptMetadata, hasReceiptMetadata, loadTrustedPublicKeys, verifyPublishedReceipt } from './ontology-release-trust.js';
|
|
9
|
+
function parseJson(bytes, label) {
|
|
10
|
+
try {
|
|
11
|
+
return JSON.parse(bytes.toString('utf8'));
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
throw new OntologyError('ONTOLOGY_MANIFEST_INVALID', `Invalid JSON in ${label}`, {
|
|
15
|
+
cause: error.message
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function parseTime(value, label) {
|
|
20
|
+
const timestamp = Date.parse(value);
|
|
21
|
+
if (!Number.isFinite(timestamp)) {
|
|
22
|
+
throw new OntologyError('ONTOLOGY_MANIFEST_INVALID', `Invalid ${label}: ${value}`);
|
|
23
|
+
}
|
|
24
|
+
return timestamp;
|
|
25
|
+
}
|
|
26
|
+
export class OntologyRegistry {
|
|
27
|
+
constructor({ rootDir = process.cwd(), configDir = 'config/ontology', publicKeyPem = process.env.ONTOLOGY_PUBLICATION_SIGNING_PUBLIC_KEY || '' } = {}) {
|
|
28
|
+
this.configDir = path.resolve(rootDir, configDir);
|
|
29
|
+
this.releasesDir = path.resolve(this.configDir, 'releases');
|
|
30
|
+
this.publicKeyPem = publicKeyPem;
|
|
31
|
+
try {
|
|
32
|
+
this.trustedPublicKeys = loadTrustedPublicKeys(this.configDir);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
throw new OntologyError('ONTOLOGY_MANIFEST_INVALID', error.message);
|
|
36
|
+
}
|
|
37
|
+
this.index = parseJson(readFileSync(path.join(this.configDir, 'index.json')), 'ontology index');
|
|
38
|
+
if (!Array.isArray(this.index.releases)) {
|
|
39
|
+
throw new OntologyError('ONTOLOGY_MANIFEST_INVALID', 'Ontology index releases must be an array');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
hasCurrent() {
|
|
43
|
+
return typeof this.index.current === 'string' && this.index.current.length > 0;
|
|
44
|
+
}
|
|
45
|
+
resolve({ version, asOf } = {}) {
|
|
46
|
+
let entry;
|
|
47
|
+
if (version) {
|
|
48
|
+
entry = this.index.releases.find((release) => release.version === version);
|
|
49
|
+
}
|
|
50
|
+
else if (asOf) {
|
|
51
|
+
const target = parseTime(asOf, 'asOf');
|
|
52
|
+
entry = this.index.releases
|
|
53
|
+
.filter((release) => (hasCompleteReceiptMetadata(release) || release.version === this.index.current)
|
|
54
|
+
&& parseTime(release.effective_at, 'release effective_at') <= target)
|
|
55
|
+
.sort((left, right) => parseTime(right.effective_at, 'release effective_at') - parseTime(left.effective_at, 'release effective_at'))[0];
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
if (!this.hasCurrent()) {
|
|
59
|
+
throw new OntologyError('ONTOLOGY_CURRENT_UNAVAILABLE', 'No current ontology release has been published');
|
|
60
|
+
}
|
|
61
|
+
entry = this.index.releases.find((release) => release.version === this.index.current);
|
|
62
|
+
}
|
|
63
|
+
if (!entry) {
|
|
64
|
+
throw new OntologyError('ONTOLOGY_VERSION_UNKNOWN', `Ontology release was not found: ${version || asOf}`, {
|
|
65
|
+
version: version || null,
|
|
66
|
+
as_of: asOf || null
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
const releasePath = path.resolve(this.configDir, entry.path);
|
|
70
|
+
const relativePath = path.relative(this.releasesDir, releasePath);
|
|
71
|
+
if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
|
|
72
|
+
throw new OntologyError('ONTOLOGY_MANIFEST_INVALID', 'Ontology release path must stay inside releases/', {
|
|
73
|
+
path: entry.path
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
const bytes = readFileSync(releasePath);
|
|
77
|
+
const digest = createHash('sha256').update(bytes).digest('hex');
|
|
78
|
+
if (entry.content_digest_algorithm !== 'sha256' || digest !== entry.content_digest) {
|
|
79
|
+
throw new OntologyError('ONTOLOGY_DIGEST_MISMATCH', `Ontology release digest mismatch: ${entry.version}`, {
|
|
80
|
+
version: entry.version,
|
|
81
|
+
expected: entry.content_digest,
|
|
82
|
+
actual: digest
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
const manifest = parseJson(bytes, `ontology release ${entry.version}`);
|
|
86
|
+
if (manifest.version !== entry.version || manifest.effective_at !== entry.effective_at) {
|
|
87
|
+
throw new OntologyError('ONTOLOGY_MANIFEST_INVALID', 'Ontology index metadata does not match release manifest', {
|
|
88
|
+
index_version: entry.version,
|
|
89
|
+
manifest_version: manifest.version,
|
|
90
|
+
index_effective_at: entry.effective_at,
|
|
91
|
+
manifest_effective_at: manifest.effective_at
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
const receiptTrust = verifyPublishedReceipt({
|
|
95
|
+
configDir: this.configDir,
|
|
96
|
+
entry,
|
|
97
|
+
manifest,
|
|
98
|
+
publicKeyPem: this.publicKeyPem,
|
|
99
|
+
trustedPublicKeys: this.trustedPublicKeys
|
|
100
|
+
});
|
|
101
|
+
const claimsPublication = this.index.current === entry.version
|
|
102
|
+
|| entry.status === 'retired'
|
|
103
|
+
|| hasReceiptMetadata(entry);
|
|
104
|
+
if (claimsPublication && !receiptTrust.verified) {
|
|
105
|
+
throw new OntologyError('ONTOLOGY_PUBLICATION_UNVERIFIED', `Ontology publication receipt is not trusted: ${entry.version}`, {
|
|
106
|
+
http_status: 503,
|
|
107
|
+
version: entry.version,
|
|
108
|
+
reason: receiptTrust.reason,
|
|
109
|
+
...receiptTrust.details
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
const effectiveStatus = receiptTrust.verified && this.index.current === entry.version
|
|
113
|
+
? 'active'
|
|
114
|
+
: receiptTrust.verified && entry.status === 'retired'
|
|
115
|
+
? 'retired'
|
|
116
|
+
: receiptTrust.verified
|
|
117
|
+
? 'approved'
|
|
118
|
+
: 'proposed';
|
|
119
|
+
return {
|
|
120
|
+
kernel: new OntologyKernel({ manifest, status: effectiveStatus }),
|
|
121
|
+
digest,
|
|
122
|
+
entry: structuredClone(entry),
|
|
123
|
+
publicationVerification: receiptTrust.verified
|
|
124
|
+
? {
|
|
125
|
+
status: 'verified',
|
|
126
|
+
key_id: receiptTrust.receipt.key_id,
|
|
127
|
+
signature_algorithm: receiptTrust.receipt.signature_algorithm,
|
|
128
|
+
trust_source: this.publicKeyPem ? 'environment_override' : 'git_trust_store',
|
|
129
|
+
receipt_digest: receiptTrust.receipt_digest
|
|
130
|
+
}
|
|
131
|
+
: { status: 'unverified', reason: receiptTrust.reason }
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
interpretHistory(snapshot = {}, { version, asOf } = {}) {
|
|
135
|
+
const recordedVersion = snapshot.ontology_version || null;
|
|
136
|
+
if (version && recordedVersion && version !== recordedVersion) {
|
|
137
|
+
throw new OntologyError('ONTOLOGY_HISTORY_VERSION_MISMATCH', 'Requested ontology version does not match the recorded fact version', {
|
|
138
|
+
requested_version: version,
|
|
139
|
+
recorded_version: recordedVersion
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
let release;
|
|
143
|
+
try {
|
|
144
|
+
if (version || recordedVersion) {
|
|
145
|
+
release = this.resolve({ version: version || recordedVersion });
|
|
146
|
+
}
|
|
147
|
+
else if (asOf) {
|
|
148
|
+
release = this.resolve({ asOf });
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
return this.unverifiedHistory(snapshot, asOf, {
|
|
152
|
+
code: 'ONTOLOGY_HISTORY_VERSION_UNRESOLVED',
|
|
153
|
+
message: 'Historical interpretation requires a recorded ontology version or resolvable as-of time'
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
if (!version && !recordedVersion && error instanceof OntologyError
|
|
159
|
+
&& ['ONTOLOGY_VERSION_UNKNOWN', 'ONTOLOGY_CURRENT_UNAVAILABLE', 'ONTOLOGY_PUBLICATION_UNVERIFIED'].includes(error.code)) {
|
|
160
|
+
return this.unverifiedHistory(snapshot, asOf, {
|
|
161
|
+
code: error.code,
|
|
162
|
+
message: error.message
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
throw error;
|
|
166
|
+
}
|
|
167
|
+
if (release.kernel.status === 'proposed') {
|
|
168
|
+
return this.unverifiedHistory(snapshot, asOf, {
|
|
169
|
+
code: 'ONTOLOGY_PUBLICATION_UNVERIFIED',
|
|
170
|
+
message: `Ontology release has not been published: ${release.entry.version}`
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
const resolvedVersion = release.entry.version;
|
|
174
|
+
const mismatchedEvents = (snapshot.evolution_events || [])
|
|
175
|
+
.filter((event) => event.ontology_version && event.ontology_version !== resolvedVersion)
|
|
176
|
+
.map((event) => event.event_id);
|
|
177
|
+
if (mismatchedEvents.length) {
|
|
178
|
+
throw new OntologyError('ONTOLOGY_HISTORY_VERSION_MISMATCH', 'Evolution events must use the recorded fact ontology version', {
|
|
179
|
+
recorded_version: recordedVersion,
|
|
180
|
+
resolved_version: resolvedVersion,
|
|
181
|
+
event_ids: mismatchedEvents
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
...release.kernel.interpretHistory(snapshot, { asOf }),
|
|
186
|
+
recorded_ontology_version: recordedVersion,
|
|
187
|
+
resolved_ontology_version: resolvedVersion,
|
|
188
|
+
verification: 'verified'
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
unverifiedHistory(snapshot, asOf, reason) {
|
|
192
|
+
return {
|
|
193
|
+
...structuredClone(snapshot),
|
|
194
|
+
ontology_version: null,
|
|
195
|
+
recorded_ontology_version: snapshot.ontology_version || null,
|
|
196
|
+
resolved_ontology_version: null,
|
|
197
|
+
interpretation_as_of: asOf || null,
|
|
198
|
+
verification: 'unverified',
|
|
199
|
+
unverified_reason: reason
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function hasReceiptMetadata(entry?: any): boolean;
|
|
2
|
+
export declare function hasCompleteReceiptMetadata(entry?: any): boolean;
|
|
3
|
+
export declare function loadTrustedPublicKeys(configDir: any): any;
|
|
4
|
+
export declare function verifyPublishedReceipt({ configDir, entry, manifest, publicKeyPem, trustedPublicKeys }: any): any;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// Moved from brainbase-unson server/services/ontology-release-trust.js without changing behavior.
|
|
2
|
+
// Only type annotations were added; values stay open JSON objects as in the original.
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
4
|
+
import { existsSync, readFileSync, realpathSync, statSync } from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { canonicalJson, publicationReceiptContractErrors, sha256, verifyPublicationReceipt } from './ontology-publication.js';
|
|
7
|
+
const SHA256_DIGEST = /^[a-f0-9]{64}$/;
|
|
8
|
+
const RECEIPT_FIELDS = ['receipt_path', 'receipt_digest_algorithm', 'receipt_digest'];
|
|
9
|
+
export function hasReceiptMetadata(entry = {}) {
|
|
10
|
+
return RECEIPT_FIELDS.some((field) => Object.prototype.hasOwnProperty.call(entry, field));
|
|
11
|
+
}
|
|
12
|
+
export function hasCompleteReceiptMetadata(entry = {}) {
|
|
13
|
+
return typeof entry.receipt_path === 'string'
|
|
14
|
+
&& entry.receipt_path.trim().length > 0
|
|
15
|
+
&& entry.receipt_digest_algorithm === 'sha256'
|
|
16
|
+
&& typeof entry.receipt_digest === 'string'
|
|
17
|
+
&& SHA256_DIGEST.test(entry.receipt_digest);
|
|
18
|
+
}
|
|
19
|
+
function invalid(reason, details = {}) {
|
|
20
|
+
return { verified: false, reason, details };
|
|
21
|
+
}
|
|
22
|
+
function isContained(parent, child) {
|
|
23
|
+
const relative = path.relative(parent, child);
|
|
24
|
+
return relative !== '' && !relative.startsWith('..') && !path.isAbsolute(relative);
|
|
25
|
+
}
|
|
26
|
+
export function loadTrustedPublicKeys(configDir) {
|
|
27
|
+
const trustStorePath = path.join(configDir, 'trusted-public-keys.json');
|
|
28
|
+
if (!existsSync(trustStorePath))
|
|
29
|
+
return {};
|
|
30
|
+
const trustStore = JSON.parse(readFileSync(trustStorePath, 'utf8'));
|
|
31
|
+
if (trustStore.schema_version !== '1.0.0'
|
|
32
|
+
|| !trustStore.keys
|
|
33
|
+
|| Array.isArray(trustStore.keys)
|
|
34
|
+
|| typeof trustStore.keys !== 'object') {
|
|
35
|
+
throw new Error('Ontology trusted public keys must use the 1.0.0 key map schema');
|
|
36
|
+
}
|
|
37
|
+
for (const [keyId, key] of Object.entries(trustStore.keys)) {
|
|
38
|
+
if (!keyId
|
|
39
|
+
|| key?.algorithm !== 'ed25519'
|
|
40
|
+
|| typeof key.public_key_pem !== 'string'
|
|
41
|
+
|| !key.public_key_pem.includes('-----BEGIN PUBLIC KEY-----')) {
|
|
42
|
+
throw new Error(`Invalid ontology trusted public key: ${keyId || '<empty>'}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return trustStore.keys;
|
|
46
|
+
}
|
|
47
|
+
export function verifyPublishedReceipt({ configDir, entry = {}, manifest, publicKeyPem = '', trustedPublicKeys = {} }) {
|
|
48
|
+
if (!hasCompleteReceiptMetadata(entry)) {
|
|
49
|
+
return invalid(hasReceiptMetadata(entry) ? 'incomplete_metadata' : 'missing_metadata');
|
|
50
|
+
}
|
|
51
|
+
const publicationsDir = path.resolve(configDir, 'publications');
|
|
52
|
+
const receiptPath = path.resolve(configDir, entry.receipt_path);
|
|
53
|
+
if (!isContained(publicationsDir, receiptPath)) {
|
|
54
|
+
return invalid('path_escape');
|
|
55
|
+
}
|
|
56
|
+
let realPublicationsDir;
|
|
57
|
+
let realReceiptPath;
|
|
58
|
+
let receiptBytes;
|
|
59
|
+
try {
|
|
60
|
+
realPublicationsDir = realpathSync(publicationsDir);
|
|
61
|
+
realReceiptPath = realpathSync(receiptPath);
|
|
62
|
+
if (!isContained(realPublicationsDir, realReceiptPath) || !statSync(realReceiptPath).isFile()) {
|
|
63
|
+
return invalid('path_escape_or_not_file');
|
|
64
|
+
}
|
|
65
|
+
receiptBytes = readFileSync(realReceiptPath);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return invalid('receipt_unavailable');
|
|
69
|
+
}
|
|
70
|
+
const actualDigest = sha256(receiptBytes);
|
|
71
|
+
if (actualDigest !== entry.receipt_digest) {
|
|
72
|
+
return invalid('digest_mismatch', { expected: entry.receipt_digest, actual: actualDigest });
|
|
73
|
+
}
|
|
74
|
+
let receipt;
|
|
75
|
+
try {
|
|
76
|
+
receipt = JSON.parse(receiptBytes.toString('utf8'));
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return invalid('invalid_json');
|
|
80
|
+
}
|
|
81
|
+
const contractErrors = publicationReceiptContractErrors(receipt);
|
|
82
|
+
if (contractErrors.length) {
|
|
83
|
+
return invalid('contract_mismatch', { fields: contractErrors });
|
|
84
|
+
}
|
|
85
|
+
const trustedPublicKeyPem = publicKeyPem || trustedPublicKeys[receipt.key_id]?.public_key_pem || '';
|
|
86
|
+
if (!trustedPublicKeyPem) {
|
|
87
|
+
return invalid('public_key_unavailable');
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
if (!verifyPublicationReceipt(receipt, trustedPublicKeyPem)) {
|
|
91
|
+
return invalid('signature_invalid');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return invalid('signature_invalid');
|
|
96
|
+
}
|
|
97
|
+
const governance = manifest?.governance || {};
|
|
98
|
+
const expectedBindings = {
|
|
99
|
+
release_version: entry.version,
|
|
100
|
+
release_digest: entry.content_digest,
|
|
101
|
+
source_commit_sha: entry.source_commit_sha,
|
|
102
|
+
decision_id: governance.decision_id,
|
|
103
|
+
scope_entity_id: governance.scope_entity_id,
|
|
104
|
+
proposer_entity_id: governance.proposer_entity_id,
|
|
105
|
+
decider_entity_id: governance.decider_entity_id,
|
|
106
|
+
applier_entity_id: governance.applier_entity_id,
|
|
107
|
+
actor_entity_id: governance.applier_entity_id
|
|
108
|
+
};
|
|
109
|
+
const mismatches = Object.entries(expectedBindings)
|
|
110
|
+
.filter(([key, value]) => !value || receipt.payload?.[key] !== value)
|
|
111
|
+
.map(([key]) => key);
|
|
112
|
+
const manifestImpactScope = canonicalJson(manifest?.impact_scope);
|
|
113
|
+
if (canonicalJson(receipt.payload?.impact_scope) !== manifestImpactScope
|
|
114
|
+
|| (entry.impact_scope && canonicalJson(entry.impact_scope) !== manifestImpactScope)) {
|
|
115
|
+
mismatches.push('impact_scope');
|
|
116
|
+
}
|
|
117
|
+
if (mismatches.length) {
|
|
118
|
+
return invalid('binding_mismatch', { fields: mismatches });
|
|
119
|
+
}
|
|
120
|
+
return { verified: true, receipt, receipt_path: realReceiptPath, receipt_digest: actualDigest };
|
|
121
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unson/brainbase-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.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",
|
|
@@ -187,6 +187,30 @@
|
|
|
187
187
|
"types": "./dist/judgment-resolution-contract.d.ts",
|
|
188
188
|
"import": "./dist/judgment-resolution-contract.js"
|
|
189
189
|
},
|
|
190
|
+
"./graph-maintenance-engine": {
|
|
191
|
+
"types": "./dist/graph-maintenance-engine.d.ts",
|
|
192
|
+
"import": "./dist/graph-maintenance-engine.js"
|
|
193
|
+
},
|
|
194
|
+
"./memory-scope-policy": {
|
|
195
|
+
"types": "./dist/memory-scope-policy.d.ts",
|
|
196
|
+
"import": "./dist/memory-scope-policy.js"
|
|
197
|
+
},
|
|
198
|
+
"./ontology-kernel": {
|
|
199
|
+
"types": "./dist/ontology-kernel.d.ts",
|
|
200
|
+
"import": "./dist/ontology-kernel.js"
|
|
201
|
+
},
|
|
202
|
+
"./ontology-registry": {
|
|
203
|
+
"types": "./dist/ontology-registry.d.ts",
|
|
204
|
+
"import": "./dist/ontology-registry.js"
|
|
205
|
+
},
|
|
206
|
+
"./ontology-publication": {
|
|
207
|
+
"types": "./dist/ontology-publication.d.ts",
|
|
208
|
+
"import": "./dist/ontology-publication.js"
|
|
209
|
+
},
|
|
210
|
+
"./ontology-release-trust": {
|
|
211
|
+
"types": "./dist/ontology-release-trust.d.ts",
|
|
212
|
+
"import": "./dist/ontology-release-trust.js"
|
|
213
|
+
},
|
|
190
214
|
"./canonical-document-writer": {
|
|
191
215
|
"types": "./dist/canonical-document-writer.d.ts",
|
|
192
216
|
"import": "./dist/canonical-document-writer.js"
|
|
@@ -455,5 +479,5 @@
|
|
|
455
479
|
"engines": {
|
|
456
480
|
"node": ">=20"
|
|
457
481
|
},
|
|
458
|
-
"gitHead": "
|
|
482
|
+
"gitHead": "9275d1ad54f053c85229a44b1c4cba811969cd29"
|
|
459
483
|
}
|
package/ui/world/world-view.js
CHANGED
|
@@ -1514,6 +1514,27 @@ function memoryLines(answer) {
|
|
|
1514
1514
|
return lines;
|
|
1515
1515
|
}
|
|
1516
1516
|
|
|
1517
|
+
/** At most this many waiting judgments are listed in the world; the rest are counted, never hidden. */
|
|
1518
|
+
const WAITING_LIST_LIMIT = 3;
|
|
1519
|
+
|
|
1520
|
+
/**
|
|
1521
|
+
* The judgments waiting for the owner now, newest first, each a way down to the same judgment in
|
|
1522
|
+
* 判断の履歴 (ledger P17 / U2). The world stays an overview: only the exceptions become a short list.
|
|
1523
|
+
*/
|
|
1524
|
+
function waitingLines(proofs, judgmentHref) {
|
|
1525
|
+
const waiting = [...proofs.values()]
|
|
1526
|
+
.filter(isWaiting)
|
|
1527
|
+
.sort((a, b) => String(b.proof?.recorded_at ?? '').localeCompare(String(a.proof?.recorded_at ?? '')));
|
|
1528
|
+
const lines = waiting.slice(0, WAITING_LIST_LIMIT).map((item) => {
|
|
1529
|
+
const id = item.proof.decision_attempt_id;
|
|
1530
|
+
const question = item.proof.interruption?.question_display_text ?? item.proof.decision?.summary ?? id;
|
|
1531
|
+
return { text: question, summary: question, link: { href: judgmentHref(id), label: '「判断の履歴」で開く', className: 'bb-world-waiting-link' } };
|
|
1532
|
+
});
|
|
1533
|
+
const rest = waiting.length - lines.length;
|
|
1534
|
+
if (rest > 0) lines.push({ text: `ほか${rest}件は「判断の履歴」で見られます。`, summary: `ほか${rest}件の待っている判断`, link: null });
|
|
1535
|
+
return lines;
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1517
1538
|
/** 判断の履歴 in the local web (it was 今日, `#today`, until 2026-10-11; the shell still opens old links). */
|
|
1518
1539
|
function defaultJudgmentHref(decisionAttemptId) {
|
|
1519
1540
|
return decisionAttemptId ? `#value-proofs?decision=${encodeURIComponent(decisionAttemptId)}` : '#value-proofs';
|
|
@@ -2439,6 +2460,9 @@ export function createWorldView({ root, rail, page, presentation = 'standard', d
|
|
|
2439
2460
|
note('判断', `${home.delegation_map.judged}件。作業した場所を読めないため、事業には置かず広場の種類別だけで描いています(${placesResult.ok ? placesResult.data?.reason ?? '理由不明' : placesResult.error})。今あなたを待っている判断${waiting}件。`, 'warning', '判断の作業場所:読めない');
|
|
2440
2461
|
}
|
|
2441
2462
|
}
|
|
2463
|
+
if (judgmentsConnected) {
|
|
2464
|
+
for (const line of waitingLines(proofs, judgmentHref)) note('待っている判断', line.text, 'attention', line.summary, line.link);
|
|
2465
|
+
}
|
|
2442
2466
|
const rowItems = new Map(rows.map((row) => [row.key, Array.isArray(row.items) ? row.items : []]));
|
|
2443
2467
|
const memory = await memoryRead;
|
|
2444
2468
|
if (disposed) return;
|