@unson/brainbase-mcp 0.7.0 → 0.9.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/README.md +1 -1
- package/contracts/foundation/graph-history.sql +634 -0
- package/contracts/judgment-dag/digest.json +2 -2
- package/contracts/judgment-value-proof/schema.json +43 -1
- package/dist/canonical-task-service.js +16 -5
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +107 -0
- package/dist/company-os-objectives.d.ts +11 -0
- package/dist/company-os-objectives.js +84 -47
- package/dist/foundation-authenticated-tools.d.ts +56 -0
- package/dist/foundation-authenticated-tools.js +347 -0
- package/dist/foundation-graph-http.d.ts +43 -0
- package/dist/foundation-graph-http.js +243 -0
- package/dist/foundation-graph-write.d.ts +90 -0
- package/dist/foundation-graph-write.js +447 -0
- package/dist/foundation-public-provider.d.ts +140 -0
- package/dist/foundation-public-provider.js +192 -0
- package/dist/graph-corrections.d.ts +157 -0
- package/dist/graph-corrections.js +620 -0
- package/dist/graph-foundation-reader.d.ts +60 -0
- package/dist/graph-foundation-reader.js +494 -0
- package/dist/graph-retrieval.d.ts +6 -1
- package/dist/graph-revision-reader.d.ts +80 -0
- package/dist/graph-revision-reader.js +246 -0
- package/dist/graph-web-http.d.ts +26 -0
- package/dist/graph-web-http.js +215 -0
- package/dist/graph-web.d.ts +364 -0
- package/dist/graph-web.js +639 -0
- package/dist/judgment-problem-snapshot.d.ts +30 -2
- package/dist/judgment-problem-snapshot.js +302 -6
- package/dist/judgment-value-proof-review.d.ts +166 -0
- package/dist/judgment-value-proof-review.js +437 -0
- package/dist/judgment-value-proof.d.ts +28 -1
- package/dist/judgment-value-proof.js +53 -8
- package/dist/knowledge-continuation.d.ts +127 -0
- package/dist/knowledge-continuation.js +446 -0
- package/dist/knowledge-lookup.d.ts +178 -0
- package/dist/knowledge-lookup.js +758 -0
- package/dist/local-web-fetch-bridge.d.ts +5 -0
- package/dist/local-web-fetch-bridge.js +37 -0
- package/dist/local-web-host.d.ts +109 -0
- package/dist/local-web-host.js +688 -0
- package/dist/local-web-security.d.ts +25 -0
- package/dist/local-web-security.js +95 -0
- package/dist/organization-graph.d.ts +15 -0
- package/dist/organization-graph.js +24 -4
- package/dist/personal-knowledge.d.ts +12 -0
- package/dist/personal-knowledge.js +29 -0
- package/dist/personal-memory-handover.d.ts +108 -0
- package/dist/personal-memory-handover.js +369 -0
- package/dist/philosophy-revision-reader.d.ts +85 -0
- package/dist/philosophy-revision-reader.js +241 -0
- package/dist/portable-graph.d.ts +24 -0
- package/dist/portable-graph.js +77 -0
- package/dist/server.d.ts +7 -2
- package/dist/server.js +9 -6
- package/dist/value-proof-review-http.d.ts +25 -0
- package/dist/value-proof-review-http.js +185 -0
- package/dist/world-model.d.ts +21 -3
- package/dist/world-model.js +94 -12
- package/package.json +94 -6
- package/ui/brainbase-tokens.css +56 -0
- package/ui/graph-own-share.js +367 -0
- package/ui/graph-projects-view.css +23 -0
- package/ui/graph-projects-view.js +730 -0
- package/ui/graph-registry-view.css +101 -0
- package/ui/graph-registry-view.js +804 -0
- package/ui/graph-view-shared.css +522 -0
- package/ui/graph-view-shared.js +1227 -0
- package/ui/judgment-view.css +119 -45
- package/ui/local-web-shell.css +396 -0
- package/ui/local-web-shell.js +378 -0
- package/ui/objective-editor-http-port.js +154 -0
- package/ui/objective-editor.css +180 -74
- package/ui/objective-editor.js +490 -43
- package/ui/outcome-knowledge.css +50 -14
- package/ui/outcome-knowledge.js +15 -1
- package/ui/value-proof-review.css +504 -0
- package/ui/value-proof-review.js +1104 -0
- package/ui/workspace-kit.css +485 -0
- package/ui/workspace-kit.js +229 -0
- package/ui/world-model-view.css +188 -0
- package/ui/world-model-view.js +486 -0
- package/ui/icons/mana/LICENSE.tabler.txt +0 -21
- package/ui/icons/mana/affiliate.svg +0 -20
- package/ui/icons/mana/brand-github.svg +0 -15
- package/ui/icons/mana/brand-google-drive.svg +0 -17
- package/ui/icons/mana/brand-slack.svg +0 -18
- package/ui/icons/mana/cpu.svg +0 -24
- package/ui/outcome-mana.css +0 -255
- package/ui/outcome-mana.js +0 -2325
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { appendFile, mkdir, readdir, readFile } from 'node:fs/promises';
|
|
3
|
+
import { dirname, join, resolve } from 'node:path';
|
|
4
|
+
import { placeJudgmentValueProof, validateJudgmentValueProof } from './judgment-value-proof.js';
|
|
5
|
+
import { defaultDataDir } from './paths.js';
|
|
6
|
+
export const JUDGMENT_VALUE_PROOF_FILE_SUFFIX = '.value-proof.json';
|
|
7
|
+
export const JUDGMENT_VALUE_PROOF_FEEDBACK_SCHEMA = 'brainbase-judgment-value-proof-feedback-v1';
|
|
8
|
+
export const JUDGMENT_VALUE_PROOF_FEEDBACK_FILE = 'judgment-value-proof-feedback.jsonl';
|
|
9
|
+
const FEEDBACK_SUMMARY_LIMIT = 500;
|
|
10
|
+
const DEFAULT_STALE_AFTER_MS = 48 * 60 * 60 * 1000;
|
|
11
|
+
const FEEDBACK_STATUSES = [
|
|
12
|
+
'accepted',
|
|
13
|
+
'corrected',
|
|
14
|
+
'next_time_ask',
|
|
15
|
+
'reverted'
|
|
16
|
+
];
|
|
17
|
+
const SUMMARY_REQUIRED = ['corrected', 'reverted'];
|
|
18
|
+
export const JUDGMENT_VALUE_PROOF_FEEDBACK_LAYERS = [
|
|
19
|
+
'delegation',
|
|
20
|
+
'method',
|
|
21
|
+
'objective',
|
|
22
|
+
'world_model',
|
|
23
|
+
'philosophy',
|
|
24
|
+
'other'
|
|
25
|
+
];
|
|
26
|
+
export function defaultJudgmentJournalRoot(dataDir = defaultDataDir()) {
|
|
27
|
+
return join(resolve(dataDir), 'judgment-journal');
|
|
28
|
+
}
|
|
29
|
+
function errorMessage(error) {
|
|
30
|
+
return error instanceof Error ? error.message : String(error);
|
|
31
|
+
}
|
|
32
|
+
function errorCode(error) {
|
|
33
|
+
return typeof error === 'object' && error !== null && 'code' in error
|
|
34
|
+
? String(error.code)
|
|
35
|
+
: undefined;
|
|
36
|
+
}
|
|
37
|
+
function compareRecordedAtDesc(left, right) {
|
|
38
|
+
return left < right ? 1 : left > right ? -1 : 0;
|
|
39
|
+
}
|
|
40
|
+
async function readProofFile(file) {
|
|
41
|
+
try {
|
|
42
|
+
const proof = validateJudgmentValueProof(JSON.parse(await readFile(file, 'utf8')));
|
|
43
|
+
return { file, proof };
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
return { file, reason: errorMessage(error) };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Reads saved judgment value proofs from `<root>/*.value-proof.json` and
|
|
51
|
+
* `<root>/<session>/*.value-proof.json`. The journal is read-only here.
|
|
52
|
+
*/
|
|
53
|
+
export async function readJudgmentValueProofJournal(options = {}) {
|
|
54
|
+
const root = resolve(options.root ?? defaultJudgmentJournalRoot());
|
|
55
|
+
let topLevel;
|
|
56
|
+
try {
|
|
57
|
+
topLevel = await readdir(root, { withFileTypes: true });
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
const code = errorCode(error);
|
|
61
|
+
const reason = code === 'ENOENT'
|
|
62
|
+
? 'judgment_journal_not_found'
|
|
63
|
+
: code === 'ENOTDIR'
|
|
64
|
+
? 'judgment_journal_not_directory'
|
|
65
|
+
: `judgment_journal_unreadable:${code ?? errorMessage(error)}`;
|
|
66
|
+
return { status: 'unavailable', root, reason };
|
|
67
|
+
}
|
|
68
|
+
const files = [];
|
|
69
|
+
for (const entry of topLevel) {
|
|
70
|
+
const path = join(root, entry.name);
|
|
71
|
+
if (entry.isFile() && entry.name.endsWith(JUDGMENT_VALUE_PROOF_FILE_SUFFIX)) {
|
|
72
|
+
files.push(path);
|
|
73
|
+
}
|
|
74
|
+
else if (entry.isDirectory()) {
|
|
75
|
+
const nested = await readdir(path, { withFileTypes: true });
|
|
76
|
+
for (const child of nested) {
|
|
77
|
+
if (child.isFile() && child.name.endsWith(JUDGMENT_VALUE_PROOF_FILE_SUFFIX)) {
|
|
78
|
+
files.push(join(path, child.name));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const results = await Promise.all(files.sort().map(readProofFile));
|
|
84
|
+
const entries = results
|
|
85
|
+
.filter((result) => 'proof' in result)
|
|
86
|
+
.sort((left, right) => compareRecordedAtDesc(left.proof.recorded_at, right.proof.recorded_at)
|
|
87
|
+
|| left.file.localeCompare(right.file));
|
|
88
|
+
const rejected = results.filter((result) => 'reason' in result);
|
|
89
|
+
return {
|
|
90
|
+
status: 'available',
|
|
91
|
+
root,
|
|
92
|
+
entries,
|
|
93
|
+
rejected,
|
|
94
|
+
latest_recorded_at: entries[0]?.proof.recorded_at ?? null
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
export function classifyJudgmentValueProof(proof) {
|
|
98
|
+
if (placeJudgmentValueProof(proof).web_surface === 'none')
|
|
99
|
+
return 'other';
|
|
100
|
+
if (proof.state === 'waiting_human')
|
|
101
|
+
return 'needs_human';
|
|
102
|
+
if (proof.state === 'blocked')
|
|
103
|
+
return 'blocked';
|
|
104
|
+
if (proof.interruption.resolution === 'continued_without_human')
|
|
105
|
+
return 'continued';
|
|
106
|
+
return 'other';
|
|
107
|
+
}
|
|
108
|
+
function feedbackFile(dataDir) {
|
|
109
|
+
return join(resolve(dataDir ?? defaultDataDir()), JUDGMENT_VALUE_PROOF_FEEDBACK_FILE);
|
|
110
|
+
}
|
|
111
|
+
function requiredId(value, field) {
|
|
112
|
+
const normalized = typeof value === 'string' ? value.trim() : '';
|
|
113
|
+
if (!normalized)
|
|
114
|
+
throw new TypeError(`${field} must be a non-empty string`);
|
|
115
|
+
return normalized;
|
|
116
|
+
}
|
|
117
|
+
function normalizeSummary(status, summary) {
|
|
118
|
+
const normalized = typeof summary === 'string' ? summary.trim() : '';
|
|
119
|
+
if (!normalized && SUMMARY_REQUIRED.includes(status)) {
|
|
120
|
+
throw new TypeError(`${status} feedback requires summary`);
|
|
121
|
+
}
|
|
122
|
+
if (Array.from(normalized).length > FEEDBACK_SUMMARY_LIMIT) {
|
|
123
|
+
throw new TypeError(`feedback summary must be at most ${FEEDBACK_SUMMARY_LIMIT} characters`);
|
|
124
|
+
}
|
|
125
|
+
return normalized || null;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* `next_time_ask` always targets the delegation boundary; `corrected` must say what it corrects;
|
|
129
|
+
* `accepted` and `reverted` carry no layer.
|
|
130
|
+
*/
|
|
131
|
+
function normalizeTargetLayer(status, layer) {
|
|
132
|
+
if (layer !== undefined && layer !== null && !JUDGMENT_VALUE_PROOF_FEEDBACK_LAYERS.includes(layer)) {
|
|
133
|
+
throw new TypeError(`unsupported feedback target_layer: ${String(layer)}`);
|
|
134
|
+
}
|
|
135
|
+
if (status === 'next_time_ask') {
|
|
136
|
+
if (layer && layer !== 'delegation')
|
|
137
|
+
throw new TypeError('next_time_ask feedback targets delegation');
|
|
138
|
+
return 'delegation';
|
|
139
|
+
}
|
|
140
|
+
if (status === 'corrected') {
|
|
141
|
+
if (!layer)
|
|
142
|
+
throw new TypeError('corrected feedback requires target_layer');
|
|
143
|
+
return layer;
|
|
144
|
+
}
|
|
145
|
+
if (layer)
|
|
146
|
+
throw new TypeError(`${status} feedback does not take target_layer`);
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
function feedbackId(decisionAttemptId, status, layer, summary) {
|
|
150
|
+
return `sha256:${createHash('sha256').update(`${decisionAttemptId}\n${status}\n${layer ?? ''}\n${summary ?? ''}`).digest('hex')}`;
|
|
151
|
+
}
|
|
152
|
+
function parseFeedbackRecord(line, lineNumber) {
|
|
153
|
+
let value;
|
|
154
|
+
try {
|
|
155
|
+
value = JSON.parse(line);
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
throw new Error(`judgment_value_proof_feedback_corrupt:line_${lineNumber}`, { cause: error });
|
|
159
|
+
}
|
|
160
|
+
if (value.schema_version !== JUDGMENT_VALUE_PROOF_FEEDBACK_SCHEMA
|
|
161
|
+
|| !FEEDBACK_STATUSES.includes(value.status)
|
|
162
|
+
|| typeof value.feedback_id !== 'string'
|
|
163
|
+
|| typeof value.intent_id !== 'string'
|
|
164
|
+
|| typeof value.decision_attempt_id !== 'string'
|
|
165
|
+
|| typeof value.recorded_at !== 'string'
|
|
166
|
+
|| !(value.summary === null || typeof value.summary === 'string')
|
|
167
|
+
|| !(value.target_layer === undefined || value.target_layer === null
|
|
168
|
+
|| JUDGMENT_VALUE_PROOF_FEEDBACK_LAYERS.includes(value.target_layer))) {
|
|
169
|
+
throw new Error(`judgment_value_proof_feedback_invalid:line_${lineNumber}`);
|
|
170
|
+
}
|
|
171
|
+
return value;
|
|
172
|
+
}
|
|
173
|
+
/** Reads all recorded feedback in append order. A missing file means no feedback has been recorded. */
|
|
174
|
+
export async function readJudgmentValueProofFeedback(options = {}) {
|
|
175
|
+
let text;
|
|
176
|
+
try {
|
|
177
|
+
text = await readFile(feedbackFile(options.dataDir), 'utf8');
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
if (errorCode(error) === 'ENOENT')
|
|
181
|
+
return [];
|
|
182
|
+
throw error;
|
|
183
|
+
}
|
|
184
|
+
return text.split('\n')
|
|
185
|
+
.map((line, index) => ({ line, lineNumber: index + 1 }))
|
|
186
|
+
.filter(({ line }) => line.trim())
|
|
187
|
+
.map(({ line, lineNumber }) => parseFeedbackRecord(line, lineNumber));
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Appends one owner feedback record without touching the judgment journal.
|
|
191
|
+
* Re-sending the same attempt, status and summary returns the existing record.
|
|
192
|
+
*/
|
|
193
|
+
export async function recordJudgmentValueProofFeedback(input) {
|
|
194
|
+
const intentId = requiredId(input.intent_id, 'intent_id');
|
|
195
|
+
const decisionAttemptId = requiredId(input.decision_attempt_id, 'decision_attempt_id');
|
|
196
|
+
if (!FEEDBACK_STATUSES.includes(input.status)) {
|
|
197
|
+
throw new TypeError(`unsupported feedback status: ${String(input.status)}`);
|
|
198
|
+
}
|
|
199
|
+
const summary = normalizeSummary(input.status, input.summary);
|
|
200
|
+
const targetLayer = normalizeTargetLayer(input.status, input.target_layer);
|
|
201
|
+
const id = feedbackId(decisionAttemptId, input.status, targetLayer, summary);
|
|
202
|
+
const existing = (await readJudgmentValueProofFeedback({ dataDir: input.dataDir }))
|
|
203
|
+
.find((record) => record.feedback_id === id);
|
|
204
|
+
if (existing)
|
|
205
|
+
return { record: existing, created: false };
|
|
206
|
+
const record = {
|
|
207
|
+
schema_version: JUDGMENT_VALUE_PROOF_FEEDBACK_SCHEMA,
|
|
208
|
+
feedback_id: id,
|
|
209
|
+
intent_id: intentId,
|
|
210
|
+
decision_attempt_id: decisionAttemptId,
|
|
211
|
+
status: input.status,
|
|
212
|
+
summary,
|
|
213
|
+
target_layer: targetLayer,
|
|
214
|
+
recorded_at: (input.now ?? new Date()).toISOString()
|
|
215
|
+
};
|
|
216
|
+
const file = feedbackFile(input.dataDir);
|
|
217
|
+
await mkdir(dirname(file), { recursive: true });
|
|
218
|
+
await appendFile(file, `${JSON.stringify(record)}\n`, 'utf8');
|
|
219
|
+
const readBack = (await readJudgmentValueProofFeedback({ dataDir: input.dataDir }))
|
|
220
|
+
.find((entry) => entry.feedback_id === id);
|
|
221
|
+
if (!readBack)
|
|
222
|
+
throw new Error('judgment_value_proof_feedback_readback_missing');
|
|
223
|
+
return { record: readBack, created: true };
|
|
224
|
+
}
|
|
225
|
+
/** Returns the proof with its latest owner feedback applied, plus the feedback history for that attempt. */
|
|
226
|
+
export function applyJudgmentValueProofFeedback(proof, records) {
|
|
227
|
+
const history = records.filter((record) => record.intent_id === proof.intent_id
|
|
228
|
+
&& record.decision_attempt_id === proof.decision_attempt_id);
|
|
229
|
+
const latest = history.at(-1);
|
|
230
|
+
if (!latest)
|
|
231
|
+
return { proof, history };
|
|
232
|
+
const applied = {
|
|
233
|
+
...proof,
|
|
234
|
+
feedback: {
|
|
235
|
+
status: latest.status,
|
|
236
|
+
summary: latest.summary,
|
|
237
|
+
evidence_ref: {
|
|
238
|
+
kind: 'human_feedback',
|
|
239
|
+
ref: `judgment-value-proof-feedback:${latest.feedback_id}`,
|
|
240
|
+
status: 'verified',
|
|
241
|
+
label: '本人の評価'
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
return { proof: validateJudgmentValueProof(applied), history };
|
|
246
|
+
}
|
|
247
|
+
export function buildJudgmentValueProofReviewHome(journal, feedback, options = {}) {
|
|
248
|
+
if (journal.status === 'unavailable')
|
|
249
|
+
return journal;
|
|
250
|
+
const sections = {
|
|
251
|
+
needs_human: [],
|
|
252
|
+
blocked: [],
|
|
253
|
+
continued: [],
|
|
254
|
+
other: []
|
|
255
|
+
};
|
|
256
|
+
for (const entry of journal.entries) {
|
|
257
|
+
const { proof, history } = applyJudgmentValueProofFeedback(entry.proof, feedback);
|
|
258
|
+
const section = classifyJudgmentValueProof(proof);
|
|
259
|
+
sections[section].push({ file: entry.file, section, proof, feedback_history: history });
|
|
260
|
+
}
|
|
261
|
+
const delegationMap = buildJudgmentDelegationMap([...sections.needs_human, ...sections.blocked, ...sections.continued], feedback);
|
|
262
|
+
const now = (options.now ?? new Date()).getTime();
|
|
263
|
+
const staleAfterMs = options.staleAfterMs ?? DEFAULT_STALE_AFTER_MS;
|
|
264
|
+
const latest = journal.latest_recorded_at === null ? null : Date.parse(journal.latest_recorded_at);
|
|
265
|
+
const possiblyStalled = latest === null || Number.isNaN(latest) || now - latest > staleAfterMs;
|
|
266
|
+
return {
|
|
267
|
+
status: 'available',
|
|
268
|
+
root: journal.root,
|
|
269
|
+
coverage: {
|
|
270
|
+
saved: journal.entries.length,
|
|
271
|
+
rejected: journal.rejected.length,
|
|
272
|
+
latest_recorded_at: journal.latest_recorded_at,
|
|
273
|
+
possibly_stalled: possiblyStalled
|
|
274
|
+
},
|
|
275
|
+
sections,
|
|
276
|
+
delegation_map: delegationMap,
|
|
277
|
+
rejected: journal.rejected
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function delegationRowIdentity(proof) {
|
|
281
|
+
const kind = proof.decision.judgment_kind;
|
|
282
|
+
if (kind)
|
|
283
|
+
return { key: `kind:${kind.key}`, source: 'judgment_kind', label: kind.label };
|
|
284
|
+
const reason = proof.interruption.reason_code?.trim();
|
|
285
|
+
if (reason)
|
|
286
|
+
return { key: `reason:${reason}`, source: 'reason_code', label: reason };
|
|
287
|
+
return { key: 'unrecorded', source: 'unrecorded', label: null };
|
|
288
|
+
}
|
|
289
|
+
function laterFeedback(left, right) {
|
|
290
|
+
return !left || right.record.recorded_at > left.record.recorded_at
|
|
291
|
+
|| (right.record.recorded_at === left.record.recorded_at && right.order > left.order);
|
|
292
|
+
}
|
|
293
|
+
function returnsDelegation(record) {
|
|
294
|
+
return record.status === 'next_time_ask' || record.status === 'reverted'
|
|
295
|
+
|| (record.status === 'corrected' && record.target_layer === 'delegation');
|
|
296
|
+
}
|
|
297
|
+
function newestFirst(entries) {
|
|
298
|
+
return [...entries].sort((left, right) => compareRecordedAtDesc(left.at, right.at)).map((entry) => entry.ref);
|
|
299
|
+
}
|
|
300
|
+
function itemRef(item, rowKey) {
|
|
301
|
+
return { intent_id: item.proof.intent_id, decision_attempt_id: item.proof.decision_attempt_id, row_key: rowKey };
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Groups reviewable judgments by kind and decides, per kind, whether Brainbase is trusted to
|
|
305
|
+
* proceed, is still being checked, or returns the judgment to the owner. Deterministic: the state
|
|
306
|
+
* follows only the newest judgment and the newest owner feedback in the row.
|
|
307
|
+
*/
|
|
308
|
+
export function buildJudgmentDelegationMap(items, feedback) {
|
|
309
|
+
const order = new Map(feedback.map((record, index) => [record, index]));
|
|
310
|
+
const sorted = [...items].sort((left, right) => compareRecordedAtDesc(left.proof.recorded_at, right.proof.recorded_at)
|
|
311
|
+
|| left.file.localeCompare(right.file));
|
|
312
|
+
const groups = new Map();
|
|
313
|
+
for (const item of sorted) {
|
|
314
|
+
const identity = delegationRowIdentity(item.proof);
|
|
315
|
+
const group = groups.get(identity.key) ?? { identity, items: [] };
|
|
316
|
+
group.items.push(item);
|
|
317
|
+
groups.set(identity.key, group);
|
|
318
|
+
}
|
|
319
|
+
const correctedAfterContinue = [];
|
|
320
|
+
const continuedAfterAsk = [];
|
|
321
|
+
const rows = [];
|
|
322
|
+
for (const { identity, items: rowItems } of groups.values()) {
|
|
323
|
+
const byFeedback = { accepted: 0, corrected: 0, next_time_ask: 0, reverted: 0 };
|
|
324
|
+
let latestFeedback = null;
|
|
325
|
+
// The first ask is the cutoff: every later judgment that still proceeded without asking ignored it.
|
|
326
|
+
let firstAsk = null;
|
|
327
|
+
let continued = 0;
|
|
328
|
+
let returned = 0;
|
|
329
|
+
let rated = 0;
|
|
330
|
+
let inherited = 0;
|
|
331
|
+
for (const item of rowItems) {
|
|
332
|
+
const resolution = item.proof.interruption.resolution;
|
|
333
|
+
if (resolution === 'continued_without_human')
|
|
334
|
+
continued += 1;
|
|
335
|
+
if (resolution === 'human_required')
|
|
336
|
+
returned += 1;
|
|
337
|
+
if ((item.proof.decision.inheritance?.sources.length ?? 0) > 0)
|
|
338
|
+
inherited += 1;
|
|
339
|
+
const latest = item.feedback_history.at(-1);
|
|
340
|
+
if (latest) {
|
|
341
|
+
rated += 1;
|
|
342
|
+
byFeedback[latest.status] += 1;
|
|
343
|
+
if (resolution === 'continued_without_human' && (latest.status === 'corrected' || latest.status === 'reverted')) {
|
|
344
|
+
correctedAfterContinue.push({ ref: itemRef(item, identity.key), at: item.proof.recorded_at });
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
for (const record of item.feedback_history) {
|
|
348
|
+
const candidate = { record, order: order.get(record) ?? -1 };
|
|
349
|
+
if (laterFeedback(latestFeedback, candidate))
|
|
350
|
+
latestFeedback = candidate;
|
|
351
|
+
if (record.status === 'next_time_ask' && (!firstAsk || record.recorded_at < firstAsk.recorded_at))
|
|
352
|
+
firstAsk = record;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
if (firstAsk) {
|
|
356
|
+
for (const item of rowItems) {
|
|
357
|
+
if (item.proof.interruption.resolution === 'continued_without_human'
|
|
358
|
+
&& item.proof.recorded_at > firstAsk.recorded_at
|
|
359
|
+
&& item.feedback_history.at(-1)?.status !== 'accepted') {
|
|
360
|
+
continuedAfterAsk.push({ ref: itemRef(item, identity.key), at: item.proof.recorded_at });
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
const newest = rowItems[0].proof;
|
|
365
|
+
let state;
|
|
366
|
+
let basis;
|
|
367
|
+
const feedbackBasis = (record) => ({
|
|
368
|
+
reason: 'latest_feedback',
|
|
369
|
+
status: record.status,
|
|
370
|
+
target_layer: record.target_layer ?? (record.status === 'next_time_ask' ? 'delegation' : null),
|
|
371
|
+
at: record.recorded_at
|
|
372
|
+
});
|
|
373
|
+
if (newest.interruption.resolution === 'human_required') {
|
|
374
|
+
state = 'returned';
|
|
375
|
+
basis = { reason: 'latest_judgment_returned', at: newest.recorded_at };
|
|
376
|
+
}
|
|
377
|
+
else if (latestFeedback && returnsDelegation(latestFeedback.record)) {
|
|
378
|
+
state = 'returned';
|
|
379
|
+
basis = feedbackBasis(latestFeedback.record);
|
|
380
|
+
}
|
|
381
|
+
else if (newest.interruption.resolution === 'continued_without_human' && latestFeedback?.record.status === 'accepted') {
|
|
382
|
+
state = 'delegated';
|
|
383
|
+
basis = feedbackBasis(latestFeedback.record);
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
state = 'verifying';
|
|
387
|
+
basis = latestFeedback ? feedbackBasis(latestFeedback.record) : { reason: 'no_feedback' };
|
|
388
|
+
}
|
|
389
|
+
rows.push({
|
|
390
|
+
key: identity.key,
|
|
391
|
+
source: identity.source,
|
|
392
|
+
// The newest judgment names a kind row, so a relabelled kind shows its current label.
|
|
393
|
+
label: identity.source === 'judgment_kind' ? newest.decision.judgment_kind?.label ?? identity.label : identity.label,
|
|
394
|
+
state,
|
|
395
|
+
state_basis: basis,
|
|
396
|
+
counts: {
|
|
397
|
+
continued,
|
|
398
|
+
returned,
|
|
399
|
+
rated,
|
|
400
|
+
by_feedback: byFeedback,
|
|
401
|
+
corrected_or_reverted: byFeedback.corrected + byFeedback.reverted,
|
|
402
|
+
inherited
|
|
403
|
+
},
|
|
404
|
+
latest_recorded_at: newest.recorded_at,
|
|
405
|
+
items: rowItems.map((item) => itemRef(item, identity.key))
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
const sourceRank = { judgment_kind: 0, reason_code: 1, unrecorded: 2 };
|
|
409
|
+
rows.sort((left, right) => sourceRank[left.source] - sourceRank[right.source]
|
|
410
|
+
|| compareRecordedAtDesc(left.latest_recorded_at, right.latest_recorded_at)
|
|
411
|
+
|| left.key.localeCompare(right.key));
|
|
412
|
+
return {
|
|
413
|
+
judged: sorted.length,
|
|
414
|
+
kind_recorded: sorted.filter((item) => item.proof.decision.judgment_kind).length,
|
|
415
|
+
rated: rows.reduce((total, row) => total + row.counts.rated, 0),
|
|
416
|
+
rows,
|
|
417
|
+
corrected_after_continue: newestFirst(correctedAfterContinue),
|
|
418
|
+
continued_after_ask: newestFirst(continuedAfterAsk)
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
/** Counts the latest feedback per decision, for deciding where revisions should live. */
|
|
422
|
+
export function summarizeJudgmentValueProofFeedback(records) {
|
|
423
|
+
const latest = new Map();
|
|
424
|
+
for (const record of records)
|
|
425
|
+
latest.set(`${record.intent_id}\u0000${record.decision_attempt_id}`, record);
|
|
426
|
+
const byStatus = { accepted: 0, corrected: 0, next_time_ask: 0, reverted: 0 };
|
|
427
|
+
const byLayer = {
|
|
428
|
+
delegation: 0, method: 0, objective: 0, world_model: 0, philosophy: 0, other: 0, unrecorded: 0
|
|
429
|
+
};
|
|
430
|
+
for (const record of latest.values()) {
|
|
431
|
+
byStatus[record.status] += 1;
|
|
432
|
+
if (record.status === 'corrected' || record.status === 'next_time_ask') {
|
|
433
|
+
byLayer[record.target_layer ?? (record.status === 'next_time_ask' ? 'delegation' : 'unrecorded')] += 1;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return { rated: latest.size, by_status: byStatus, by_layer: byLayer };
|
|
437
|
+
}
|
|
@@ -12,9 +12,33 @@ export interface JudgmentValueProofArtifactRef {
|
|
|
12
12
|
ref: string;
|
|
13
13
|
label?: string;
|
|
14
14
|
}
|
|
15
|
+
/** Which part of the organization's judgment a basis came from. Absent means not recorded. */
|
|
16
|
+
export type JudgmentValueProofBasisLayer = 'philosophy' | 'objective' | 'world_model' | 'method' | 'constraint' | 'other';
|
|
15
17
|
export interface JudgmentValueProofBasis {
|
|
16
18
|
entity_id: string;
|
|
17
19
|
application: string;
|
|
20
|
+
layer?: JudgmentValueProofBasisLayer;
|
|
21
|
+
/** Version of the referenced entity used for this judgment. */
|
|
22
|
+
version?: string | null;
|
|
23
|
+
}
|
|
24
|
+
export interface JudgmentValueProofInheritanceSource {
|
|
25
|
+
kind: 'judgment' | 'method' | 'decision' | 'other';
|
|
26
|
+
ref: string;
|
|
27
|
+
version: string | null;
|
|
28
|
+
label: string;
|
|
29
|
+
}
|
|
30
|
+
/** Earlier experience carried into this judgment, recorded at judgment time. */
|
|
31
|
+
export interface JudgmentValueProofInheritance {
|
|
32
|
+
sources: JudgmentValueProofInheritanceSource[];
|
|
33
|
+
/** Conditions judged to be the same as the source, so the source could be reused. */
|
|
34
|
+
same_conditions: string[];
|
|
35
|
+
/** Conditions that differed and were checked again for this judgment. */
|
|
36
|
+
rechecked_conditions: string[];
|
|
37
|
+
}
|
|
38
|
+
/** Stable grouping for delegation scope, e.g. `production_release`. */
|
|
39
|
+
export interface JudgmentValueProofKind {
|
|
40
|
+
key: string;
|
|
41
|
+
label: string;
|
|
18
42
|
}
|
|
19
43
|
export interface JudgmentValueProofHumanOption {
|
|
20
44
|
id: string;
|
|
@@ -39,6 +63,8 @@ export interface JudgmentValueProof {
|
|
|
39
63
|
work_impact: string | null;
|
|
40
64
|
basis: JudgmentValueProofBasis[];
|
|
41
65
|
prior_learning_reused: boolean | 'unconfirmed';
|
|
66
|
+
judgment_kind?: JudgmentValueProofKind | null;
|
|
67
|
+
inheritance?: JudgmentValueProofInheritance | null;
|
|
42
68
|
};
|
|
43
69
|
execution: {
|
|
44
70
|
status: 'not_started' | 'executing' | 'completed' | 'blocked';
|
|
@@ -65,7 +91,8 @@ export interface JudgmentValueProofPlacement {
|
|
|
65
91
|
agent_progress: 'silent' | 'show';
|
|
66
92
|
agent_completion: 'silent' | 'show';
|
|
67
93
|
companion_attention: 'none' | 'human_decision' | 'blocked' | 'outcome_unconfirmed' | 'feedback_requested';
|
|
68
|
-
|
|
94
|
+
/** `review`: listed on the local owner review surface (same scope as the weekly digest). */
|
|
95
|
+
web_surface: 'none' | 'review';
|
|
69
96
|
weekly_digest: 'exclude' | 'include';
|
|
70
97
|
}
|
|
71
98
|
export interface JudgmentValueProofCompanionItem {
|
|
@@ -17,6 +17,25 @@ function outcomeStatusLabel(status) {
|
|
|
17
17
|
return '結果未確認';
|
|
18
18
|
return '成果確認の対象外';
|
|
19
19
|
}
|
|
20
|
+
const BASIS_LAYER_LABELS = {
|
|
21
|
+
philosophy: '大切にすること',
|
|
22
|
+
objective: '目的',
|
|
23
|
+
world_model: '現状と見通し',
|
|
24
|
+
method: '判断方法',
|
|
25
|
+
constraint: '守る条件',
|
|
26
|
+
other: 'その他'
|
|
27
|
+
};
|
|
28
|
+
function basisLabel(entry) {
|
|
29
|
+
return entry.layer ? `[${BASIS_LAYER_LABELS[entry.layer]}] ${entry.application}` : entry.application;
|
|
30
|
+
}
|
|
31
|
+
function inheritanceLine(inheritance) {
|
|
32
|
+
if (!inheritance || inheritance.sources.length === 0)
|
|
33
|
+
return null;
|
|
34
|
+
const sources = inheritance.sources.map((source) => source.label.trim()).join(' / ');
|
|
35
|
+
const same = inheritance.same_conditions.length > 0 ? `。今回も同じ: ${inheritance.same_conditions.join('、')}` : '';
|
|
36
|
+
const rechecked = inheritance.rechecked_conditions.length > 0 ? `。今回だけ確認: ${inheritance.rechecked_conditions.join('、')}` : '';
|
|
37
|
+
return `引き継ぎ: ${sources}${same}${rechecked}`;
|
|
38
|
+
}
|
|
20
39
|
function evidenceLabel(evidence) {
|
|
21
40
|
const label = evidence.label?.trim() || evidence.kind;
|
|
22
41
|
return `${label} (${evidence.status === 'verified' ? '確認済み' : '未確認'})`;
|
|
@@ -57,6 +76,29 @@ export function validateJudgmentValueProof(proof) {
|
|
|
57
76
|
throw new TypeError('outcome_verified requires verified evidence');
|
|
58
77
|
}
|
|
59
78
|
}
|
|
79
|
+
const layers = ['philosophy', 'objective', 'world_model', 'method', 'constraint', 'other'];
|
|
80
|
+
for (const entry of proof.decision.basis) {
|
|
81
|
+
if (entry.layer !== undefined && !layers.includes(entry.layer)) {
|
|
82
|
+
throw new TypeError(`unsupported decision.basis[].layer: ${String(entry.layer)}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const kind = proof.decision.judgment_kind;
|
|
86
|
+
if (kind !== undefined && kind !== null) {
|
|
87
|
+
if (!/^[a-z0-9_]{1,64}$/u.test(kind.key ?? '')) {
|
|
88
|
+
throw new TypeError('decision.judgment_kind.key must be 1-64 lowercase letters, digits or underscores');
|
|
89
|
+
}
|
|
90
|
+
requiredText(kind.label, 'decision.judgment_kind.label');
|
|
91
|
+
}
|
|
92
|
+
const inheritance = proof.decision.inheritance;
|
|
93
|
+
if (inheritance !== undefined && inheritance !== null) {
|
|
94
|
+
for (const source of inheritance.sources) {
|
|
95
|
+
requiredText(source.ref, 'decision.inheritance.sources[].ref');
|
|
96
|
+
requiredText(source.label, 'decision.inheritance.sources[].label');
|
|
97
|
+
}
|
|
98
|
+
if (inheritance.sources.length > 0 && proof.decision.prior_learning_reused === false) {
|
|
99
|
+
throw new TypeError('decision.inheritance.sources contradicts prior_learning_reused=false');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
60
102
|
if (proof.feedback.status !== 'none' && proof.feedback.status !== 'pending'
|
|
61
103
|
&& !proof.feedback.evidence_ref) {
|
|
62
104
|
throw new TypeError('recorded feedback requires evidence_ref');
|
|
@@ -80,17 +122,18 @@ export function placeJudgmentValueProof(proof) {
|
|
|
80
122
|
}
|
|
81
123
|
const behaviorChanged = proof.interruption.resolution === 'continued_without_human';
|
|
82
124
|
const completed = proof.execution.status === 'completed';
|
|
125
|
+
const weeklyDigest = proof.interruption.resolution === 'not_applicable'
|
|
126
|
+
&& proof.feedback.status === 'none'
|
|
127
|
+
&& proof.state !== 'blocked'
|
|
128
|
+
&& proof.outcome.status === 'not_applicable'
|
|
129
|
+
? 'exclude'
|
|
130
|
+
: 'include';
|
|
83
131
|
return {
|
|
84
132
|
agent_progress: behaviorChanged && proof.execution.status === 'executing' ? 'show' : 'silent',
|
|
85
133
|
agent_completion: behaviorChanged && completed ? 'show' : 'silent',
|
|
86
134
|
companion_attention: companionAttention,
|
|
87
|
-
web_surface: 'none',
|
|
88
|
-
weekly_digest:
|
|
89
|
-
&& proof.feedback.status === 'none'
|
|
90
|
-
&& proof.state !== 'blocked'
|
|
91
|
-
&& proof.outcome.status === 'not_applicable'
|
|
92
|
-
? 'exclude'
|
|
93
|
-
: 'include'
|
|
135
|
+
web_surface: weeklyDigest === 'include' ? 'review' : 'none',
|
|
136
|
+
weekly_digest: weeklyDigest
|
|
94
137
|
};
|
|
95
138
|
}
|
|
96
139
|
export function renderJudgmentValueProofProgress(proof) {
|
|
@@ -111,8 +154,9 @@ export function renderJudgmentValueProofCompletion(proof) {
|
|
|
111
154
|
const decision = requiredText(proof.decision.summary ?? '', 'decision.summary');
|
|
112
155
|
const impact = optionalText(proof.decision.work_impact) ?? '確認による中断を避けて作業を継続';
|
|
113
156
|
const basis = proof.decision.basis.length > 0
|
|
114
|
-
? proof.decision.basis.map(
|
|
157
|
+
? proof.decision.basis.map(basisLabel).join(' / ')
|
|
115
158
|
: '適用根拠は未確認';
|
|
159
|
+
const inheritance = inheritanceLine(proof.decision.inheritance);
|
|
116
160
|
const evidence = proof.outcome.evidence_refs.length > 0
|
|
117
161
|
? proof.outcome.evidence_refs.map(evidenceLabel).join(' / ')
|
|
118
162
|
: '成果証跡なし';
|
|
@@ -122,6 +166,7 @@ export function renderJudgmentValueProofCompletion(proof) {
|
|
|
122
166
|
`判断: ${decision}`,
|
|
123
167
|
`仕事への影響: ${impact}`,
|
|
124
168
|
`根拠: ${basis}`,
|
|
169
|
+
...(inheritance ? [inheritance] : []),
|
|
125
170
|
`状態: ${outcomeStatusLabel(proof.outcome.status)}`,
|
|
126
171
|
`証拠: ${evidence}`,
|
|
127
172
|
'修正する場合: 「判断を修正: …」または「次回は確認」と返信'
|