@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,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Portable execution outcome contract shared by a judgment Host and its
|
|
3
|
+
* adapters. The contract deliberately carries references only; transcript,
|
|
4
|
+
* process, Task, and delivery details stay at the Host boundary.
|
|
5
|
+
*/
|
|
6
|
+
export const JUDGMENT_EXECUTION_OUTCOME_SCHEMA_VERSION = 'judgment_execution_outcome.v1';
|
|
7
|
+
const FIELDS = new Set([
|
|
8
|
+
'schema_version', 'host', 'execution_id', 'turn_id', 'scope', 'status', 'stage',
|
|
9
|
+
'failure', 'resume_from', 'evidence'
|
|
10
|
+
]);
|
|
11
|
+
const HOST_FIELDS = new Set(['type', 'adapter_id', 'adapter_version']);
|
|
12
|
+
const FAILURE_FIELDS = new Set(['code', 'summary', 'upstream_code', 'retryable']);
|
|
13
|
+
const EVIDENCE_FIELDS = new Set(['state', 'refs']);
|
|
14
|
+
const STATUSES = new Set(['completed', 'partial', 'failed', 'blocked', 'unknown']);
|
|
15
|
+
const STAGES = new Set(['open', 'resolve', 'execute', 'finalize', 'deliver', 'readback']);
|
|
16
|
+
const EVIDENCE_STATES = new Set(['confirmed', 'unconfirmed', 'no_data']);
|
|
17
|
+
const HOST_TYPES = new Set(['codex', 'claude-code']);
|
|
18
|
+
const SCOPES = new Set(['host_turn', 'external_effect']);
|
|
19
|
+
const ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
20
|
+
function object(value, field) {
|
|
21
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
22
|
+
throw new TypeError(`${field} must be an object`);
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
function exact(value, allowed, field) {
|
|
27
|
+
const record = object(value, field);
|
|
28
|
+
for (const key of Object.keys(record)) {
|
|
29
|
+
if (!allowed.has(key))
|
|
30
|
+
throw new TypeError(`${field}.${key} is not allowed`);
|
|
31
|
+
}
|
|
32
|
+
return record;
|
|
33
|
+
}
|
|
34
|
+
function id(value, field) {
|
|
35
|
+
if (typeof value !== 'string' || !ID.test(value))
|
|
36
|
+
throw new TypeError(`${field} is invalid`);
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
function text(value, field) {
|
|
40
|
+
if (typeof value !== 'string'
|
|
41
|
+
|| !value.trim()
|
|
42
|
+
|| value.length > 1000
|
|
43
|
+
|| /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/u.test(value)) {
|
|
44
|
+
throw new TypeError(`${field} is invalid`);
|
|
45
|
+
}
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
function ref(value, field) {
|
|
49
|
+
if (typeof value !== 'string'
|
|
50
|
+
|| !value.trim()
|
|
51
|
+
|| value.length > 512
|
|
52
|
+
|| /[\u0000-\u001f\u007f]/u.test(value)) {
|
|
53
|
+
throw new TypeError(`${field} is invalid`);
|
|
54
|
+
}
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
function evidenceRef(value, field) {
|
|
58
|
+
const normalized = ref(value, field);
|
|
59
|
+
if (normalized.startsWith('/') || /^file:/iu.test(normalized)) {
|
|
60
|
+
throw new TypeError(`${field} is invalid`);
|
|
61
|
+
}
|
|
62
|
+
return normalized;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Validate and normalize a host execution outcome.
|
|
66
|
+
*
|
|
67
|
+
* The function intentionally rejects unknown keys and unsafe filesystem
|
|
68
|
+
* references so adapters cannot accidentally turn private runtime state into
|
|
69
|
+
* a public contract.
|
|
70
|
+
*/
|
|
71
|
+
export function normalizeJudgmentExecutionOutcome(input) {
|
|
72
|
+
const outcome = exact(input, FIELDS, 'outcome');
|
|
73
|
+
if (outcome.schema_version !== JUDGMENT_EXECUTION_OUTCOME_SCHEMA_VERSION) {
|
|
74
|
+
throw new TypeError('schema_version is invalid');
|
|
75
|
+
}
|
|
76
|
+
const hostInput = exact(outcome.host, HOST_FIELDS, 'host');
|
|
77
|
+
const host = {
|
|
78
|
+
type: id(hostInput.type, 'host.type'),
|
|
79
|
+
adapter_id: id(hostInput.adapter_id, 'host.adapter_id'),
|
|
80
|
+
adapter_version: id(hostInput.adapter_version, 'host.adapter_version')
|
|
81
|
+
};
|
|
82
|
+
if (!HOST_TYPES.has(host.type))
|
|
83
|
+
throw new TypeError('host.type is not registered');
|
|
84
|
+
if (!SCOPES.has(outcome.scope))
|
|
85
|
+
throw new TypeError('scope is invalid');
|
|
86
|
+
if (!STATUSES.has(outcome.status))
|
|
87
|
+
throw new TypeError('status is invalid');
|
|
88
|
+
if (!STAGES.has(outcome.stage))
|
|
89
|
+
throw new TypeError('stage is invalid');
|
|
90
|
+
const evidenceInput = exact(outcome.evidence, EVIDENCE_FIELDS, 'evidence');
|
|
91
|
+
if (!EVIDENCE_STATES.has(evidenceInput.state)) {
|
|
92
|
+
throw new TypeError('evidence.state is invalid');
|
|
93
|
+
}
|
|
94
|
+
if (!Array.isArray(evidenceInput.refs))
|
|
95
|
+
throw new TypeError('evidence.refs is invalid');
|
|
96
|
+
let evidenceRefs;
|
|
97
|
+
try {
|
|
98
|
+
evidenceRefs = evidenceInput.refs.map((reference, index) => evidenceRef(reference, `evidence.refs[${index}]`));
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
throw new TypeError('evidence.refs is invalid');
|
|
102
|
+
}
|
|
103
|
+
const status = outcome.status;
|
|
104
|
+
const scope = outcome.scope;
|
|
105
|
+
const stage = outcome.stage;
|
|
106
|
+
const evidenceState = evidenceInput.state;
|
|
107
|
+
const incomplete = status !== 'completed';
|
|
108
|
+
if (incomplete && (!outcome.failure || !outcome.resume_from)) {
|
|
109
|
+
throw new TypeError('failure and resume_from are required for a non-completed outcome');
|
|
110
|
+
}
|
|
111
|
+
let failure = null;
|
|
112
|
+
if (outcome.failure != null) {
|
|
113
|
+
const failureInput = exact(outcome.failure, FAILURE_FIELDS, 'failure');
|
|
114
|
+
failure = {
|
|
115
|
+
code: id(failureInput.code, 'failure.code'),
|
|
116
|
+
summary: text(failureInput.summary, 'failure.summary'),
|
|
117
|
+
upstream_code: failureInput.upstream_code == null
|
|
118
|
+
? null
|
|
119
|
+
: id(failureInput.upstream_code, 'failure.upstream_code'),
|
|
120
|
+
retryable: failureInput.retryable
|
|
121
|
+
};
|
|
122
|
+
if (typeof failure.retryable !== 'boolean')
|
|
123
|
+
throw new TypeError('failure.retryable is invalid');
|
|
124
|
+
}
|
|
125
|
+
if (status === 'completed' && (failure || outcome.resume_from != null)) {
|
|
126
|
+
throw new TypeError('completed outcome cannot have failure or resume_from');
|
|
127
|
+
}
|
|
128
|
+
if (status === 'completed' && (evidenceState !== 'confirmed' || evidenceRefs.length === 0)) {
|
|
129
|
+
throw new TypeError('completed outcome requires confirmed evidence refs');
|
|
130
|
+
}
|
|
131
|
+
if (status === 'completed' && scope === 'external_effect' && stage !== 'readback') {
|
|
132
|
+
throw new TypeError('completed external effect requires readback');
|
|
133
|
+
}
|
|
134
|
+
if (status === 'completed' && scope === 'host_turn' && stage !== 'finalize') {
|
|
135
|
+
throw new TypeError('completed host turn requires finalize');
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
schema_version: JUDGMENT_EXECUTION_OUTCOME_SCHEMA_VERSION,
|
|
139
|
+
host,
|
|
140
|
+
execution_id: ref(outcome.execution_id, 'execution_id'),
|
|
141
|
+
turn_id: ref(outcome.turn_id, 'turn_id'),
|
|
142
|
+
scope,
|
|
143
|
+
status,
|
|
144
|
+
stage,
|
|
145
|
+
failure,
|
|
146
|
+
resume_from: outcome.resume_from == null ? null : id(outcome.resume_from, 'resume_from'),
|
|
147
|
+
evidence: { state: evidenceState, refs: evidenceRefs }
|
|
148
|
+
};
|
|
149
|
+
}
|
package/dist/judgment-host.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type JudgmentAutonomyEvaluation, type JudgmentAutonomyResolver } from './judgment-autonomy.js';
|
|
2
|
+
import type { JudgmentKnowledgeEventSink } from './judgment-event-outbox.js';
|
|
2
3
|
import { canonicalJson } from './judgment-resolution-contract.js';
|
|
3
4
|
type Environment = Record<string, string | undefined>;
|
|
4
5
|
export interface ConversationMessage {
|
|
@@ -59,6 +60,16 @@ export interface JudgmentNodeDefinition {
|
|
|
59
60
|
kind: 'common' | 'judgment' | 'constraint' | 'clarification';
|
|
60
61
|
instruction: string;
|
|
61
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* The resolver adapter binding accepted by the Host. The adapter identity is
|
|
65
|
+
* supplied by the Host configuration and is never inferred from a receipt.
|
|
66
|
+
*/
|
|
67
|
+
export interface JudgmentHostBinding {
|
|
68
|
+
adapter_id: string;
|
|
69
|
+
adapter_version: string;
|
|
70
|
+
status: 'managed';
|
|
71
|
+
enforcement_level: 'host_contract';
|
|
72
|
+
}
|
|
62
73
|
export interface JudgmentReceipt {
|
|
63
74
|
resolution_id: string;
|
|
64
75
|
resolved_at: string;
|
|
@@ -69,12 +80,7 @@ export interface JudgmentReceipt {
|
|
|
69
80
|
status: 'resolved' | 'needs_clarification';
|
|
70
81
|
runtime_version: string;
|
|
71
82
|
manifest_digest: string;
|
|
72
|
-
host_binding:
|
|
73
|
-
adapter_id: 'brainbase-local';
|
|
74
|
-
adapter_version: '1';
|
|
75
|
-
status: 'managed';
|
|
76
|
-
enforcement_level: 'host_contract';
|
|
77
|
-
};
|
|
83
|
+
host_binding: JudgmentHostBinding;
|
|
78
84
|
project_code?: string;
|
|
79
85
|
classification: JudgmentClassification | null;
|
|
80
86
|
classification_evidence: {
|
|
@@ -106,10 +112,14 @@ export interface JudgmentHookOutput {
|
|
|
106
112
|
export interface JudgmentHostOptions {
|
|
107
113
|
env?: Environment;
|
|
108
114
|
resolver?: (request: JudgmentRequest) => Promise<JudgmentReceipt>;
|
|
115
|
+
/** Expected resolver binding; defaults to the built-in local adapter. */
|
|
116
|
+
expectedHostBinding?: JudgmentHostBinding;
|
|
109
117
|
autonomyResolver?: JudgmentAutonomyResolver;
|
|
110
118
|
autonomyMode?: JudgmentAutonomyMode;
|
|
111
119
|
autonomyCanaryProjects?: readonly string[];
|
|
112
120
|
trustedConversationMessages?: ConversationMessage[];
|
|
121
|
+
/** Optional adapter-owned queue. The default Host lifecycle remains unchanged. */
|
|
122
|
+
knowledgeEventSink?: JudgmentKnowledgeEventSink;
|
|
113
123
|
}
|
|
114
124
|
export type JudgmentAutonomyMode = 'off' | 'canary' | 'on';
|
|
115
125
|
interface LocalResolverOptions {
|
|
@@ -206,9 +216,10 @@ export interface JudgmentStopBlock {
|
|
|
206
216
|
reason: string;
|
|
207
217
|
}
|
|
208
218
|
export { canonicalJson };
|
|
209
|
-
export declare function buildJudgmentRequest(payload: JudgmentHookPayload, { env, trustedConversationMessages }?: {
|
|
219
|
+
export declare function buildJudgmentRequest(payload: JudgmentHookPayload, { env, trustedConversationMessages, expectedHostBinding }?: {
|
|
210
220
|
env?: Environment;
|
|
211
221
|
trustedConversationMessages?: ConversationMessage[];
|
|
222
|
+
expectedHostBinding?: JudgmentHostBinding;
|
|
212
223
|
}): JudgmentRequest;
|
|
213
224
|
export declare function resolveLocalJudgment(request: JudgmentRequest, options?: LocalResolverOptions): Promise<JudgmentReceipt>;
|
|
214
225
|
export declare function buildOwnerAudit(request: JudgmentRequest, receipt: JudgmentReceipt, { historicalExact }?: {
|
package/dist/judgment-host.js
CHANGED
|
@@ -3,7 +3,14 @@ import { closeSync, constants as fsConstants, fsyncSync, linkSync, mkdirSync, op
|
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { basename, delimiter, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
5
5
|
import { evaluateJudgmentAutonomy, renderJudgmentAutonomyContinuation } from './judgment-autonomy.js';
|
|
6
|
+
import { toKnowledgeEventFromJudgmentEpisode } from './judgment-event-adapter.js';
|
|
6
7
|
import { canonicalJson } from './judgment-resolution-contract.js';
|
|
8
|
+
const DEFAULT_HOST_BINDING = Object.freeze({
|
|
9
|
+
adapter_id: 'brainbase-local',
|
|
10
|
+
adapter_version: '1',
|
|
11
|
+
status: 'managed',
|
|
12
|
+
enforcement_level: 'host_contract'
|
|
13
|
+
});
|
|
7
14
|
const RUNTIME_VERSION = 'portable-judgment-runtime-1.0.0';
|
|
8
15
|
const ZERO_CALL_DISPLAY_LINE = '📚 Brainbase未参照: 必須参照なし・実呼び出し0回 ✓';
|
|
9
16
|
const PORTABLE_KNOWLEDGE_TOOLS = {
|
|
@@ -71,6 +78,24 @@ function sha256(value) {
|
|
|
71
78
|
function record(value) {
|
|
72
79
|
return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
73
80
|
}
|
|
81
|
+
function normalizeExpectedHostBinding(value) {
|
|
82
|
+
const candidate = value === undefined ? DEFAULT_HOST_BINDING : record(value);
|
|
83
|
+
if (!candidate
|
|
84
|
+
|| typeof candidate.adapter_id !== 'string'
|
|
85
|
+
|| candidate.adapter_id.trim().length === 0
|
|
86
|
+
|| typeof candidate.adapter_version !== 'string'
|
|
87
|
+
|| candidate.adapter_version.trim().length === 0
|
|
88
|
+
|| candidate.status !== 'managed'
|
|
89
|
+
|| candidate.enforcement_level !== 'host_contract') {
|
|
90
|
+
throw new Error('judgment_host_binding_config_invalid');
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
adapter_id: candidate.adapter_id,
|
|
94
|
+
adapter_version: candidate.adapter_version,
|
|
95
|
+
status: 'managed',
|
|
96
|
+
enforcement_level: 'host_contract'
|
|
97
|
+
};
|
|
98
|
+
}
|
|
74
99
|
function contentText(content) {
|
|
75
100
|
if (typeof content === 'string')
|
|
76
101
|
return content;
|
|
@@ -298,7 +323,8 @@ function acceptedProjection(receipt) {
|
|
|
298
323
|
selected_dag_ids: receipt.selected_dag_ids
|
|
299
324
|
};
|
|
300
325
|
}
|
|
301
|
-
function readAdoptionEntry(path) {
|
|
326
|
+
function readAdoptionEntry(path, expectedHostBinding = DEFAULT_HOST_BINDING) {
|
|
327
|
+
const expectedBinding = normalizeExpectedHostBinding(expectedHostBinding);
|
|
302
328
|
let parsed;
|
|
303
329
|
try {
|
|
304
330
|
parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
@@ -326,7 +352,7 @@ function readAdoptionEntry(path) {
|
|
|
326
352
|
if (entry.request_text_digest !== sha256(request.request)) {
|
|
327
353
|
throw new Error(`judgment_journal_request_digest_mismatch:${basename(path)}`);
|
|
328
354
|
}
|
|
329
|
-
const verifiedReceipt = verifyReceipt(receipt, request);
|
|
355
|
+
const verifiedReceipt = verifyReceipt(receipt, request, expectedBinding);
|
|
330
356
|
if (entry.schema_version === 'brainbase-judgment-adoption-v2') {
|
|
331
357
|
if (typeof entry.receipt_digest !== 'string'
|
|
332
358
|
|| entry.receipt_digest !== sha256(canonicalJson(verifiedReceipt))) {
|
|
@@ -351,7 +377,7 @@ function readAdoptionEntry(path) {
|
|
|
351
377
|
owner_audit: buildOwnerAudit(request, verifiedReceipt, { historicalExact: false })
|
|
352
378
|
};
|
|
353
379
|
}
|
|
354
|
-
function priorReceipts(sessionRef, currentTurnId, env) {
|
|
380
|
+
function priorReceipts(sessionRef, currentTurnId, env, expectedHostBinding = DEFAULT_HOST_BINDING) {
|
|
355
381
|
const { directory, target } = journalPaths(sessionRef, currentTurnId, env);
|
|
356
382
|
let names;
|
|
357
383
|
try {
|
|
@@ -363,7 +389,7 @@ function priorReceipts(sessionRef, currentTurnId, env) {
|
|
|
363
389
|
throw error;
|
|
364
390
|
}
|
|
365
391
|
return names.filter((name) => name !== basename(target)).map((name) => {
|
|
366
|
-
const entry = readAdoptionEntry(join(directory, name));
|
|
392
|
+
const entry = readAdoptionEntry(join(directory, name), expectedHostBinding);
|
|
367
393
|
if (entry.request.conversation_context.session_ref !== sessionRef) {
|
|
368
394
|
throw new Error(`judgment_journal_session_mismatch:${name}`);
|
|
369
395
|
}
|
|
@@ -371,7 +397,7 @@ function priorReceipts(sessionRef, currentTurnId, env) {
|
|
|
371
397
|
}).sort((left, right) => left.accepted_at.localeCompare(right.accepted_at))
|
|
372
398
|
.map((entry) => entry.projection);
|
|
373
399
|
}
|
|
374
|
-
export function buildJudgmentRequest(payload, { env = process.env, trustedConversationMessages } = {}) {
|
|
400
|
+
export function buildJudgmentRequest(payload, { env = process.env, trustedConversationMessages, expectedHostBinding } = {}) {
|
|
375
401
|
const request = typeof payload.prompt === 'string' && payload.prompt.trim() ? payload.prompt : '';
|
|
376
402
|
const turnId = typeof payload.turn_id === 'string' ? payload.turn_id.trim() : '';
|
|
377
403
|
const sessionId = typeof payload.session_id === 'string' ? payload.session_id : '';
|
|
@@ -395,7 +421,7 @@ export function buildJudgmentRequest(payload, { env = process.env, trustedConver
|
|
|
395
421
|
schema_version: 'brainbase-conversation-context-v1',
|
|
396
422
|
session_ref: sessionRef,
|
|
397
423
|
messages: messages.map((message, sequence) => ({ ...message, sequence })),
|
|
398
|
-
prior_receipts: priorReceipts(sessionRef, turnId, env),
|
|
424
|
+
prior_receipts: priorReceipts(sessionRef, turnId, env, expectedHostBinding),
|
|
399
425
|
runtime: {
|
|
400
426
|
host: 'codex',
|
|
401
427
|
model: typeof payload.model === 'string' && payload.model ? payload.model : null,
|
|
@@ -599,7 +625,8 @@ function receiptPlanDigest(receipt) {
|
|
|
599
625
|
delete planValue.plan_digest;
|
|
600
626
|
return sha256(canonicalJson(planValue));
|
|
601
627
|
}
|
|
602
|
-
function verifyReceipt(receipt, request) {
|
|
628
|
+
function verifyReceipt(receipt, request, expectedHostBinding = DEFAULT_HOST_BINDING) {
|
|
629
|
+
const expectedBinding = normalizeExpectedHostBinding(expectedHostBinding);
|
|
603
630
|
if (!record(receipt) || receipt.turn_id !== request.turn_id)
|
|
604
631
|
throw new Error('judgment_receipt_turn_mismatch');
|
|
605
632
|
if (receipt.request_digest !== sha256(canonicalJson(request)))
|
|
@@ -609,10 +636,10 @@ function verifyReceipt(receipt, request) {
|
|
|
609
636
|
if (receipt.runtime_version !== RUNTIME_VERSION || receipt.manifest_digest !== MANIFEST_DIGEST) {
|
|
610
637
|
throw new Error('judgment_receipt_runtime_mismatch');
|
|
611
638
|
}
|
|
612
|
-
if (receipt.host_binding?.adapter_id !==
|
|
613
|
-
|| receipt.host_binding.adapter_version !==
|
|
614
|
-
|| receipt.host_binding.status !==
|
|
615
|
-
|| receipt.host_binding.enforcement_level !==
|
|
639
|
+
if (receipt.host_binding?.adapter_id !== expectedBinding.adapter_id
|
|
640
|
+
|| receipt.host_binding.adapter_version !== expectedBinding.adapter_version
|
|
641
|
+
|| receipt.host_binding.status !== expectedBinding.status
|
|
642
|
+
|| receipt.host_binding.enforcement_level !== expectedBinding.enforcement_level) {
|
|
616
643
|
throw new Error('judgment_receipt_binding_unmanaged');
|
|
617
644
|
}
|
|
618
645
|
if (!Array.isArray(receipt.active_node_definitions) || receipt.active_node_definitions.length === 0) {
|
|
@@ -623,10 +650,10 @@ function verifyReceipt(receipt, request) {
|
|
|
623
650
|
}
|
|
624
651
|
return receipt;
|
|
625
652
|
}
|
|
626
|
-
function existingAdoption(request, env) {
|
|
653
|
+
function existingAdoption(request, env, expectedHostBinding = DEFAULT_HOST_BINDING) {
|
|
627
654
|
const { target } = journalPaths(request.conversation_context.session_ref, request.turn_id, env);
|
|
628
655
|
try {
|
|
629
|
-
const entry = readAdoptionEntry(target);
|
|
656
|
+
const entry = readAdoptionEntry(target, expectedHostBinding);
|
|
630
657
|
if (entry.request_text_digest !== sha256(request.request)
|
|
631
658
|
|| entry.request.turn_id !== request.turn_id
|
|
632
659
|
|| entry.request.conversation_context.session_ref !== request.conversation_context.session_ref) {
|
|
@@ -640,7 +667,7 @@ function existingAdoption(request, env) {
|
|
|
640
667
|
throw error;
|
|
641
668
|
}
|
|
642
669
|
}
|
|
643
|
-
function existingAdoptionForPayload(payload, env) {
|
|
670
|
+
function existingAdoptionForPayload(payload, env, expectedHostBinding = DEFAULT_HOST_BINDING) {
|
|
644
671
|
const request = typeof payload.prompt === 'string' && payload.prompt.trim() ? payload.prompt : '';
|
|
645
672
|
const turnId = typeof payload.turn_id === 'string' ? payload.turn_id.trim() : '';
|
|
646
673
|
const sessionId = typeof payload.session_id === 'string' ? payload.session_id : '';
|
|
@@ -649,7 +676,7 @@ function existingAdoptionForPayload(payload, env) {
|
|
|
649
676
|
const sessionRef = sha256(sessionId);
|
|
650
677
|
const { target } = journalPaths(sessionRef, turnId, env);
|
|
651
678
|
try {
|
|
652
|
-
const entry = readAdoptionEntry(target);
|
|
679
|
+
const entry = readAdoptionEntry(target, expectedHostBinding);
|
|
653
680
|
if (entry.request_text_digest !== sha256(request)
|
|
654
681
|
|| entry.request.turn_id !== turnId
|
|
655
682
|
|| entry.request.conversation_context.session_ref !== sessionRef) {
|
|
@@ -663,7 +690,7 @@ function existingAdoptionForPayload(payload, env) {
|
|
|
663
690
|
throw error;
|
|
664
691
|
}
|
|
665
692
|
}
|
|
666
|
-
function adoptReceipt(request, receipt, env) {
|
|
693
|
+
function adoptReceipt(request, receipt, env, expectedHostBinding = DEFAULT_HOST_BINDING) {
|
|
667
694
|
const { directory, target } = journalPaths(request.conversation_context.session_ref, request.turn_id, env);
|
|
668
695
|
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
669
696
|
const ownerAudit = buildOwnerAudit(request, receipt);
|
|
@@ -691,7 +718,7 @@ function adoptReceipt(request, receipt, env) {
|
|
|
691
718
|
catch (error) {
|
|
692
719
|
if (!(error instanceof Error && 'code' in error && error.code === 'EEXIST'))
|
|
693
720
|
throw error;
|
|
694
|
-
return existingAdoption(request, env);
|
|
721
|
+
return existingAdoption(request, env, expectedHostBinding);
|
|
695
722
|
}
|
|
696
723
|
}
|
|
697
724
|
finally {
|
|
@@ -831,8 +858,8 @@ function readEpisode(path) {
|
|
|
831
858
|
}
|
|
832
859
|
return value;
|
|
833
860
|
}
|
|
834
|
-
function verifyEpisodeAdoption(episode, adoptionPath) {
|
|
835
|
-
const adoption = readAdoptionEntry(adoptionPath);
|
|
861
|
+
function verifyEpisodeAdoption(episode, adoptionPath, expectedHostBinding = DEFAULT_HOST_BINDING) {
|
|
862
|
+
const adoption = readAdoptionEntry(adoptionPath, expectedHostBinding);
|
|
836
863
|
if (sha256(canonicalJson(adoption.receipt)) !== episode.initial_receipt_digest
|
|
837
864
|
|| adoption.request.turn_id !== episode.turn_id
|
|
838
865
|
|| adoption.request.conversation_context.session_ref !== episode.session_ref) {
|
|
@@ -1093,6 +1120,7 @@ function successOutput(request, receipt, ownerAudit = buildOwnerAudit(request, r
|
|
|
1093
1120
|
}
|
|
1094
1121
|
export async function runJudgmentHost(payload, options = {}) {
|
|
1095
1122
|
const env = options.env ?? process.env;
|
|
1123
|
+
const expectedHostBinding = normalizeExpectedHostBinding(options.expectedHostBinding);
|
|
1096
1124
|
const sessionId = typeof payload.session_id === 'string' ? payload.session_id : '';
|
|
1097
1125
|
const turnId = typeof payload.turn_id === 'string' ? payload.turn_id.trim() : '';
|
|
1098
1126
|
if (!sessionId || !turnId)
|
|
@@ -1101,7 +1129,7 @@ export async function runJudgmentHost(payload, options = {}) {
|
|
|
1101
1129
|
return withTurnLock(paths.lock, async () => {
|
|
1102
1130
|
try {
|
|
1103
1131
|
const episode = readEpisode(paths.episode);
|
|
1104
|
-
verifyEpisodeAdoption(episode, paths.target);
|
|
1132
|
+
verifyEpisodeAdoption(episode, paths.target, expectedHostBinding);
|
|
1105
1133
|
const requestText = typeof payload.prompt === 'string' ? payload.prompt.trim() : '';
|
|
1106
1134
|
if (!requestText || episode.request_text_digest !== sha256(requestText)) {
|
|
1107
1135
|
throw new Error('judgment_episode_binding_mismatch');
|
|
@@ -1112,13 +1140,14 @@ export async function runJudgmentHost(payload, options = {}) {
|
|
|
1112
1140
|
if (!(error instanceof Error && 'code' in error && error.code === 'ENOENT'))
|
|
1113
1141
|
throw error;
|
|
1114
1142
|
}
|
|
1115
|
-
const adopted = existingAdoptionForPayload(payload, env);
|
|
1143
|
+
const adopted = existingAdoptionForPayload(payload, env, expectedHostBinding);
|
|
1116
1144
|
const request = adopted?.request ?? buildJudgmentRequest(payload, {
|
|
1117
1145
|
env,
|
|
1118
|
-
trustedConversationMessages: options.trustedConversationMessages
|
|
1146
|
+
trustedConversationMessages: options.trustedConversationMessages,
|
|
1147
|
+
expectedHostBinding
|
|
1119
1148
|
});
|
|
1120
|
-
const existing = adopted ?? existingAdoption(request, env);
|
|
1121
|
-
const adoption = existing ?? adoptReceipt(request, verifyReceipt(await (options.resolver ?? resolveLocalJudgment)(request), request), env);
|
|
1149
|
+
const existing = adopted ?? existingAdoption(request, env, expectedHostBinding);
|
|
1150
|
+
const adoption = existing ?? adoptReceipt(request, verifyReceipt(await (options.resolver ?? resolveLocalJudgment)(request), request, expectedHostBinding), env, expectedHostBinding);
|
|
1122
1151
|
const episodeWithoutDigest = {
|
|
1123
1152
|
schema_version: 'brainbase-judgment-episode-v1',
|
|
1124
1153
|
session_ref: request.conversation_context.session_ref,
|
|
@@ -1241,7 +1270,7 @@ function toolDisplayLine(tool, input, response, status) {
|
|
|
1241
1270
|
const result = selectionParts.length > 0 ? selectionParts.join('/') : '正常応答を確認';
|
|
1242
1271
|
return `📚 ${contract.label}: 「${target}」→ ${result} ✓`;
|
|
1243
1272
|
}
|
|
1244
|
-
async function recordToolUse(payload, env) {
|
|
1273
|
+
async function recordToolUse(payload, env, expectedHostBinding = DEFAULT_HOST_BINDING) {
|
|
1245
1274
|
const { sessionRef, turnId } = hookIdentity(payload);
|
|
1246
1275
|
const paths = journalPaths(sessionRef, turnId, env);
|
|
1247
1276
|
const toolUseId = typeof payload.tool_use_id === 'string' ? payload.tool_use_id.trim() : '';
|
|
@@ -1255,7 +1284,7 @@ async function recordToolUse(payload, env) {
|
|
|
1255
1284
|
let episode;
|
|
1256
1285
|
try {
|
|
1257
1286
|
episode = readEpisode(paths.episode);
|
|
1258
|
-
verifyEpisodeAdoption(episode, paths.target);
|
|
1287
|
+
verifyEpisodeAdoption(episode, paths.target, expectedHostBinding);
|
|
1259
1288
|
}
|
|
1260
1289
|
catch (error) {
|
|
1261
1290
|
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
@@ -1385,7 +1414,7 @@ function persistAutonomyReceipt(paths, sessionRef, snapshot, evaluation, mode) {
|
|
|
1385
1414
|
writeImmutableJson(path, receipt);
|
|
1386
1415
|
return receipt;
|
|
1387
1416
|
}
|
|
1388
|
-
async function autonomyStopSnapshot(payload, env) {
|
|
1417
|
+
async function autonomyStopSnapshot(payload, env, expectedHostBinding = DEFAULT_HOST_BINDING) {
|
|
1389
1418
|
const answer = typeof payload.last_assistant_message === 'string' ? payload.last_assistant_message : '';
|
|
1390
1419
|
if (!answer.trim())
|
|
1391
1420
|
return null;
|
|
@@ -1395,7 +1424,7 @@ async function autonomyStopSnapshot(payload, env) {
|
|
|
1395
1424
|
let episode;
|
|
1396
1425
|
try {
|
|
1397
1426
|
episode = readEpisode(paths.episode);
|
|
1398
|
-
verifyEpisodeAdoption(episode, paths.target);
|
|
1427
|
+
verifyEpisodeAdoption(episode, paths.target, expectedHostBinding);
|
|
1399
1428
|
}
|
|
1400
1429
|
catch (error) {
|
|
1401
1430
|
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
@@ -1434,7 +1463,7 @@ async function evaluateAutonomyStop(payload, env, options) {
|
|
|
1434
1463
|
const config = resolveAutonomyMode(options, env);
|
|
1435
1464
|
if (config.mode === 'off')
|
|
1436
1465
|
return null;
|
|
1437
|
-
const snapshot = await autonomyStopSnapshot(payload, env);
|
|
1466
|
+
const snapshot = await autonomyStopSnapshot(payload, env, options.expectedHostBinding);
|
|
1438
1467
|
if (!snapshot)
|
|
1439
1468
|
return null;
|
|
1440
1469
|
if (config.mode === 'canary'
|
|
@@ -1448,7 +1477,7 @@ async function evaluateAutonomyStop(payload, env, options) {
|
|
|
1448
1477
|
const paths = journalPaths(sessionRef, turnId, env);
|
|
1449
1478
|
const stillActive = await withTurnLock(paths.lock, async () => {
|
|
1450
1479
|
const episode = readEpisode(paths.episode);
|
|
1451
|
-
verifyEpisodeAdoption(episode, paths.target);
|
|
1480
|
+
verifyEpisodeAdoption(episode, paths.target, options.expectedHostBinding);
|
|
1452
1481
|
if (episode.episode_digest !== snapshot.episode_digest) {
|
|
1453
1482
|
throw new Error('judgment_autonomy_episode_changed');
|
|
1454
1483
|
}
|
|
@@ -1484,7 +1513,7 @@ async function evaluateAutonomyStop(payload, env, options) {
|
|
|
1484
1513
|
reason: renderJudgmentAutonomyContinuation(evaluation)
|
|
1485
1514
|
};
|
|
1486
1515
|
}
|
|
1487
|
-
async function finalizeEpisode(payload, env) {
|
|
1516
|
+
async function finalizeEpisode(payload, env, knowledgeEventSink, expectedHostBinding = DEFAULT_HOST_BINDING) {
|
|
1488
1517
|
const { sessionRef, turnId } = hookIdentity(payload);
|
|
1489
1518
|
const paths = journalPaths(sessionRef, turnId, env);
|
|
1490
1519
|
const answerDigest = sha256(typeof payload.last_assistant_message === 'string' ? payload.last_assistant_message : '');
|
|
@@ -1492,7 +1521,7 @@ async function finalizeEpisode(payload, env) {
|
|
|
1492
1521
|
let episode;
|
|
1493
1522
|
try {
|
|
1494
1523
|
episode = readEpisode(paths.episode);
|
|
1495
|
-
verifyEpisodeAdoption(episode, paths.target);
|
|
1524
|
+
verifyEpisodeAdoption(episode, paths.target, expectedHostBinding);
|
|
1496
1525
|
}
|
|
1497
1526
|
catch (error) {
|
|
1498
1527
|
if (error instanceof Error && 'code' in error && error.code === 'ENOENT')
|
|
@@ -1512,6 +1541,7 @@ async function finalizeEpisode(payload, env) {
|
|
|
1512
1541
|
|| existing.answer_digest !== answerDigest) {
|
|
1513
1542
|
throw new Error('judgment_final_conflict');
|
|
1514
1543
|
}
|
|
1544
|
+
await enqueueFinalizedKnowledgeEvent(knowledgeEventSink, sessionRef, turnId, existing, payload.last_assistant_message ?? '');
|
|
1515
1545
|
return existing;
|
|
1516
1546
|
}
|
|
1517
1547
|
catch (error) {
|
|
@@ -1595,23 +1625,41 @@ async function finalizeEpisode(payload, env) {
|
|
|
1595
1625
|
final_digest: sha256(canonicalJson(finalWithoutDigest))
|
|
1596
1626
|
};
|
|
1597
1627
|
writeImmutableJson(paths.final, final);
|
|
1628
|
+
await enqueueFinalizedKnowledgeEvent(knowledgeEventSink, sessionRef, turnId, final, answer);
|
|
1598
1629
|
return final;
|
|
1599
1630
|
});
|
|
1600
1631
|
}
|
|
1632
|
+
async function enqueueFinalizedKnowledgeEvent(sink, sessionRef, turnId, final, answer) {
|
|
1633
|
+
if (!sink)
|
|
1634
|
+
return null;
|
|
1635
|
+
const event = toKnowledgeEventFromJudgmentEpisode({
|
|
1636
|
+
episode_id: `${sessionRef}:${turnId}`,
|
|
1637
|
+
session_id: sessionRef,
|
|
1638
|
+
turn_id: turnId,
|
|
1639
|
+
finalized_at: final.finalized_at,
|
|
1640
|
+
answer_digest: final.answer_digest,
|
|
1641
|
+
completion_status: 'complete',
|
|
1642
|
+
final_answer: answer
|
|
1643
|
+
});
|
|
1644
|
+
if (event)
|
|
1645
|
+
await sink.enqueue(event);
|
|
1646
|
+
return event;
|
|
1647
|
+
}
|
|
1601
1648
|
export async function processJudgmentHook(payload, options = {}) {
|
|
1602
1649
|
const eventName = payload.hook_event_name ?? payload.hookEventName ?? 'UserPromptSubmit';
|
|
1603
1650
|
const env = options.env ?? process.env;
|
|
1651
|
+
const expectedHostBinding = normalizeExpectedHostBinding(options.expectedHostBinding);
|
|
1604
1652
|
if (eventName === 'UserPromptSubmit')
|
|
1605
1653
|
return runJudgmentHost(payload, options);
|
|
1606
1654
|
if (eventName === 'PostToolUse') {
|
|
1607
|
-
const event = await recordToolUse(payload, env);
|
|
1655
|
+
const event = await recordToolUse(payload, env, expectedHostBinding);
|
|
1608
1656
|
return event ? { systemMessage: event.display_line } : {};
|
|
1609
1657
|
}
|
|
1610
1658
|
if (eventName === 'Stop') {
|
|
1611
1659
|
const autonomyBlock = await evaluateAutonomyStop(payload, env, options);
|
|
1612
1660
|
if (autonomyBlock)
|
|
1613
1661
|
return autonomyBlock;
|
|
1614
|
-
const output = await finalizeEpisode(payload, env);
|
|
1662
|
+
const output = await finalizeEpisode(payload, env, options.knowledgeEventSink, expectedHostBinding);
|
|
1615
1663
|
if ('decision' in output && output.decision === 'block' && payload.stop_hook_active === true) {
|
|
1616
1664
|
throw new Error('judgment_stop_repair_exhausted');
|
|
1617
1665
|
}
|