@unson/brainbase-mcp 0.43.0 → 0.44.1
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/contracts/canonical-task/operation-schema.sql +58 -0
- package/contracts/canonical-task/search-indexes.sql +10 -0
- package/contracts/canonical-task/store-schema.sql +77 -0
- package/dist/automation-run.d.ts +213 -0
- package/dist/automation-run.js +479 -0
- package/dist/canonical-task-operations.d.ts +205 -0
- package/dist/canonical-task-operations.js +700 -0
- package/dist/canonical-task-postgres.d.ts +139 -0
- package/dist/canonical-task-postgres.js +554 -0
- package/dist/canonical-task-service.d.ts +13 -0
- package/dist/canonical-task-service.js +55 -3
- package/dist/canonical-task-store-config.d.ts +31 -0
- package/dist/canonical-task-store-config.js +53 -0
- package/dist/config-parser.d.ts +298 -0
- package/dist/config-parser.js +593 -0
- package/dist/connected-onboarding.d.ts +57 -0
- package/dist/connected-onboarding.js +37 -0
- package/dist/durable-wait-http.d.ts +3 -1
- package/dist/durable-wait-http.js +73 -6
- package/dist/durable-waits.d.ts +93 -3
- package/dist/durable-waits.js +320 -8
- package/dist/honcho.d.ts +25 -0
- package/dist/honcho.js +81 -0
- package/dist/judgment-event-adapter.d.ts +72 -0
- package/dist/judgment-event-adapter.js +114 -0
- package/dist/judgment-event-outbox.d.ts +182 -0
- package/dist/judgment-event-outbox.js +568 -0
- package/dist/judgment-execution-outcome.d.ts +46 -0
- package/dist/judgment-execution-outcome.js +149 -0
- package/dist/judgment-host.d.ts +18 -7
- package/dist/judgment-host.js +82 -34
- package/dist/judgment-lifecycle-core.d.ts +198 -0
- package/dist/judgment-lifecycle-core.js +464 -0
- package/dist/judgment-node-evidence.d.ts +57 -0
- package/dist/judgment-node-evidence.js +297 -0
- package/dist/judgment-runtime-resolution.d.ts +264 -0
- package/dist/judgment-runtime-resolution.js +1248 -0
- package/dist/judgment-runtime-resolution.public.d.ts +255 -0
- package/dist/knowledge-event.d.ts +28 -0
- package/dist/knowledge-event.js +114 -0
- package/dist/mcp-runtime.d.ts +75 -0
- package/dist/mcp-runtime.js +188 -0
- package/dist/meeting-automation-runtime.d.ts +102 -0
- package/dist/meeting-automation-runtime.js +249 -0
- package/dist/meeting-automation.d.ts +155 -0
- package/dist/meeting-automation.js +341 -0
- package/dist/meeting-minutes-context-receipt.d.ts +91 -0
- package/dist/meeting-minutes-context-receipt.js +280 -0
- package/dist/meeting-source-sync.d.ts +89 -0
- package/dist/meeting-source-sync.js +230 -0
- package/dist/mesh.d.ts +58 -0
- package/dist/mesh.js +134 -0
- package/dist/onboarding-runtime.d.ts +47 -0
- package/dist/onboarding-runtime.js +4719 -0
- package/dist/routine-cycle.d.ts +51 -0
- package/dist/routine-cycle.js +153 -0
- package/dist/routine-liveness.d.ts +49 -0
- package/dist/routine-liveness.js +227 -0
- package/dist/routine-runtime.d.ts +101 -0
- package/dist/routine-runtime.js +237 -0
- package/dist/routines.d.ts +3 -0
- package/dist/routines.js +3 -0
- package/dist/run-receipt.d.ts +40 -0
- package/dist/run-receipt.js +635 -0
- package/dist/runtime-host.d.ts +64 -0
- package/dist/runtime-host.js +70 -0
- package/dist/world-work.d.ts +16 -0
- package/package.json +115 -3
- package/ui/meeting-minutes.css +122 -0
- package/ui/meeting-minutes.js +316 -0
- package/ui/objective-editor.js +47 -11
- package/ui/world/world-district.js +18 -3
- package/ui/world/world-placement.js +13 -0
- package/ui/world/world-work-rail.js +7 -1
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import type { ConversationMessage, JudgmentOwnerAudit, JudgmentReceipt, JudgmentRequest } from './judgment-host.js';
|
|
2
|
+
/**
|
|
3
|
+
* The lifecycle core deliberately has no filesystem, environment, Graph, or
|
|
4
|
+
* network dependency. Hosts provide those boundaries through the ports below.
|
|
5
|
+
* A host therefore keeps ownership of the canonical journal and can adopt this
|
|
6
|
+
* state machine without moving its existing persistence or authorization code.
|
|
7
|
+
*/
|
|
8
|
+
export type MaybePromise<T> = T | PromiseLike<T>;
|
|
9
|
+
export interface JudgmentLifecycleIdentity {
|
|
10
|
+
session_ref: string;
|
|
11
|
+
turn_id: string;
|
|
12
|
+
}
|
|
13
|
+
export interface JudgmentLifecycleRequestInput {
|
|
14
|
+
request: string;
|
|
15
|
+
turn_id: string;
|
|
16
|
+
session_ref: string;
|
|
17
|
+
project_code?: string;
|
|
18
|
+
messages?: readonly ConversationMessage[];
|
|
19
|
+
prior_receipts?: readonly Record<string, unknown>[];
|
|
20
|
+
runtime?: {
|
|
21
|
+
host?: string;
|
|
22
|
+
model?: string | null;
|
|
23
|
+
permission_mode?: string | null;
|
|
24
|
+
project_binding?: string | null;
|
|
25
|
+
};
|
|
26
|
+
instruction_bindings?: ReadonlyArray<{
|
|
27
|
+
scope: string;
|
|
28
|
+
source_ref: string;
|
|
29
|
+
digest: string;
|
|
30
|
+
}>;
|
|
31
|
+
completeness?: 'complete' | 'partial';
|
|
32
|
+
}
|
|
33
|
+
export interface JudgmentLifecycleAdoption {
|
|
34
|
+
schema_version: 'brainbase-judgment-adoption-v1' | 'brainbase-judgment-adoption-v2';
|
|
35
|
+
accepted_at: string;
|
|
36
|
+
request_text_digest: string;
|
|
37
|
+
request: JudgmentRequest;
|
|
38
|
+
receipt: JudgmentReceipt;
|
|
39
|
+
receipt_digest: string;
|
|
40
|
+
owner_audit: JudgmentOwnerAudit;
|
|
41
|
+
}
|
|
42
|
+
export interface JudgmentLifecycleEpisode {
|
|
43
|
+
schema_version: 'brainbase-judgment-episode-v1';
|
|
44
|
+
session_ref: string;
|
|
45
|
+
turn_id: string;
|
|
46
|
+
started_at: string;
|
|
47
|
+
request_text_digest: string;
|
|
48
|
+
initial_receipt_digest: string;
|
|
49
|
+
adoption: JudgmentLifecycleAdoption;
|
|
50
|
+
audit_contract: {
|
|
51
|
+
schema_version: 'brainbase-owner-audit-contract-v1';
|
|
52
|
+
repair_body_policy: 'preserve';
|
|
53
|
+
zero_call_display_line: string | null;
|
|
54
|
+
zero_call_display_line_digest: string | null;
|
|
55
|
+
};
|
|
56
|
+
episode_digest: string;
|
|
57
|
+
}
|
|
58
|
+
export interface JudgmentLifecycleToolEvent {
|
|
59
|
+
schema_version: 'brainbase-judgment-tool-event-v1';
|
|
60
|
+
session_ref: string;
|
|
61
|
+
turn_id: string;
|
|
62
|
+
sequence: number;
|
|
63
|
+
recorded_at: string;
|
|
64
|
+
tool_use_id: string;
|
|
65
|
+
tool_name: string;
|
|
66
|
+
tool_input: unknown;
|
|
67
|
+
tool_response: unknown;
|
|
68
|
+
audit_kind: 'routing' | 'search' | 'retrieval';
|
|
69
|
+
success: boolean;
|
|
70
|
+
satisfies: string[];
|
|
71
|
+
display_line: string;
|
|
72
|
+
event_fingerprint: string;
|
|
73
|
+
event_digest: string;
|
|
74
|
+
}
|
|
75
|
+
export interface JudgmentLifecycleFinalReceipt {
|
|
76
|
+
schema_version: 'brainbase-judgment-final-v1';
|
|
77
|
+
session_ref: string;
|
|
78
|
+
turn_id: string;
|
|
79
|
+
finalized_at: string;
|
|
80
|
+
initial_receipt_digest: string;
|
|
81
|
+
event_count: number;
|
|
82
|
+
event_set_digest: string;
|
|
83
|
+
answer_digest: string;
|
|
84
|
+
completion_status: 'complete';
|
|
85
|
+
owner_audit_complete: true;
|
|
86
|
+
owner_audit_line_count: number;
|
|
87
|
+
final_digest: string;
|
|
88
|
+
}
|
|
89
|
+
export interface JudgmentLifecycleBlock {
|
|
90
|
+
decision: 'block';
|
|
91
|
+
block_kind: 'audit_repair';
|
|
92
|
+
reason: string;
|
|
93
|
+
}
|
|
94
|
+
export interface JudgmentLifecycleJournalPort {
|
|
95
|
+
/** The implementation owns the turn lock, validation, and canonical writes. */
|
|
96
|
+
withLock<T>(identity: JudgmentLifecycleIdentity, operation: () => MaybePromise<T>): MaybePromise<T>;
|
|
97
|
+
readEpisode(identity: JudgmentLifecycleIdentity): MaybePromise<JudgmentLifecycleEpisode | null>;
|
|
98
|
+
writeEpisode(episode: JudgmentLifecycleEpisode): MaybePromise<void>;
|
|
99
|
+
readEvents(identity: JudgmentLifecycleIdentity): MaybePromise<readonly JudgmentLifecycleToolEvent[]>;
|
|
100
|
+
appendEvent(identity: JudgmentLifecycleIdentity, event: JudgmentLifecycleToolEvent): MaybePromise<void>;
|
|
101
|
+
readFinal(identity: JudgmentLifecycleIdentity): MaybePromise<JudgmentLifecycleFinalReceipt | null>;
|
|
102
|
+
writeFinal(final: JudgmentLifecycleFinalReceipt): MaybePromise<void>;
|
|
103
|
+
}
|
|
104
|
+
export interface JudgmentLifecycleResolverPort {
|
|
105
|
+
/** The host may bind this to the OSS local resolver or another approved resolver. */
|
|
106
|
+
resolve(request: JudgmentRequest): MaybePromise<JudgmentReceipt>;
|
|
107
|
+
}
|
|
108
|
+
export interface JudgmentLifecycleKnowledgeTransportPort {
|
|
109
|
+
/** Optional projection/queue boundary. It is never used to replace journal writes. */
|
|
110
|
+
enqueueFinalized(input: {
|
|
111
|
+
episode: JudgmentLifecycleEpisode;
|
|
112
|
+
final: JudgmentLifecycleFinalReceipt;
|
|
113
|
+
answer: string;
|
|
114
|
+
}): MaybePromise<void>;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Authorization, Graph access, and Task persistence intentionally stay outside
|
|
118
|
+
* this interface. A host can perform those actions before or after a lifecycle
|
|
119
|
+
* call and retain its existing tenant/authentication boundary.
|
|
120
|
+
*/
|
|
121
|
+
export interface JudgmentLifecyclePorts {
|
|
122
|
+
journal: JudgmentLifecycleJournalPort;
|
|
123
|
+
resolver: JudgmentLifecycleResolverPort;
|
|
124
|
+
transport?: JudgmentLifecycleKnowledgeTransportPort;
|
|
125
|
+
}
|
|
126
|
+
export interface JudgmentLifecycleCore {
|
|
127
|
+
start(input: {
|
|
128
|
+
request: JudgmentRequest;
|
|
129
|
+
accepted_at: string;
|
|
130
|
+
}): Promise<JudgmentLifecycleEpisode>;
|
|
131
|
+
recordToolEvent(input: {
|
|
132
|
+
identity: JudgmentLifecycleIdentity;
|
|
133
|
+
eligible: boolean;
|
|
134
|
+
recorded_at: string;
|
|
135
|
+
tool_use_id: string;
|
|
136
|
+
tool_name: string;
|
|
137
|
+
tool_input: unknown;
|
|
138
|
+
tool_response: unknown;
|
|
139
|
+
audit_kind: JudgmentLifecycleToolEvent['audit_kind'];
|
|
140
|
+
success: boolean;
|
|
141
|
+
satisfies: readonly string[];
|
|
142
|
+
display_line: string;
|
|
143
|
+
}): Promise<JudgmentLifecycleToolEvent | null>;
|
|
144
|
+
finalize(input: {
|
|
145
|
+
identity: JudgmentLifecycleIdentity;
|
|
146
|
+
finalized_at: string;
|
|
147
|
+
answer: string;
|
|
148
|
+
audit: {
|
|
149
|
+
complete: boolean;
|
|
150
|
+
line_count: number;
|
|
151
|
+
reason?: string;
|
|
152
|
+
};
|
|
153
|
+
}): Promise<JudgmentLifecycleFinalReceipt | JudgmentLifecycleBlock>;
|
|
154
|
+
}
|
|
155
|
+
export declare const ZERO_CALL_DISPLAY_LINE = "\uD83D\uDCDA Brainbase\u672A\u53C2\u7167: \u5FC5\u9808\u53C2\u7167\u306A\u3057\u30FB\u5B9F\u547C\u3073\u51FA\u30570\u56DE \u2713";
|
|
156
|
+
/**
|
|
157
|
+
* Normalizes host-provided context without discovering files, reading env, or
|
|
158
|
+
* deciding which source is authoritative. Those are host responsibilities.
|
|
159
|
+
*/
|
|
160
|
+
export declare function normalizeJudgmentRequest(input: JudgmentLifecycleRequestInput): JudgmentRequest;
|
|
161
|
+
/** Fails closed on an unbound or tampered request before the resolver is used. */
|
|
162
|
+
export declare function assertCanonicalJudgmentRequest(request: JudgmentRequest): JudgmentRequest;
|
|
163
|
+
/** Deterministically renders the owner-visible line used by the Host contract. */
|
|
164
|
+
export declare function buildJudgmentOwnerAudit(request: JudgmentRequest, receipt: JudgmentReceipt, { historicalExact }?: {
|
|
165
|
+
historicalExact?: boolean;
|
|
166
|
+
}): JudgmentOwnerAudit;
|
|
167
|
+
export declare function assertJudgmentOwnerAudit(value: unknown, receipt: JudgmentReceipt): JudgmentOwnerAudit;
|
|
168
|
+
/** Pure episode/adoption projection. The port owns writing and locking. */
|
|
169
|
+
export declare function createJudgmentEpisode(input: {
|
|
170
|
+
request: JudgmentRequest;
|
|
171
|
+
receipt: JudgmentReceipt;
|
|
172
|
+
accepted_at: string;
|
|
173
|
+
owner_audit?: JudgmentOwnerAudit;
|
|
174
|
+
}): JudgmentLifecycleEpisode;
|
|
175
|
+
/** Pure event projection. Tool eligibility and display rendering stay Host-owned. */
|
|
176
|
+
export declare function createJudgmentToolEvent(input: {
|
|
177
|
+
session_ref: string;
|
|
178
|
+
turn_id: string;
|
|
179
|
+
sequence: number;
|
|
180
|
+
recorded_at: string;
|
|
181
|
+
tool_use_id: string;
|
|
182
|
+
tool_name: string;
|
|
183
|
+
tool_input: unknown;
|
|
184
|
+
tool_response: unknown;
|
|
185
|
+
audit_kind: JudgmentLifecycleToolEvent['audit_kind'];
|
|
186
|
+
success: boolean;
|
|
187
|
+
satisfies: readonly string[];
|
|
188
|
+
display_line: string;
|
|
189
|
+
}): JudgmentLifecycleToolEvent;
|
|
190
|
+
/** Pure final receipt projection. Callers must provide successful audit validation. */
|
|
191
|
+
export declare function createJudgmentFinalReceipt(input: {
|
|
192
|
+
episode: JudgmentLifecycleEpisode;
|
|
193
|
+
events: readonly JudgmentLifecycleToolEvent[];
|
|
194
|
+
finalized_at: string;
|
|
195
|
+
answer: string;
|
|
196
|
+
owner_audit_line_count: number;
|
|
197
|
+
}): JudgmentLifecycleFinalReceipt;
|
|
198
|
+
export declare function createJudgmentLifecycleCore(ports: JudgmentLifecyclePorts): JudgmentLifecycleCore;
|
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
import { canonicalJson, sha256Hex } from './judgment-resolution-contract.js';
|
|
2
|
+
export const ZERO_CALL_DISPLAY_LINE = '📚 Brainbase未参照: 必須参照なし・実呼び出し0回 ✓';
|
|
3
|
+
function record(value) {
|
|
4
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
5
|
+
? value
|
|
6
|
+
: null;
|
|
7
|
+
}
|
|
8
|
+
function nonEmpty(value, code) {
|
|
9
|
+
if (typeof value !== 'string' || value.trim().length === 0)
|
|
10
|
+
throw new TypeError(code);
|
|
11
|
+
return value.trim();
|
|
12
|
+
}
|
|
13
|
+
function normalizeMessages(messages) {
|
|
14
|
+
return messages.flatMap((message, sequence) => {
|
|
15
|
+
if (!message || !['user', 'assistant'].includes(message.role)
|
|
16
|
+
|| typeof message.text !== 'string' || !message.text.trim())
|
|
17
|
+
return [];
|
|
18
|
+
return [{
|
|
19
|
+
sequence,
|
|
20
|
+
turn_id: typeof message.turn_id === 'string' ? message.turn_id : null,
|
|
21
|
+
role: message.role,
|
|
22
|
+
phase: typeof message.phase === 'string' ? message.phase : null,
|
|
23
|
+
text: message.text
|
|
24
|
+
}];
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Normalizes host-provided context without discovering files, reading env, or
|
|
29
|
+
* deciding which source is authoritative. Those are host responsibilities.
|
|
30
|
+
*/
|
|
31
|
+
export function normalizeJudgmentRequest(input) {
|
|
32
|
+
const request = nonEmpty(input.request, 'judgment_request_missing');
|
|
33
|
+
const turnId = nonEmpty(input.turn_id, 'judgment_turn_id_missing');
|
|
34
|
+
const sessionRef = nonEmpty(input.session_ref, 'judgment_session_ref_missing');
|
|
35
|
+
const messages = normalizeMessages(input.messages ?? []);
|
|
36
|
+
const currentMessages = messages.filter((message) => !(message.role === 'user' && message.turn_id === turnId && message.text === request));
|
|
37
|
+
const trailingMessage = currentMessages.at(-1);
|
|
38
|
+
if (trailingMessage?.role === 'user' && trailingMessage.turn_id === null && trailingMessage.text === request) {
|
|
39
|
+
trailingMessage.turn_id = turnId;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
currentMessages.push({
|
|
43
|
+
sequence: currentMessages.length,
|
|
44
|
+
turn_id: turnId,
|
|
45
|
+
role: 'user',
|
|
46
|
+
phase: null,
|
|
47
|
+
text: request
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
const contextWithoutDigest = {
|
|
51
|
+
schema_version: 'brainbase-conversation-context-v1',
|
|
52
|
+
session_ref: sessionRef,
|
|
53
|
+
messages: currentMessages.map((message, sequence) => ({ ...message, sequence })),
|
|
54
|
+
prior_receipts: [...(input.prior_receipts ?? [])],
|
|
55
|
+
runtime: {
|
|
56
|
+
host: input.runtime?.host ?? 'codex',
|
|
57
|
+
model: input.runtime?.model ?? null,
|
|
58
|
+
permission_mode: input.runtime?.permission_mode ?? null,
|
|
59
|
+
project_binding: input.runtime?.project_binding ?? null
|
|
60
|
+
},
|
|
61
|
+
instruction_bindings: [...(input.instruction_bindings ?? [])],
|
|
62
|
+
completeness: input.completeness ?? 'partial'
|
|
63
|
+
};
|
|
64
|
+
return {
|
|
65
|
+
request,
|
|
66
|
+
turn_id: turnId,
|
|
67
|
+
...(input.project_code ? { project_code: input.project_code } : {}),
|
|
68
|
+
conversation_context: {
|
|
69
|
+
...contextWithoutDigest,
|
|
70
|
+
source_digest: sha256Hex(canonicalJson(contextWithoutDigest))
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** Fails closed on an unbound or tampered request before the resolver is used. */
|
|
75
|
+
export function assertCanonicalJudgmentRequest(request) {
|
|
76
|
+
const requestText = nonEmpty(request.request, 'judgment_request_missing');
|
|
77
|
+
const turnId = nonEmpty(request.turn_id, 'judgment_turn_id_missing');
|
|
78
|
+
const context = record(request.conversation_context);
|
|
79
|
+
if (!context || context.schema_version !== 'brainbase-conversation-context-v1') {
|
|
80
|
+
throw new TypeError('judgment_context_invalid');
|
|
81
|
+
}
|
|
82
|
+
const sessionRef = nonEmpty(context.session_ref, 'judgment_session_ref_missing');
|
|
83
|
+
if (!Array.isArray(context.messages) || !Array.isArray(context.prior_receipts)
|
|
84
|
+
|| !record(context.runtime) || !Array.isArray(context.instruction_bindings)
|
|
85
|
+
|| !['complete', 'partial'].includes(String(context.completeness))
|
|
86
|
+
|| typeof context.source_digest !== 'string') {
|
|
87
|
+
throw new TypeError('judgment_context_invalid');
|
|
88
|
+
}
|
|
89
|
+
const { source_digest: _sourceDigest, ...contextWithoutDigest } = context;
|
|
90
|
+
if (sha256Hex(canonicalJson(contextWithoutDigest)) !== context.source_digest) {
|
|
91
|
+
throw new Error('judgment_context_digest_mismatch');
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
...request,
|
|
95
|
+
request: requestText,
|
|
96
|
+
turn_id: turnId,
|
|
97
|
+
conversation_context: {
|
|
98
|
+
...request.conversation_context,
|
|
99
|
+
session_ref: sessionRef
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const OWNER_EXCERPT_LIMIT = 26;
|
|
104
|
+
const PRIOR_EVIDENCE_SOURCES = new Set([
|
|
105
|
+
'prior_receipt',
|
|
106
|
+
'prior_message'
|
|
107
|
+
]);
|
|
108
|
+
function sanitizeOwnerExcerpt(value) {
|
|
109
|
+
const redacted = String(value ?? '')
|
|
110
|
+
.replace(/\b(token|api[_-]?key|secret|password)\s*=\s*[^\s]+/giu, '$1=[秘密情報]')
|
|
111
|
+
.replace(/\b(?:sk-[a-z0-9_-]{8,}|ghp_[a-z0-9_]{8,}|github_pat_[a-z0-9_]{8,}|xox[a-z]-[a-z0-9-]{8,}|AIza[a-z0-9_-]{8,})\b/giu, '[秘密情報]')
|
|
112
|
+
.replace(/[\u0000-\u001f\u007f]+/gu, ' ')
|
|
113
|
+
.replace(/[「」]/gu, ' ')
|
|
114
|
+
.replace(/\s+/gu, ' ')
|
|
115
|
+
.trim();
|
|
116
|
+
const points = Array.from(redacted);
|
|
117
|
+
return points.length > OWNER_EXCERPT_LIMIT
|
|
118
|
+
? `${points.slice(0, OWNER_EXCERPT_LIMIT).join('')}…`
|
|
119
|
+
: redacted;
|
|
120
|
+
}
|
|
121
|
+
function ownerEvidenceSource(request, receipt) {
|
|
122
|
+
const sourceTurnIds = Array.isArray(receipt.classification_evidence?.source_turn_ids)
|
|
123
|
+
? receipt.classification_evidence.source_turn_ids.filter((turnId) => typeof turnId === 'string')
|
|
124
|
+
: [];
|
|
125
|
+
const inherited = receipt.reconciliation_reasons?.includes('classification_inherited_from_prior_turn');
|
|
126
|
+
const prior = PRIOR_EVIDENCE_SOURCES.has(receipt.classification_evidence?.source) || inherited;
|
|
127
|
+
if (!prior)
|
|
128
|
+
return { sourceKind: 'current_request', text: request.request, sourceTurnIds };
|
|
129
|
+
const messages = Array.isArray(request.conversation_context?.messages)
|
|
130
|
+
? request.conversation_context.messages
|
|
131
|
+
: [];
|
|
132
|
+
const exact = [...messages].reverse().find((message) => (message.role === 'user'
|
|
133
|
+
&& message.turn_id !== request.turn_id
|
|
134
|
+
&& typeof message.turn_id === 'string'
|
|
135
|
+
&& sourceTurnIds.includes(message.turn_id)));
|
|
136
|
+
const fallback = sourceTurnIds.length === 0
|
|
137
|
+
? [...messages].reverse().find((message) => message.role === 'user' && message.turn_id !== request.turn_id)
|
|
138
|
+
: undefined;
|
|
139
|
+
return {
|
|
140
|
+
sourceKind: exact ? 'prior_turn' : fallback ? 'prior_turn_fallback' : 'prior_turn_unavailable',
|
|
141
|
+
text: exact?.text ?? fallback?.text ?? '',
|
|
142
|
+
sourceTurnIds
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function ownerDecision(receipt) {
|
|
146
|
+
if (receipt.selected_dag_ids?.includes('knowledge.v1'))
|
|
147
|
+
return 'Brainbase内検索が必要と判断';
|
|
148
|
+
const byIntent = {
|
|
149
|
+
implement: '実装依頼として継続',
|
|
150
|
+
investigate: '調査として確認',
|
|
151
|
+
diagnose: '原因診断として確認',
|
|
152
|
+
review: 'レビューとして確認',
|
|
153
|
+
design: '設計として検討',
|
|
154
|
+
answer: '質問として回答',
|
|
155
|
+
operate: '運用依頼として対応'
|
|
156
|
+
};
|
|
157
|
+
return receipt.classification?.intent
|
|
158
|
+
? byIntent[receipt.classification.intent] ?? '回答方針を確認'
|
|
159
|
+
: '回答方針を確認';
|
|
160
|
+
}
|
|
161
|
+
/** Deterministically renders the owner-visible line used by the Host contract. */
|
|
162
|
+
export function buildJudgmentOwnerAudit(request, receipt, { historicalExact = true } = {}) {
|
|
163
|
+
const evidence = ownerEvidenceSource(request, receipt);
|
|
164
|
+
const excerpt = sanitizeOwnerExcerpt(evidence.text);
|
|
165
|
+
let decision = ownerDecision(receipt);
|
|
166
|
+
let displayLine;
|
|
167
|
+
if (receipt.status === 'needs_clarification' || receipt.selected_dag_ids.includes('clarification.v1')) {
|
|
168
|
+
decision = '確認質問';
|
|
169
|
+
displayLine = `⚠️ 判断参照: 「${excerpt || '現在の依頼'}」の対象を特定できず → ${decision}`;
|
|
170
|
+
}
|
|
171
|
+
else if (evidence.sourceKind === 'prior_turn_unavailable') {
|
|
172
|
+
decision = '判断証跡を要確認';
|
|
173
|
+
displayLine = `⚠️ 判断参照: 参照元の会話を確認できず → ${decision}`;
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
const prefix = evidence.sourceKind.startsWith('prior_turn') ? '直前の' : '';
|
|
177
|
+
displayLine = `🧠 判断参照: ${prefix}「${excerpt || '現在の依頼'}」を参照 → ${decision} ✓`;
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
schema_version: 'brainbase-owner-audit-v1',
|
|
181
|
+
renderer_version: '1',
|
|
182
|
+
locale: 'ja-JP',
|
|
183
|
+
historical_exact: historicalExact,
|
|
184
|
+
source_receipt_digest: sha256Hex(canonicalJson(receipt)),
|
|
185
|
+
source_kind: evidence.sourceKind,
|
|
186
|
+
source_turn_ids: evidence.sourceTurnIds,
|
|
187
|
+
source_excerpt: excerpt,
|
|
188
|
+
decision,
|
|
189
|
+
display_line: displayLine,
|
|
190
|
+
text_digest: sha256Hex(displayLine)
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
export function assertJudgmentOwnerAudit(value, receipt) {
|
|
194
|
+
const ownerAudit = record(value);
|
|
195
|
+
if (!ownerAudit || ownerAudit.schema_version !== 'brainbase-owner-audit-v1') {
|
|
196
|
+
throw new Error('judgment_owner_audit_missing');
|
|
197
|
+
}
|
|
198
|
+
if (ownerAudit.source_receipt_digest !== sha256Hex(canonicalJson(receipt))) {
|
|
199
|
+
throw new Error('judgment_owner_audit_receipt_mismatch');
|
|
200
|
+
}
|
|
201
|
+
if (typeof ownerAudit.display_line !== 'string' || ownerAudit.text_digest !== sha256Hex(ownerAudit.display_line)) {
|
|
202
|
+
throw new Error('judgment_owner_audit_digest_mismatch');
|
|
203
|
+
}
|
|
204
|
+
return ownerAudit;
|
|
205
|
+
}
|
|
206
|
+
function assertReceiptForRequest(receipt, request) {
|
|
207
|
+
if (!record(receipt) || receipt.turn_id !== request.turn_id)
|
|
208
|
+
throw new Error('judgment_receipt_turn_mismatch');
|
|
209
|
+
if (receipt.request_digest !== sha256Hex(canonicalJson(request)))
|
|
210
|
+
throw new Error('judgment_receipt_request_mismatch');
|
|
211
|
+
if (receipt.context_digest !== sha256Hex(canonicalJson(request.conversation_context))) {
|
|
212
|
+
throw new Error('judgment_receipt_context_mismatch');
|
|
213
|
+
}
|
|
214
|
+
if (typeof receipt.plan_digest !== 'string' || receipt.plan_digest.length === 0) {
|
|
215
|
+
throw new Error('judgment_receipt_plan_missing');
|
|
216
|
+
}
|
|
217
|
+
return receipt;
|
|
218
|
+
}
|
|
219
|
+
/** Pure episode/adoption projection. The port owns writing and locking. */
|
|
220
|
+
export function createJudgmentEpisode(input) {
|
|
221
|
+
const request = assertCanonicalJudgmentRequest(input.request);
|
|
222
|
+
const receipt = assertReceiptForRequest(input.receipt, request);
|
|
223
|
+
const acceptedAt = nonEmpty(input.accepted_at, 'judgment_adoption_time_missing');
|
|
224
|
+
const ownerAudit = assertJudgmentOwnerAudit(input.owner_audit ?? buildJudgmentOwnerAudit(request, receipt), receipt);
|
|
225
|
+
const adoption = {
|
|
226
|
+
schema_version: 'brainbase-judgment-adoption-v2',
|
|
227
|
+
accepted_at: acceptedAt,
|
|
228
|
+
request_text_digest: sha256Hex(request.request),
|
|
229
|
+
request,
|
|
230
|
+
receipt,
|
|
231
|
+
receipt_digest: sha256Hex(canonicalJson(receipt)),
|
|
232
|
+
owner_audit: ownerAudit
|
|
233
|
+
};
|
|
234
|
+
const initialReceiptDigest = sha256Hex(canonicalJson(receipt));
|
|
235
|
+
const episodeWithoutDigest = {
|
|
236
|
+
schema_version: 'brainbase-judgment-episode-v1',
|
|
237
|
+
session_ref: request.conversation_context.session_ref,
|
|
238
|
+
turn_id: request.turn_id,
|
|
239
|
+
started_at: acceptedAt,
|
|
240
|
+
request_text_digest: sha256Hex(request.request),
|
|
241
|
+
initial_receipt_digest: initialReceiptDigest,
|
|
242
|
+
adoption,
|
|
243
|
+
audit_contract: {
|
|
244
|
+
schema_version: 'brainbase-owner-audit-contract-v1',
|
|
245
|
+
repair_body_policy: 'preserve',
|
|
246
|
+
zero_call_display_line: receipt.selected_dag_ids.includes('knowledge.v1') ? null : ZERO_CALL_DISPLAY_LINE,
|
|
247
|
+
zero_call_display_line_digest: receipt.selected_dag_ids.includes('knowledge.v1')
|
|
248
|
+
? null
|
|
249
|
+
: sha256Hex(ZERO_CALL_DISPLAY_LINE)
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
return {
|
|
253
|
+
...episodeWithoutDigest,
|
|
254
|
+
episode_digest: sha256Hex(canonicalJson(episodeWithoutDigest))
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
function eventFingerprint(input) {
|
|
258
|
+
return sha256Hex(canonicalJson(input));
|
|
259
|
+
}
|
|
260
|
+
/** Pure event projection. Tool eligibility and display rendering stay Host-owned. */
|
|
261
|
+
export function createJudgmentToolEvent(input) {
|
|
262
|
+
const sessionRef = nonEmpty(input.session_ref, 'judgment_session_ref_missing');
|
|
263
|
+
const turnId = nonEmpty(input.turn_id, 'judgment_turn_id_missing');
|
|
264
|
+
const toolUseId = nonEmpty(input.tool_use_id, 'judgment_tool_use_id_missing');
|
|
265
|
+
const toolName = nonEmpty(input.tool_name, 'judgment_tool_name_missing');
|
|
266
|
+
const recordedAt = nonEmpty(input.recorded_at, 'judgment_tool_event_time_missing');
|
|
267
|
+
if (!Number.isSafeInteger(input.sequence) || input.sequence < 1)
|
|
268
|
+
throw new TypeError('judgment_tool_event_sequence_invalid');
|
|
269
|
+
if (typeof input.display_line !== 'string')
|
|
270
|
+
throw new TypeError('judgment_tool_display_line_invalid');
|
|
271
|
+
const toolInput = input.tool_input ?? null;
|
|
272
|
+
const toolResponse = input.tool_response ?? null;
|
|
273
|
+
const fingerprint = eventFingerprint({
|
|
274
|
+
tool_use_id: toolUseId,
|
|
275
|
+
tool_name: toolName,
|
|
276
|
+
tool_input: toolInput,
|
|
277
|
+
tool_response: toolResponse
|
|
278
|
+
});
|
|
279
|
+
const eventWithoutDigest = {
|
|
280
|
+
schema_version: 'brainbase-judgment-tool-event-v1',
|
|
281
|
+
session_ref: sessionRef,
|
|
282
|
+
turn_id: turnId,
|
|
283
|
+
sequence: input.sequence,
|
|
284
|
+
recorded_at: recordedAt,
|
|
285
|
+
tool_use_id: toolUseId,
|
|
286
|
+
tool_name: toolName,
|
|
287
|
+
tool_input: toolInput,
|
|
288
|
+
tool_response: toolResponse,
|
|
289
|
+
audit_kind: input.audit_kind,
|
|
290
|
+
success: input.success,
|
|
291
|
+
satisfies: [...input.satisfies],
|
|
292
|
+
display_line: input.display_line,
|
|
293
|
+
event_fingerprint: fingerprint
|
|
294
|
+
};
|
|
295
|
+
return {
|
|
296
|
+
...eventWithoutDigest,
|
|
297
|
+
event_digest: sha256Hex(canonicalJson(eventWithoutDigest))
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
/** Pure final receipt projection. Callers must provide successful audit validation. */
|
|
301
|
+
export function createJudgmentFinalReceipt(input) {
|
|
302
|
+
if (!Number.isSafeInteger(input.owner_audit_line_count) || input.owner_audit_line_count < 1) {
|
|
303
|
+
throw new TypeError('judgment_owner_audit_line_count_invalid');
|
|
304
|
+
}
|
|
305
|
+
const finalizedAt = nonEmpty(input.finalized_at, 'judgment_finalized_time_missing');
|
|
306
|
+
const eventSetDigest = sha256Hex(canonicalJson(input.events));
|
|
307
|
+
const finalWithoutDigest = {
|
|
308
|
+
schema_version: 'brainbase-judgment-final-v1',
|
|
309
|
+
session_ref: input.episode.session_ref,
|
|
310
|
+
turn_id: input.episode.turn_id,
|
|
311
|
+
finalized_at: finalizedAt,
|
|
312
|
+
initial_receipt_digest: input.episode.initial_receipt_digest,
|
|
313
|
+
event_count: input.events.length,
|
|
314
|
+
event_set_digest: eventSetDigest,
|
|
315
|
+
answer_digest: sha256Hex(input.answer),
|
|
316
|
+
completion_status: 'complete',
|
|
317
|
+
owner_audit_complete: true,
|
|
318
|
+
owner_audit_line_count: input.owner_audit_line_count
|
|
319
|
+
};
|
|
320
|
+
return {
|
|
321
|
+
...finalWithoutDigest,
|
|
322
|
+
final_digest: sha256Hex(canonicalJson(finalWithoutDigest))
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
function assertIdentity(episode, identity) {
|
|
326
|
+
if (episode.session_ref !== identity.session_ref || episode.turn_id !== identity.turn_id) {
|
|
327
|
+
throw new Error('judgment_episode_binding_mismatch');
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
function assertEventIdentity(event, identity) {
|
|
331
|
+
if (event.session_ref !== identity.session_ref || event.turn_id !== identity.turn_id) {
|
|
332
|
+
throw new Error('judgment_tool_event_binding_mismatch');
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
function finalMatches(final, episode, events, answer) {
|
|
336
|
+
return final.session_ref === episode.session_ref
|
|
337
|
+
&& final.turn_id === episode.turn_id
|
|
338
|
+
&& final.initial_receipt_digest === episode.initial_receipt_digest
|
|
339
|
+
&& final.event_count === events.length
|
|
340
|
+
&& final.event_set_digest === sha256Hex(canonicalJson(events))
|
|
341
|
+
&& final.answer_digest === sha256Hex(answer);
|
|
342
|
+
}
|
|
343
|
+
function eventInputFingerprint(input) {
|
|
344
|
+
return eventFingerprint({
|
|
345
|
+
tool_use_id: nonEmpty(input.tool_use_id, 'judgment_tool_use_id_missing'),
|
|
346
|
+
tool_name: nonEmpty(input.tool_name, 'judgment_tool_name_missing'),
|
|
347
|
+
tool_input: input.tool_input ?? null,
|
|
348
|
+
tool_response: input.tool_response ?? null
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
export function createJudgmentLifecycleCore(ports) {
|
|
352
|
+
return {
|
|
353
|
+
async start(input) {
|
|
354
|
+
const request = assertCanonicalJudgmentRequest(input.request);
|
|
355
|
+
const identity = {
|
|
356
|
+
session_ref: request.conversation_context.session_ref,
|
|
357
|
+
turn_id: request.turn_id
|
|
358
|
+
};
|
|
359
|
+
return ports.journal.withLock(identity, async () => {
|
|
360
|
+
const existing = await ports.journal.readEpisode(identity);
|
|
361
|
+
if (existing) {
|
|
362
|
+
assertIdentity(existing, identity);
|
|
363
|
+
const existingRequest = assertCanonicalJudgmentRequest(existing.adoption.request);
|
|
364
|
+
const existingReceipt = assertReceiptForRequest(existing.adoption.receipt, existingRequest);
|
|
365
|
+
if (sha256Hex(canonicalJson(existingRequest)) !== sha256Hex(canonicalJson(request))) {
|
|
366
|
+
throw new Error('judgment_episode_request_conflict');
|
|
367
|
+
}
|
|
368
|
+
if (existing.request_text_digest !== sha256Hex(existingRequest.request)
|
|
369
|
+
|| existing.initial_receipt_digest !== sha256Hex(canonicalJson(existingReceipt))) {
|
|
370
|
+
throw new Error('judgment_episode_binding_mismatch');
|
|
371
|
+
}
|
|
372
|
+
return existing;
|
|
373
|
+
}
|
|
374
|
+
const receipt = assertReceiptForRequest(await ports.resolver.resolve(request), request);
|
|
375
|
+
const episode = createJudgmentEpisode({
|
|
376
|
+
request,
|
|
377
|
+
receipt,
|
|
378
|
+
accepted_at: input.accepted_at
|
|
379
|
+
});
|
|
380
|
+
await ports.journal.writeEpisode(episode);
|
|
381
|
+
return episode;
|
|
382
|
+
});
|
|
383
|
+
},
|
|
384
|
+
async recordToolEvent(input) {
|
|
385
|
+
const identity = input.identity;
|
|
386
|
+
if (!input.eligible)
|
|
387
|
+
return null;
|
|
388
|
+
return ports.journal.withLock(identity, async () => {
|
|
389
|
+
const episode = await ports.journal.readEpisode(identity);
|
|
390
|
+
if (!episode)
|
|
391
|
+
throw new Error('judgment_orphan_post_tool_use');
|
|
392
|
+
assertIdentity(episode, identity);
|
|
393
|
+
const existingFinal = await ports.journal.readFinal(identity);
|
|
394
|
+
if (existingFinal)
|
|
395
|
+
throw new Error('judgment_episode_already_finalized');
|
|
396
|
+
const events = [...await ports.journal.readEvents(identity)];
|
|
397
|
+
const fingerprint = eventInputFingerprint(input);
|
|
398
|
+
const replay = events.find((event) => event.tool_use_id === input.tool_use_id);
|
|
399
|
+
if (replay) {
|
|
400
|
+
assertEventIdentity(replay, identity);
|
|
401
|
+
if (replay.event_fingerprint !== fingerprint)
|
|
402
|
+
throw new Error('judgment_tool_event_conflict');
|
|
403
|
+
return replay;
|
|
404
|
+
}
|
|
405
|
+
const event = createJudgmentToolEvent({
|
|
406
|
+
session_ref: identity.session_ref,
|
|
407
|
+
turn_id: identity.turn_id,
|
|
408
|
+
sequence: events.length + 1,
|
|
409
|
+
recorded_at: input.recorded_at,
|
|
410
|
+
tool_use_id: input.tool_use_id,
|
|
411
|
+
tool_name: input.tool_name,
|
|
412
|
+
tool_input: input.tool_input,
|
|
413
|
+
tool_response: input.tool_response,
|
|
414
|
+
audit_kind: input.audit_kind,
|
|
415
|
+
success: input.success,
|
|
416
|
+
satisfies: input.satisfies,
|
|
417
|
+
display_line: input.display_line
|
|
418
|
+
});
|
|
419
|
+
await ports.journal.appendEvent(identity, event);
|
|
420
|
+
return event;
|
|
421
|
+
});
|
|
422
|
+
},
|
|
423
|
+
async finalize(input) {
|
|
424
|
+
const identity = input.identity;
|
|
425
|
+
return ports.journal.withLock(identity, async () => {
|
|
426
|
+
const episode = await ports.journal.readEpisode(identity);
|
|
427
|
+
if (!episode)
|
|
428
|
+
throw new Error('judgment_orphan_stop');
|
|
429
|
+
assertIdentity(episode, identity);
|
|
430
|
+
const events = [...await ports.journal.readEvents(identity)];
|
|
431
|
+
events.forEach((event) => assertEventIdentity(event, identity));
|
|
432
|
+
const existing = await ports.journal.readFinal(identity);
|
|
433
|
+
if (existing) {
|
|
434
|
+
if (!finalMatches(existing, episode, events, input.answer))
|
|
435
|
+
throw new Error('judgment_final_conflict');
|
|
436
|
+
if (ports.transport) {
|
|
437
|
+
await ports.transport.enqueueFinalized({ episode, final: existing, answer: input.answer });
|
|
438
|
+
}
|
|
439
|
+
return existing;
|
|
440
|
+
}
|
|
441
|
+
if (!input.audit.complete) {
|
|
442
|
+
return {
|
|
443
|
+
decision: 'block',
|
|
444
|
+
block_kind: 'audit_repair',
|
|
445
|
+
reason: input.audit.reason
|
|
446
|
+
?? 'Brainbase judgment episodeを完了する前に監査行と回答本文を確認してください。'
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
const final = createJudgmentFinalReceipt({
|
|
450
|
+
episode,
|
|
451
|
+
events,
|
|
452
|
+
finalized_at: input.finalized_at,
|
|
453
|
+
answer: input.answer,
|
|
454
|
+
owner_audit_line_count: input.audit.line_count
|
|
455
|
+
});
|
|
456
|
+
await ports.journal.writeFinal(final);
|
|
457
|
+
if (ports.transport) {
|
|
458
|
+
await ports.transport.enqueueFinalized({ episode, final, answer: input.answer });
|
|
459
|
+
}
|
|
460
|
+
return final;
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
}
|