@unson/brainbase-mcp 0.10.12 → 0.10.14
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/judgment-frame.d.ts +101 -0
- package/dist/judgment-frame.js +251 -0
- package/dist/knowledge-lookup.js +57 -46
- package/package.json +6 -2
- package/ui/value-proof-review.js +7 -2
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Judgment frame (brainbase-project ADR-014).
|
|
3
|
+
*
|
|
4
|
+
* A judgment-bearing turn selects the philosophy, objectives, and world models
|
|
5
|
+
* that bear on the decision from a compact catalog, and records how each
|
|
6
|
+
* selected reference was used for each option. The catalog is small enough to
|
|
7
|
+
* show in full; the record is checked structurally, never for semantic quality.
|
|
8
|
+
*
|
|
9
|
+
* Status is a label, not a gate: draft objectives and unverified models stay
|
|
10
|
+
* usable. Only superseded or inactive records are excluded from the catalog.
|
|
11
|
+
*/
|
|
12
|
+
export declare const JUDGMENT_FRAME_CATALOG_VERSION: "judgment-frame-catalog.v1";
|
|
13
|
+
export declare const JUDGMENT_FRAME_RECORD_VERSION: "judgment-frame-record.v1";
|
|
14
|
+
export type JudgmentFrameKind = 'philosophy' | 'objective' | 'model';
|
|
15
|
+
export type JudgmentFrameStatus = 'active' | 'draft' | 'adopted_unverified' | 'verified';
|
|
16
|
+
export type JudgmentFrameRole = 'constraint' | 'criterion' | 'prediction';
|
|
17
|
+
export type JudgmentFrameConstraintVerdict = 'satisfied' | 'violated' | 'tension';
|
|
18
|
+
/** Each kind is used in exactly one way inside an option evaluation. */
|
|
19
|
+
export declare const JUDGMENT_FRAME_ROLE_BY_KIND: Readonly<Record<JudgmentFrameKind, JudgmentFrameRole>>;
|
|
20
|
+
export interface JudgmentFrameSourceRecord {
|
|
21
|
+
readonly id: string;
|
|
22
|
+
readonly entity_type: string;
|
|
23
|
+
readonly version?: number | string;
|
|
24
|
+
readonly lifecycle_status?: string;
|
|
25
|
+
readonly payload?: Record<string, unknown>;
|
|
26
|
+
}
|
|
27
|
+
export interface JudgmentFrameCatalogItem {
|
|
28
|
+
readonly id: string;
|
|
29
|
+
readonly kind: JudgmentFrameKind;
|
|
30
|
+
readonly revision: string;
|
|
31
|
+
readonly status: JudgmentFrameStatus;
|
|
32
|
+
readonly text: string;
|
|
33
|
+
readonly applies_when?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface JudgmentFrameCatalog {
|
|
36
|
+
readonly catalog_version: typeof JUDGMENT_FRAME_CATALOG_VERSION;
|
|
37
|
+
readonly digest: `sha256:${string}`;
|
|
38
|
+
readonly items: readonly JudgmentFrameCatalogItem[];
|
|
39
|
+
}
|
|
40
|
+
export interface JudgmentFrameExclusion {
|
|
41
|
+
readonly id: string;
|
|
42
|
+
readonly reason: 'inactive' | 'superseded' | 'restates_source_philosophy' | 'missing_text' | 'unsupported_type';
|
|
43
|
+
}
|
|
44
|
+
export interface JudgmentFrameCatalogBuild {
|
|
45
|
+
readonly catalog: JudgmentFrameCatalog;
|
|
46
|
+
readonly excluded: readonly JudgmentFrameExclusion[];
|
|
47
|
+
}
|
|
48
|
+
export interface JudgmentFrameSelection {
|
|
49
|
+
readonly ref: string;
|
|
50
|
+
readonly kind: JudgmentFrameKind;
|
|
51
|
+
readonly why: string;
|
|
52
|
+
}
|
|
53
|
+
export interface JudgmentFrameUse {
|
|
54
|
+
readonly ref: string;
|
|
55
|
+
readonly role: JudgmentFrameRole;
|
|
56
|
+
readonly note: string;
|
|
57
|
+
/** Required for constraint uses: how the option stands against the philosophy. */
|
|
58
|
+
readonly verdict?: JudgmentFrameConstraintVerdict;
|
|
59
|
+
}
|
|
60
|
+
export interface JudgmentFrameOption {
|
|
61
|
+
readonly label: string;
|
|
62
|
+
readonly uses: readonly JudgmentFrameUse[];
|
|
63
|
+
}
|
|
64
|
+
export interface JudgmentFrameRecord {
|
|
65
|
+
readonly record_version: typeof JUDGMENT_FRAME_RECORD_VERSION;
|
|
66
|
+
readonly catalog_digest: string;
|
|
67
|
+
readonly selections: readonly JudgmentFrameSelection[];
|
|
68
|
+
/** Why no reference of a kind applies. Required for each kind without a selection. */
|
|
69
|
+
readonly none?: Partial<Record<JudgmentFrameKind, string>>;
|
|
70
|
+
readonly options: readonly JudgmentFrameOption[];
|
|
71
|
+
readonly chosen_option: string;
|
|
72
|
+
}
|
|
73
|
+
export type JudgmentFrameIssueCode = 'frame_record_invalid' | 'frame_catalog_mismatch' | 'frame_ref_unknown' | 'frame_kind_mismatch' | 'frame_selection_duplicate' | 'frame_kind_unaddressed' | 'frame_kind_contradiction' | 'frame_use_unselected' | 'frame_role_mismatch' | 'frame_verdict_missing' | 'frame_selection_unused' | 'frame_chosen_unknown';
|
|
74
|
+
export interface JudgmentFrameIssue {
|
|
75
|
+
readonly code: JudgmentFrameIssueCode;
|
|
76
|
+
readonly path: string;
|
|
77
|
+
readonly message: string;
|
|
78
|
+
}
|
|
79
|
+
/** Exceptions that ADR-014 F5 returns to a human. The host decides when they block. */
|
|
80
|
+
export type JudgmentFrameEscalationCode = 'objective_unanchored' | 'chosen_violates_philosophy' | 'chosen_philosophy_tension';
|
|
81
|
+
export interface JudgmentFrameEscalation {
|
|
82
|
+
readonly code: JudgmentFrameEscalationCode;
|
|
83
|
+
readonly refs: readonly string[];
|
|
84
|
+
}
|
|
85
|
+
export interface JudgmentFrameValidation {
|
|
86
|
+
readonly valid: boolean;
|
|
87
|
+
readonly issues: readonly JudgmentFrameIssue[];
|
|
88
|
+
readonly escalations: readonly JudgmentFrameEscalation[];
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Build the compact catalog shown to the model on a judgment-bearing turn.
|
|
92
|
+
* Excluded records are returned with a reason so a shrinking catalog is visible.
|
|
93
|
+
*/
|
|
94
|
+
export declare function buildJudgmentFrameCatalog(records: readonly JudgmentFrameSourceRecord[]): JudgmentFrameCatalogBuild;
|
|
95
|
+
/** One line per item, grouped by kind, for the model-facing prompt. */
|
|
96
|
+
export declare function renderJudgmentFrameCatalog(catalog: JudgmentFrameCatalog): string;
|
|
97
|
+
/**
|
|
98
|
+
* Check that a frame record only uses catalog references and that every
|
|
99
|
+
* selection is used in at least one option with the role of its kind.
|
|
100
|
+
*/
|
|
101
|
+
export declare function validateJudgmentFrameRecord(value: unknown, catalog: JudgmentFrameCatalog): JudgmentFrameValidation;
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Judgment frame (brainbase-project ADR-014).
|
|
4
|
+
*
|
|
5
|
+
* A judgment-bearing turn selects the philosophy, objectives, and world models
|
|
6
|
+
* that bear on the decision from a compact catalog, and records how each
|
|
7
|
+
* selected reference was used for each option. The catalog is small enough to
|
|
8
|
+
* show in full; the record is checked structurally, never for semantic quality.
|
|
9
|
+
*
|
|
10
|
+
* Status is a label, not a gate: draft objectives and unverified models stay
|
|
11
|
+
* usable. Only superseded or inactive records are excluded from the catalog.
|
|
12
|
+
*/
|
|
13
|
+
export const JUDGMENT_FRAME_CATALOG_VERSION = 'judgment-frame-catalog.v1';
|
|
14
|
+
export const JUDGMENT_FRAME_RECORD_VERSION = 'judgment-frame-record.v1';
|
|
15
|
+
/** Each kind is used in exactly one way inside an option evaluation. */
|
|
16
|
+
export const JUDGMENT_FRAME_ROLE_BY_KIND = Object.freeze({
|
|
17
|
+
philosophy: 'constraint',
|
|
18
|
+
objective: 'criterion',
|
|
19
|
+
model: 'prediction',
|
|
20
|
+
});
|
|
21
|
+
const KINDS = ['philosophy', 'objective', 'model'];
|
|
22
|
+
const MAX_TEXT = 400;
|
|
23
|
+
const MAX_APPLIES_WHEN = 160;
|
|
24
|
+
const MAX_NOTE = 2_000;
|
|
25
|
+
const MAX_SELECTIONS = 24;
|
|
26
|
+
const MAX_OPTIONS = 8;
|
|
27
|
+
const MAX_USES = 32;
|
|
28
|
+
const isRecord = (value) => Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
29
|
+
const text = (value) => typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
30
|
+
const nonEmpty = (value, max) => typeof value === 'string' && value.trim().length > 0 && value.length <= max;
|
|
31
|
+
function clip(value, max) {
|
|
32
|
+
const chars = [...value];
|
|
33
|
+
return chars.length <= max ? value : `${chars.slice(0, max - 1).join('')}…`;
|
|
34
|
+
}
|
|
35
|
+
function canonicalJson(value) {
|
|
36
|
+
if (Array.isArray(value))
|
|
37
|
+
return `[${value.map(canonicalJson).join(',')}]`;
|
|
38
|
+
if (isRecord(value)) {
|
|
39
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(',')}}`;
|
|
40
|
+
}
|
|
41
|
+
return JSON.stringify(value);
|
|
42
|
+
}
|
|
43
|
+
function itemText(kind, payload) {
|
|
44
|
+
const foundation = isRecord(payload.foundation) ? payload.foundation : {};
|
|
45
|
+
if (kind === 'philosophy') {
|
|
46
|
+
const statement = text(payload.statement) ?? text(foundation.meaning);
|
|
47
|
+
const name = text(payload.display_name) ?? text(payload.title);
|
|
48
|
+
if (!statement)
|
|
49
|
+
return undefined;
|
|
50
|
+
return name && name !== statement ? `${name}:${statement}` : statement;
|
|
51
|
+
}
|
|
52
|
+
if (kind === 'objective')
|
|
53
|
+
return text(foundation.desiredState) ?? text(foundation.meaning) ?? text(payload.statement);
|
|
54
|
+
return text(foundation.meaning) ?? text(payload.statement) ?? text(payload.title);
|
|
55
|
+
}
|
|
56
|
+
function itemStatus(kind, payload) {
|
|
57
|
+
const foundation = isRecord(payload.foundation) ? payload.foundation : {};
|
|
58
|
+
if (kind === 'model') {
|
|
59
|
+
if (foundation.validationState === 'verified')
|
|
60
|
+
return 'verified';
|
|
61
|
+
if (foundation.adoptionState === 'approved')
|
|
62
|
+
return 'adopted_unverified';
|
|
63
|
+
return 'draft';
|
|
64
|
+
}
|
|
65
|
+
const state = text(foundation.adoptionState) ?? text(payload.status);
|
|
66
|
+
return state === 'active' || state === 'approved' ? 'active' : 'draft';
|
|
67
|
+
}
|
|
68
|
+
function isSuperseded(payload) {
|
|
69
|
+
const foundation = isRecord(payload.foundation) ? payload.foundation : {};
|
|
70
|
+
return [payload.status, foundation.adoptionState, foundation.lifecycle]
|
|
71
|
+
.some((value) => typeof value === 'string' && /^(superseded|retired|rejected)/.test(value));
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Build the compact catalog shown to the model on a judgment-bearing turn.
|
|
75
|
+
* Excluded records are returned with a reason so a shrinking catalog is visible.
|
|
76
|
+
*/
|
|
77
|
+
export function buildJudgmentFrameCatalog(records) {
|
|
78
|
+
const philosophyIds = new Set(records.filter((record) => record.entity_type === 'philosophy').map((record) => record.id));
|
|
79
|
+
const items = [];
|
|
80
|
+
const excluded = [];
|
|
81
|
+
for (const record of records) {
|
|
82
|
+
const kind = KINDS.find((candidate) => candidate === record.entity_type);
|
|
83
|
+
if (!kind) {
|
|
84
|
+
excluded.push({ id: record.id, reason: 'unsupported_type' });
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const payload = isRecord(record.payload) ? record.payload : {};
|
|
88
|
+
if (record.lifecycle_status !== undefined && record.lifecycle_status !== 'active') {
|
|
89
|
+
excluded.push({ id: record.id, reason: 'inactive' });
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (isSuperseded(payload)) {
|
|
93
|
+
excluded.push({ id: record.id, reason: 'superseded' });
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const decomposition = isRecord(payload.decomposition) ? payload.decomposition : {};
|
|
97
|
+
const sourcePin = isRecord(decomposition.sourcePin) ? decomposition.sourcePin : {};
|
|
98
|
+
if (kind === 'philosophy' && typeof sourcePin.id === 'string' && sourcePin.id !== record.id && philosophyIds.has(sourcePin.id)) {
|
|
99
|
+
excluded.push({ id: record.id, reason: 'restates_source_philosophy' });
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const body = itemText(kind, payload);
|
|
103
|
+
if (!body) {
|
|
104
|
+
excluded.push({ id: record.id, reason: 'missing_text' });
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const foundation = isRecord(payload.foundation) ? payload.foundation : {};
|
|
108
|
+
const appliesWhen = kind === 'model' ? text(payload.applicability_text) : undefined;
|
|
109
|
+
items.push({
|
|
110
|
+
id: record.id,
|
|
111
|
+
kind,
|
|
112
|
+
revision: text(foundation.revision) ?? String(record.version ?? '1'),
|
|
113
|
+
status: itemStatus(kind, payload),
|
|
114
|
+
text: clip(body, MAX_TEXT),
|
|
115
|
+
...(appliesWhen ? { applies_when: clip(appliesWhen, MAX_APPLIES_WHEN) } : {}),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
items.sort((left, right) => KINDS.indexOf(left.kind) - KINDS.indexOf(right.kind) || left.id.localeCompare(right.id));
|
|
119
|
+
const digest = `sha256:${createHash('sha256').update(canonicalJson(items)).digest('hex')}`;
|
|
120
|
+
return { catalog: { catalog_version: JUDGMENT_FRAME_CATALOG_VERSION, digest, items }, excluded };
|
|
121
|
+
}
|
|
122
|
+
const KIND_HEADINGS = {
|
|
123
|
+
philosophy: '哲学(constraintとして使う。verdictを付ける)',
|
|
124
|
+
objective: '目的(criterionとして使う)',
|
|
125
|
+
model: '世界モデル(predictionとして使う。仮説であり、検証済みとは限らない)',
|
|
126
|
+
};
|
|
127
|
+
/** One line per item, grouped by kind, for the model-facing prompt. */
|
|
128
|
+
export function renderJudgmentFrameCatalog(catalog) {
|
|
129
|
+
const lines = [`judgment frame catalog ${catalog.digest}`];
|
|
130
|
+
for (const kind of KINDS) {
|
|
131
|
+
lines.push('', `## ${KIND_HEADINGS[kind]}`);
|
|
132
|
+
for (const item of catalog.items.filter((candidate) => candidate.kind === kind)) {
|
|
133
|
+
lines.push(`- ${item.id} [${item.status}] ${item.text}${item.applies_when ? `(適用:${item.applies_when})` : ''}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return lines.join('\n');
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Check that a frame record only uses catalog references and that every
|
|
140
|
+
* selection is used in at least one option with the role of its kind.
|
|
141
|
+
*/
|
|
142
|
+
export function validateJudgmentFrameRecord(value, catalog) {
|
|
143
|
+
const issues = [];
|
|
144
|
+
const issue = (code, path, message) => issues.push({ code, path, message });
|
|
145
|
+
if (!isRecord(value) || value.record_version !== JUDGMENT_FRAME_RECORD_VERSION) {
|
|
146
|
+
issue('frame_record_invalid', '$', `record_version must be ${JUDGMENT_FRAME_RECORD_VERSION}`);
|
|
147
|
+
return { valid: false, issues, escalations: [] };
|
|
148
|
+
}
|
|
149
|
+
if (value.catalog_digest !== catalog.digest) {
|
|
150
|
+
issue('frame_catalog_mismatch', 'catalog_digest', 'catalog_digest does not match the catalog shown on this turn');
|
|
151
|
+
}
|
|
152
|
+
const byId = new Map(catalog.items.map((item) => [item.id, item]));
|
|
153
|
+
const selections = Array.isArray(value.selections) ? value.selections : null;
|
|
154
|
+
if (!selections || selections.length > MAX_SELECTIONS) {
|
|
155
|
+
issue('frame_record_invalid', 'selections', `selections must be a list of at most ${MAX_SELECTIONS}`);
|
|
156
|
+
}
|
|
157
|
+
const selected = new Map();
|
|
158
|
+
(selections ?? []).forEach((selection, index) => {
|
|
159
|
+
const path = `selections[${index}]`;
|
|
160
|
+
if (!isRecord(selection) || !nonEmpty(selection.ref, 1_000) || !nonEmpty(selection.why, MAX_NOTE)
|
|
161
|
+
|| !KINDS.includes(selection.kind)) {
|
|
162
|
+
issue('frame_record_invalid', path, 'selection needs ref, kind, and why');
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const item = byId.get(selection.ref);
|
|
166
|
+
if (!item) {
|
|
167
|
+
issue('frame_ref_unknown', `${path}.ref`, `${selection.ref} is not in the catalog`);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (item.kind !== selection.kind) {
|
|
171
|
+
issue('frame_kind_mismatch', `${path}.kind`, `${selection.ref} is a ${item.kind}, not a ${String(selection.kind)}`);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (selected.has(selection.ref)) {
|
|
175
|
+
issue('frame_selection_duplicate', `${path}.ref`, `${selection.ref} is selected twice`);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
selected.set(selection.ref, item.kind);
|
|
179
|
+
});
|
|
180
|
+
const none = isRecord(value.none) ? value.none : {};
|
|
181
|
+
for (const kind of KINDS) {
|
|
182
|
+
const hasSelection = [...selected.values()].includes(kind);
|
|
183
|
+
const reason = none[kind];
|
|
184
|
+
if (hasSelection && reason !== undefined) {
|
|
185
|
+
issue('frame_kind_contradiction', `none.${kind}`, `${kind} has both a selection and a none reason`);
|
|
186
|
+
}
|
|
187
|
+
else if (!hasSelection && !nonEmpty(reason, MAX_NOTE)) {
|
|
188
|
+
issue('frame_kind_unaddressed', `none.${kind}`, `select a ${kind} or give the reason none applies`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const options = Array.isArray(value.options) ? value.options : null;
|
|
192
|
+
if (!options || options.length === 0 || options.length > MAX_OPTIONS) {
|
|
193
|
+
issue('frame_record_invalid', 'options', `options must list 1 to ${MAX_OPTIONS} evaluated options`);
|
|
194
|
+
}
|
|
195
|
+
const used = new Set();
|
|
196
|
+
const labels = new Set();
|
|
197
|
+
(options ?? []).forEach((option, optionIndex) => {
|
|
198
|
+
const path = `options[${optionIndex}]`;
|
|
199
|
+
if (!isRecord(option) || !nonEmpty(option.label, 400) || !Array.isArray(option.uses) || option.uses.length > MAX_USES) {
|
|
200
|
+
issue('frame_record_invalid', path, `option needs a label and at most ${MAX_USES} uses`);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
labels.add(option.label);
|
|
204
|
+
option.uses.forEach((use, useIndex) => {
|
|
205
|
+
const usePath = `${path}.uses[${useIndex}]`;
|
|
206
|
+
if (!isRecord(use) || !nonEmpty(use.ref, 1_000) || !nonEmpty(use.note, MAX_NOTE)) {
|
|
207
|
+
issue('frame_record_invalid', usePath, 'use needs ref, role, and note');
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const kind = selected.get(use.ref);
|
|
211
|
+
if (!kind) {
|
|
212
|
+
issue('frame_use_unselected', `${usePath}.ref`, `${use.ref} is used but not selected`);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (use.role !== JUDGMENT_FRAME_ROLE_BY_KIND[kind]) {
|
|
216
|
+
issue('frame_role_mismatch', `${usePath}.role`, `a ${kind} is used as ${JUDGMENT_FRAME_ROLE_BY_KIND[kind]}`);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (use.role === 'constraint' && !['satisfied', 'violated', 'tension'].includes(use.verdict)) {
|
|
220
|
+
issue('frame_verdict_missing', `${usePath}.verdict`, 'a constraint use needs verdict satisfied, violated, or tension');
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
used.add(use.ref);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
for (const ref of selected.keys()) {
|
|
227
|
+
if (!used.has(ref))
|
|
228
|
+
issue('frame_selection_unused', 'selections', `${ref} is selected but not used for any option`);
|
|
229
|
+
}
|
|
230
|
+
const chosen = (options ?? []).find((option) => isRecord(option) && option.label === value.chosen_option);
|
|
231
|
+
if (!nonEmpty(value.chosen_option, 400) || !labels.has(value.chosen_option)) {
|
|
232
|
+
issue('frame_chosen_unknown', 'chosen_option', 'chosen_option must name one of the option labels');
|
|
233
|
+
}
|
|
234
|
+
const escalations = [];
|
|
235
|
+
if (![...selected.values()].includes('objective') && nonEmpty(none.objective, MAX_NOTE)) {
|
|
236
|
+
escalations.push({ code: 'objective_unanchored', refs: [] });
|
|
237
|
+
}
|
|
238
|
+
const chosenUses = isRecord(chosen) && Array.isArray(chosen.uses) ? chosen.uses : [];
|
|
239
|
+
if (chosenUses.length > 0) {
|
|
240
|
+
const verdictRefs = (verdict) => chosenUses
|
|
241
|
+
.filter((use) => isRecord(use) && use.role === 'constraint' && use.verdict === verdict)
|
|
242
|
+
.map((use) => String(use.ref));
|
|
243
|
+
const violated = verdictRefs('violated');
|
|
244
|
+
const tension = verdictRefs('tension');
|
|
245
|
+
if (violated.length > 0)
|
|
246
|
+
escalations.push({ code: 'chosen_violates_philosophy', refs: violated });
|
|
247
|
+
if (tension.length > 0)
|
|
248
|
+
escalations.push({ code: 'chosen_philosophy_tension', refs: tension });
|
|
249
|
+
}
|
|
250
|
+
return { valid: issues.length === 0, issues, escalations };
|
|
251
|
+
}
|
package/dist/knowledge-lookup.js
CHANGED
|
@@ -640,6 +640,25 @@ function unsupported(input, action, error) {
|
|
|
640
640
|
error: { code: error.code, message: error.message },
|
|
641
641
|
};
|
|
642
642
|
}
|
|
643
|
+
function lookupActionVariant(kind, properties, required) {
|
|
644
|
+
return {
|
|
645
|
+
type: 'object',
|
|
646
|
+
properties: {
|
|
647
|
+
kind: { const: kind },
|
|
648
|
+
...properties,
|
|
649
|
+
required_fields: {
|
|
650
|
+
type: 'array',
|
|
651
|
+
minItems: 1,
|
|
652
|
+
maxItems: MAX_FIELDS,
|
|
653
|
+
description: 'Must contain the same public field paths as the top-level required_fields.',
|
|
654
|
+
items: { type: 'string', minLength: 1, maxLength: MAX_FIELD_LENGTH },
|
|
655
|
+
},
|
|
656
|
+
why_different: { type: 'string', minLength: 1, maxLength: MAX_HINT_LENGTH },
|
|
657
|
+
},
|
|
658
|
+
required: ['kind', ...required],
|
|
659
|
+
additionalProperties: false,
|
|
660
|
+
};
|
|
661
|
+
}
|
|
643
662
|
export const knowledgeLookupTools = [{
|
|
644
663
|
name: 'brainbase_knowledge_lookup',
|
|
645
664
|
description: 'Find and read authorized Graph knowledge by purpose. Each call performs one bounded search, read, relation follow, or finish proposal; project hints never change authorization scope.',
|
|
@@ -664,57 +683,49 @@ export const knowledgeLookupTools = [{
|
|
|
664
683
|
missing_information: { type: 'array', maxItems: MAX_FIELDS, items: { type: 'string', minLength: 1, maxLength: MAX_HINT_LENGTH } },
|
|
665
684
|
based_on_attempt_ids: { type: 'array', maxItems: MAX_IDS, items: { type: 'string', minLength: 1, maxLength: 300 } },
|
|
666
685
|
why_different: { type: 'string', minLength: 1, maxLength: MAX_HINT_LENGTH },
|
|
686
|
+
// Each variant lists its own properties. Codex renders only the oneOf
|
|
687
|
+
// variants into the model-facing declaration, so properties declared
|
|
688
|
+
// beside oneOf are invisible and read/finish calls cannot be written.
|
|
667
689
|
next_action: {
|
|
668
690
|
type: 'object',
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
type: '
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
691
|
+
oneOf: [
|
|
692
|
+
lookupActionVariant('search', {
|
|
693
|
+
query: { type: 'string', minLength: 1, maxLength: MAX_TEXT_LENGTH },
|
|
694
|
+
entity_types: { type: 'array', maxItems: 10, items: { type: 'string', minLength: 1, maxLength: 100 } },
|
|
695
|
+
seed_ids: { type: 'array', minItems: 1, maxItems: MAX_IDS, items: { type: 'string', minLength: 1, maxLength: 1_000 } },
|
|
696
|
+
}, []),
|
|
697
|
+
lookupActionVariant('read', {
|
|
698
|
+
entity_id: { type: 'string', minLength: 1, maxLength: 1_000 },
|
|
699
|
+
entity_type: { type: 'string', minLength: 1, maxLength: 100 },
|
|
700
|
+
}, ['entity_id', 'entity_type']),
|
|
701
|
+
lookupActionVariant('follow_relation', {
|
|
702
|
+
seed_ids: { type: 'array', minItems: 1, maxItems: MAX_IDS, items: { type: 'string', minLength: 1, maxLength: 1_000 } },
|
|
703
|
+
relation: { type: 'string', minLength: 1, maxLength: MAX_RELATION_LENGTH },
|
|
704
|
+
direction: { type: 'string', enum: ['incoming', 'outgoing'] },
|
|
705
|
+
target_types: { type: 'array', maxItems: 10, items: { type: 'string', minLength: 1, maxLength: 100 } },
|
|
706
|
+
}, ['seed_ids', 'relation', 'direction']),
|
|
707
|
+
lookupActionVariant('finish', {
|
|
708
|
+
assessment: { type: 'string', enum: ['sufficient', 'insufficient'] },
|
|
709
|
+
status: { type: 'string', enum: ['satisfied', 'unresolved', 'needs_user_input'] },
|
|
710
|
+
reference_ids: { type: 'array', maxItems: MAX_IDS, items: { type: 'string', minLength: 1, maxLength: 1_000 } },
|
|
711
|
+
field_evidence: {
|
|
712
|
+
type: 'array',
|
|
713
|
+
maxItems: MAX_FIELDS,
|
|
714
|
+
items: {
|
|
715
|
+
type: 'object',
|
|
716
|
+
properties: {
|
|
717
|
+
field: { type: 'string', minLength: 1, maxLength: MAX_FIELD_LENGTH },
|
|
718
|
+
reference_id: { type: 'string', minLength: 1, maxLength: 1_000 },
|
|
719
|
+
attempt_id: { type: 'string', minLength: 1, maxLength: 300 },
|
|
720
|
+
},
|
|
721
|
+
required: ['field', 'reference_id', 'attempt_id'],
|
|
722
|
+
additionalProperties: false,
|
|
699
723
|
},
|
|
700
|
-
required: ['field', 'reference_id', 'attempt_id'],
|
|
701
|
-
additionalProperties: false,
|
|
702
724
|
},
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
},
|
|
707
|
-
oneOf: [
|
|
708
|
-
{ properties: { kind: { const: 'search' } }, required: ['kind'] },
|
|
709
|
-
{ properties: { kind: { const: 'read' } }, required: ['kind', 'entity_id', 'entity_type'] },
|
|
710
|
-
{ properties: { kind: { const: 'follow_relation' } }, required: ['kind', 'seed_ids', 'relation', 'direction'] },
|
|
711
|
-
{
|
|
712
|
-
properties: { kind: { const: 'finish' } },
|
|
713
|
-
required: ['kind', 'assessment', 'status', 'reference_ids', 'field_evidence', 'unresolved_items', 'termination_reason'],
|
|
714
|
-
},
|
|
725
|
+
unresolved_items: { type: 'array', maxItems: MAX_FIELDS, items: { type: 'string', minLength: 1, maxLength: MAX_HINT_LENGTH } },
|
|
726
|
+
termination_reason: { type: 'string', minLength: 1, maxLength: MAX_HINT_LENGTH },
|
|
727
|
+
}, ['assessment', 'status', 'reference_ids', 'field_evidence', 'unresolved_items', 'termination_reason']),
|
|
715
728
|
],
|
|
716
|
-
required: ['kind'],
|
|
717
|
-
additionalProperties: false,
|
|
718
729
|
},
|
|
719
730
|
},
|
|
720
731
|
required: ['question', 'target_hint', 'required_fields'],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unson/brainbase-mcp",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.14",
|
|
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",
|
|
@@ -27,6 +27,10 @@
|
|
|
27
27
|
"types": "./dist/judgment-dag-composition.d.ts",
|
|
28
28
|
"import": "./dist/judgment-dag-composition.js"
|
|
29
29
|
},
|
|
30
|
+
"./judgment-frame": {
|
|
31
|
+
"types": "./dist/judgment-frame.d.ts",
|
|
32
|
+
"import": "./dist/judgment-frame.js"
|
|
33
|
+
},
|
|
30
34
|
"./judgment-value-proof": {
|
|
31
35
|
"types": "./dist/judgment-value-proof.d.ts",
|
|
32
36
|
"import": "./dist/judgment-value-proof.js"
|
|
@@ -340,5 +344,5 @@
|
|
|
340
344
|
"engines": {
|
|
341
345
|
"node": ">=20"
|
|
342
346
|
},
|
|
343
|
-
"gitHead": "
|
|
347
|
+
"gitHead": "12a3ed63536642f2b00504b38c5a93672737c697"
|
|
344
348
|
}
|
package/ui/value-proof-review.js
CHANGED
|
@@ -875,9 +875,14 @@ export function renderValueProofReview(root, state, callbacks = {}, options = {}
|
|
|
875
875
|
surface.append(renderCoverageNotice(doc, home));
|
|
876
876
|
if (home.coverage.possiblyStalled) {
|
|
877
877
|
surface.append(workspaceNotice(doc, {
|
|
878
|
-
label: '
|
|
878
|
+
label: '価値の確認記録の停止',
|
|
879
879
|
tone: 'warning',
|
|
880
|
-
text:
|
|
880
|
+
text: noticeBody(
|
|
881
|
+
doc,
|
|
882
|
+
'しばらく新しい価値の確認記録(.value-proof.json)がありません。',
|
|
883
|
+
'この画面は判断journalのうち価値の確認記録だけを表示しています。判断journalには別の記録(episode/final)が書かれますが、この注意はjournal全体の停止を示しません。journal全体の最新の書き込み状況は、この画面では確認できません。',
|
|
884
|
+
'保存済み件数や最新時刻が未確認の場合は、0件・成功とは扱いません。',
|
|
885
|
+
),
|
|
881
886
|
}));
|
|
882
887
|
}
|
|
883
888
|
surface.append(renderFilters(doc, home, state, callbacks), renderMetrics(doc, home));
|