@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.
Files changed (74) hide show
  1. package/contracts/canonical-task/operation-schema.sql +58 -0
  2. package/contracts/canonical-task/search-indexes.sql +10 -0
  3. package/contracts/canonical-task/store-schema.sql +77 -0
  4. package/dist/automation-run.d.ts +213 -0
  5. package/dist/automation-run.js +479 -0
  6. package/dist/canonical-task-operations.d.ts +205 -0
  7. package/dist/canonical-task-operations.js +700 -0
  8. package/dist/canonical-task-postgres.d.ts +139 -0
  9. package/dist/canonical-task-postgres.js +554 -0
  10. package/dist/canonical-task-service.d.ts +13 -0
  11. package/dist/canonical-task-service.js +55 -3
  12. package/dist/canonical-task-store-config.d.ts +31 -0
  13. package/dist/canonical-task-store-config.js +53 -0
  14. package/dist/config-parser.d.ts +298 -0
  15. package/dist/config-parser.js +593 -0
  16. package/dist/connected-onboarding.d.ts +57 -0
  17. package/dist/connected-onboarding.js +37 -0
  18. package/dist/durable-wait-http.d.ts +3 -1
  19. package/dist/durable-wait-http.js +73 -6
  20. package/dist/durable-waits.d.ts +93 -3
  21. package/dist/durable-waits.js +320 -8
  22. package/dist/honcho.d.ts +25 -0
  23. package/dist/honcho.js +81 -0
  24. package/dist/judgment-event-adapter.d.ts +72 -0
  25. package/dist/judgment-event-adapter.js +114 -0
  26. package/dist/judgment-event-outbox.d.ts +182 -0
  27. package/dist/judgment-event-outbox.js +568 -0
  28. package/dist/judgment-execution-outcome.d.ts +46 -0
  29. package/dist/judgment-execution-outcome.js +149 -0
  30. package/dist/judgment-host.d.ts +18 -7
  31. package/dist/judgment-host.js +82 -34
  32. package/dist/judgment-lifecycle-core.d.ts +198 -0
  33. package/dist/judgment-lifecycle-core.js +464 -0
  34. package/dist/judgment-node-evidence.d.ts +57 -0
  35. package/dist/judgment-node-evidence.js +297 -0
  36. package/dist/judgment-runtime-resolution.d.ts +264 -0
  37. package/dist/judgment-runtime-resolution.js +1248 -0
  38. package/dist/judgment-runtime-resolution.public.d.ts +255 -0
  39. package/dist/knowledge-event.d.ts +28 -0
  40. package/dist/knowledge-event.js +114 -0
  41. package/dist/mcp-runtime.d.ts +75 -0
  42. package/dist/mcp-runtime.js +188 -0
  43. package/dist/meeting-automation-runtime.d.ts +102 -0
  44. package/dist/meeting-automation-runtime.js +249 -0
  45. package/dist/meeting-automation.d.ts +155 -0
  46. package/dist/meeting-automation.js +341 -0
  47. package/dist/meeting-minutes-context-receipt.d.ts +91 -0
  48. package/dist/meeting-minutes-context-receipt.js +280 -0
  49. package/dist/meeting-source-sync.d.ts +89 -0
  50. package/dist/meeting-source-sync.js +230 -0
  51. package/dist/mesh.d.ts +58 -0
  52. package/dist/mesh.js +134 -0
  53. package/dist/onboarding-runtime.d.ts +47 -0
  54. package/dist/onboarding-runtime.js +4719 -0
  55. package/dist/routine-cycle.d.ts +51 -0
  56. package/dist/routine-cycle.js +153 -0
  57. package/dist/routine-liveness.d.ts +49 -0
  58. package/dist/routine-liveness.js +227 -0
  59. package/dist/routine-runtime.d.ts +101 -0
  60. package/dist/routine-runtime.js +237 -0
  61. package/dist/routines.d.ts +3 -0
  62. package/dist/routines.js +3 -0
  63. package/dist/run-receipt.d.ts +40 -0
  64. package/dist/run-receipt.js +635 -0
  65. package/dist/runtime-host.d.ts +64 -0
  66. package/dist/runtime-host.js +70 -0
  67. package/dist/world-work.d.ts +16 -0
  68. package/package.json +115 -3
  69. package/ui/meeting-minutes.css +122 -0
  70. package/ui/meeting-minutes.js +316 -0
  71. package/ui/objective-editor.js +47 -11
  72. package/ui/world/world-district.js +18 -3
  73. package/ui/world/world-placement.js +13 -0
  74. 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
+ }
@@ -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 }?: {
@@ -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 !== 'brainbase-local'
613
- || receipt.host_binding.adapter_version !== '1'
614
- || receipt.host_binding.status !== 'managed'
615
- || receipt.host_binding.enforcement_level !== 'host_contract') {
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
  }