@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,1248 @@
1
+ // @ts-nocheck
2
+ // Stage3a migration exception: the mechanically extracted legacy helper body
3
+ // remains unchecked internally. The public adapter contract is declared and
4
+ // strict-checked in judgment-runtime-resolution.public.d.ts instead of using
5
+ // this exception to weaken the normalized input/output boundary.
6
+ /**
7
+ * Pure judgment runtime resolver core.
8
+ *
9
+ * This module is intentionally free of filesystem, transport, authentication,
10
+ * tenant, Host binding, policy authorization, TaskGraph persistence, and
11
+ * receipt I/O. Callers must validate and authorize those inputs in an adapter,
12
+ * then pass only the normalized request and already-applicable policies here.
13
+ * The implementation is kept behaviorally aligned with the former Unson
14
+ * service so the Host can opt into it without changing live behavior.
15
+ */
16
+ import { JudgmentResolutionError, canonicalJson, computeRequestDigest, sha256Hex } from './judgment-resolution-contract.js';
17
+ export { JudgmentResolutionError, canonicalJson, computeRequestDigest, sha256Hex };
18
+ const INPUT_FIELDS = new Set(['request', 'turn_id', 'project_code', 'conversation_context', 'model_interpretation']);
19
+ const CLASSIFICATION_FIELDS = new Set(['intent', 'domains', 'action_kind', 'risk', 'confidence', 'signals']);
20
+ const CONVERSATION_CONTEXT_FIELDS = new Set([
21
+ 'schema_version', 'session_ref', 'messages', 'prior_receipts', 'runtime',
22
+ 'instruction_bindings', 'completeness', 'source_digest'
23
+ ]);
24
+ const MESSAGE_FIELDS = new Set(['sequence', 'turn_id', 'role', 'phase', 'text']);
25
+ const PRIOR_RECEIPT_FIELDS = new Set([
26
+ 'turn_id', 'resolution_id', 'request_digest', 'context_digest', 'plan_digest',
27
+ 'classification', 'selected_dag_ids'
28
+ ]);
29
+ const RUNTIME_FIELDS = new Set(['host', 'entrypoint', 'model', 'permission_mode', 'project_binding']);
30
+ const INSTRUCTION_BINDING_FIELDS = new Set(['scope', 'source_ref', 'digest']);
31
+ const INTENTS = new Set(['answer', 'investigate', 'diagnose', 'design', 'implement', 'review', 'operate']);
32
+ const INTENT_ORDER = ['operate', 'implement', 'review', 'diagnose', 'design', 'investigate', 'answer'];
33
+ const DOMAINS = new Set(['general', 'knowledge', 'personal_judgment', 'engineering', 'organization', 'operations']);
34
+ const ACTIONS = ['none', 'read', 'write', 'external'];
35
+ const RISKS = ['low', 'medium', 'high', 'critical'];
36
+ const CONFIDENCES = new Set(['confirmed', 'inferred', 'unknown']);
37
+ const SIGNALS = new Set(['cumulative_effect', 'complexity_growth', 'threshold_proposal', 'parallel_exploration', 'authority_boundary', 'problem_frame_uncertain', 'external_outcome']);
38
+ const CONTENT_TYPES = new Set(['canonical_fact', 'team_document', 'source_document', 'personal_knowledge', 'operational_state', 'unknown']);
39
+ const SCOPE_SPECIFICITY = { global: 0, organization: 1, project: 2, owner: 3 };
40
+ const POLICY_VISIBILITIES = new Set(['organization', 'owner']);
41
+ const NODE_KINDS = new Set(['common', 'judgment', 'capability', 'constraint', 'fail_closed']);
42
+ const DAG_KINDS = new Set(['domain', 'constraint', 'fail_closed']);
43
+ const DOMAIN_MATCHERS = new Set(['engineering', 'knowledge', 'personal_judgment', 'organization', 'operations']);
44
+ const SAFETY_MATCHERS = new Set(['write', 'external', 'critical']);
45
+ const AUTONOMY_DECISIONS = new Set(['continue', 'escalate']);
46
+ const AUTONOMY_REASON_CODES = new Set([
47
+ 'routine_in_scope', 'classification_missing', 'policy_conflict', 'risk_or_external'
48
+ ]);
49
+ const RUNTIME_ESCALATION_REASONS = new Set([
50
+ 'irreversible_action', 'missing_authority', 'owner_value_choice',
51
+ 'required_input_unavailable', 'evidenced_terminal_blocker'
52
+ ]);
53
+ const INTENT_MATCHERS = new Set(INTENT_ORDER);
54
+ const ADAPTER_ID_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/;
55
+ const ADAPTER_VERSION_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,31}$/;
56
+ const RUNTIME_HOSTS = new Set(['codex', 'claude-code']);
57
+ const RUNTIME_ENTRYPOINTS = new Set(['codex', 'claude_code', 'mana', 'company_os', 'unknown']);
58
+ function compareCodePoints(left, right) {
59
+ const a = Array.from(left, (value) => value.codePointAt(0));
60
+ const b = Array.from(right, (value) => value.codePointAt(0));
61
+ for (let index = 0; index < Math.min(a.length, b.length); index += 1) {
62
+ if (a[index] !== b[index])
63
+ return a[index] - b[index];
64
+ }
65
+ return a.length - b.length;
66
+ }
67
+ function fail(message, code = 'judgment_resolution_input_invalid', status = 400) {
68
+ throw new JudgmentResolutionError(code, message, status);
69
+ }
70
+ function exactFields(value, allowed, name) {
71
+ if (!value || typeof value !== 'object' || Array.isArray(value))
72
+ fail(`${name} must be an object`);
73
+ for (const key of Object.keys(value)) {
74
+ if (!allowed.has(key))
75
+ fail(`${name}.${key} is not allowed`);
76
+ }
77
+ }
78
+ function requiredString(value, name) {
79
+ if (typeof value !== 'string' || !value.trim())
80
+ fail(`${name} is required`);
81
+ return value.trim();
82
+ }
83
+ function requiredText(value, name) {
84
+ if (typeof value !== 'string' || !value.trim())
85
+ fail(`${name} is required`);
86
+ return value;
87
+ }
88
+ function uniqueEnumArray(value, name, allowed, { optional = false } = {}) {
89
+ if (value === undefined && optional)
90
+ return [];
91
+ if (!Array.isArray(value) || (!optional && value.length === 0))
92
+ fail(`${name} must be a non-empty array`);
93
+ const output = [];
94
+ const seen = new Set();
95
+ for (const entry of value) {
96
+ if (typeof entry !== 'string' || !allowed.has(entry))
97
+ fail(`${name} contains an invalid value`);
98
+ if (seen.has(entry))
99
+ fail(`${name} contains duplicate value '${entry}'`);
100
+ seen.add(entry);
101
+ output.push(entry);
102
+ }
103
+ return output;
104
+ }
105
+ function uniqueStringArray(value, name) {
106
+ if (!Array.isArray(value) || value.length === 0)
107
+ fail(`${name} must be a non-empty array`);
108
+ const output = [];
109
+ const seen = new Set();
110
+ for (const entry of value) {
111
+ const normalized = requiredString(entry, name);
112
+ if (/[\u0000-\u001f\u007f]/u.test(normalized))
113
+ fail(`${name} contains an invalid value`);
114
+ if (seen.has(normalized))
115
+ fail(`${name} contains duplicate value '${normalized}'`);
116
+ seen.add(normalized);
117
+ output.push(normalized);
118
+ }
119
+ return output;
120
+ }
121
+ function stringArray(value, name, { allowEmpty = false } = {}) {
122
+ if (!Array.isArray(value) || (!allowEmpty && value.length === 0))
123
+ fail(`${name} must be a${allowEmpty ? '' : ' non-empty'} array`);
124
+ return value.map((entry) => requiredString(entry, name));
125
+ }
126
+ function enumValue(value, name, allowed) {
127
+ if (typeof value !== 'string' || !allowed.has(value))
128
+ fail(`${name} contains an invalid value`);
129
+ return value;
130
+ }
131
+ function indexFloor(order, left, right) {
132
+ return order[Math.max(order.indexOf(left), order.indexOf(right))];
133
+ }
134
+ function actionFloor(intent) {
135
+ if (intent === 'implement' || intent === 'operate')
136
+ return 'write';
137
+ if (intent === 'investigate' || intent === 'diagnose' || intent === 'review')
138
+ return 'read';
139
+ return 'none';
140
+ }
141
+ function escapeRegExp(value) {
142
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
143
+ }
144
+ function includesTerm(request, terms) {
145
+ const normalized = request.toLocaleLowerCase('ja');
146
+ return terms.some((term) => {
147
+ const normalizedTerm = term.toLocaleLowerCase('ja');
148
+ if (/^[a-z0-9_.-]+$/u.test(normalizedTerm)) {
149
+ return new RegExp(`(?<![a-z0-9_])${escapeRegExp(normalizedTerm)}(?![a-z0-9_])`, 'u').test(normalized);
150
+ }
151
+ return normalized.includes(normalizedTerm);
152
+ });
153
+ }
154
+ function includesPositiveCommandClause(request, terms) {
155
+ const japaneseTerms = terms.filter((term) => !/^[a-z0-9_.-]+$/iu.test(term));
156
+ const normalizedRequest = request.toLocaleLowerCase('ja');
157
+ const includesJapaneseCommand = japaneseTerms.some((term) => {
158
+ const normalizedTerm = term.toLocaleLowerCase('ja');
159
+ let offset = normalizedRequest.indexOf(normalizedTerm);
160
+ while (offset >= 0) {
161
+ const remainder = normalizedRequest.slice(offset + normalizedTerm.length);
162
+ const endsCommand = /^(?:$|[\s、,;;。!?!?])/u.test(remainder)
163
+ || /^(?:(?:ください|下さい|くれ|ほしい|欲しい|おけ|みろ)|(?:おいて|みて)(?:ください|下さい)?|(?:もらえ|いただけ)(?:ます(?:か|でしょうか)|ません(?:か|でしょうか)|ないですか)|(?:もらいたい|いただきたい))(?=$|[\s、,;;。!?!?])/u.test(remainder);
164
+ if (endsCommand) {
165
+ return true;
166
+ }
167
+ offset = normalizedRequest.indexOf(normalizedTerm, offset + normalizedTerm.length);
168
+ }
169
+ return false;
170
+ });
171
+ if (includesJapaneseCommand)
172
+ return true;
173
+ const englishTerms = terms.filter((term) => /^[a-z0-9_.-]+$/iu.test(term));
174
+ if (englishTerms.length === 0)
175
+ return false;
176
+ const commandPattern = englishTerms.map(escapeRegExp).join('|');
177
+ const match = new RegExp(`^(?:please\\s+|(?:can|could|would|will)\\s+you\\s+(?:please\\s+)?|you\\s+must\\s+)?(?:${commandPattern})\\b`, 'iu').exec(request.trim());
178
+ if (!match)
179
+ return false;
180
+ const remainder = request.trim().slice(match[0].length);
181
+ return !/^\s+(?:is|are|was|were|has|have|had)\b/iu.test(remainder);
182
+ }
183
+ function includesRequestedEffectTerm(request, terms) {
184
+ const normalized = request.toLocaleLowerCase('ja');
185
+ return terms.some((term) => {
186
+ const normalizedTerm = term.toLocaleLowerCase('ja');
187
+ let offset = 0;
188
+ while (offset < normalized.length) {
189
+ const matcher = /^[a-z0-9_.-]+$/u.test(normalizedTerm)
190
+ ? new RegExp(`(?<![a-z0-9_])${escapeRegExp(normalizedTerm)}(?![a-z0-9_])`, 'gu')
191
+ : null;
192
+ if (matcher)
193
+ matcher.lastIndex = offset;
194
+ const match = matcher?.exec(normalized);
195
+ const index = matcher ? (match?.index ?? -1) : normalized.indexOf(normalizedTerm, offset);
196
+ if (index < 0)
197
+ return false;
198
+ const continuation = normalized.slice(index + normalizedTerm.length).trimStart();
199
+ const isConditionalTeForm = normalizedTerm.endsWith('して')
200
+ && /^(?:も(?!ら)|しま|いる|いた|ある|あった|おり|はいけ|はなら|よい|良い|いい|問題ない|可能|でき)/u.test(continuation);
201
+ const sentenceTail = continuation.split(/[。!?\n]/u, 1)[0];
202
+ const negation = /(?:は|を|も)?(?:実行|実施)?(?:しない(?:でください)?|しません|行わない(?:でください)?|禁止)/u.exec(sentenceTail);
203
+ const listPrefix = negation ? sentenceTail.slice(0, negation.index) : '';
204
+ const isNegated = /(?:do not|don't|must not)\s*$/u.test(normalized.slice(Math.max(0, index - 16), index))
205
+ || Boolean(negation && !/(?:して|し、|し,|した|する|してください|せよ|しろ)/u.test(listPrefix));
206
+ if (!isConditionalTeForm && !isNegated)
207
+ return true;
208
+ offset = index + normalizedTerm.length;
209
+ }
210
+ return false;
211
+ });
212
+ }
213
+ // Bounds keep the check linear in the request length. Past a bound the term
214
+ // stays critical, so a bound can keep an escalation but never remove one.
215
+ const PROHIBITION_WINDOW = 200;
216
+ const MAX_PROHIBITED_OCCURRENCES = 32;
217
+ // A prohibition predicate ends its clause. A negation that continues into a
218
+ // condition, reason, or question (「行わないと」「しないので」「しませんか」) is not one.
219
+ const JAPANESE_PROHIBITION_PREDICATE = /(?:(?:しない|行わない)(?:でください|こと)?|しません|行いません|するな|避けてください|(?:して|って|んで|て|で)?は(?:いけ|なら)(?:ない|ません)|禁止(?:です|されています)?|不可(?:です)?)(?=[ねよ]?\s*[))」』]*\s*(?:$|[、,,;;。.!!]))/u;
220
+ const NOUN = String.raw `(?:(?!等|以外)[\p{Script=Han}\p{Script=Katakana}\p{Script=Latin}\p{Nd}ー々_-])+`;
221
+ const ACTION_HEAD = '(?:外部|本番|自動|手動|一括)?(?:変更|操作|処理|作業|実行|送信|送付|更新|反映|書き込み|書込み|投稿|通知|対応|行為|取引|手続き|アクション)';
222
+ const COORDINATOR = '(?:・|や|と|および|及び|または|又は|もしくは|並びに|ならびに)';
223
+ // All that may separate a critical term from the prohibition governing it: an
224
+ // action noun it modifies, the rest of a noun list, and one final particle. A
225
+ // comma list must close with など/等, since 「本番削除、確認はしない」 can also
226
+ // mean "delete, without confirming". Anything else (「以外の」「のみ行い、」
227
+ // 「は後回しに」「の停止は」) means the prohibition governs something other than
228
+ // the term.
229
+ const PROHIBITED_TERM_TAIL = new RegExp(`^(?:の?${ACTION_HEAD})?(?:(?:\\s*${COORDINATOR}\\s*${NOUN})*(?:など|等)?(?:の${ACTION_HEAD})?|(?:\\s*(?:[、,,]|${COORDINATOR})\\s*${NOUN})+(?:など|等)(?:の${ACTION_HEAD})?)[はをもが]?(?:一切|絶対に|決して)?$`, 'u');
230
+ // Before a list comma the term must start its item or follow a noun modifier;
231
+ // 「請求書を支払い、」「すぐ支払い、」「至急支払い、」 use it as a verb.
232
+ const LIST_ITEM_HEAD = /^(?:(?!等|以外)[\p{Script=Han}\p{Script=Katakana}\p{Script=Latin}\p{Nd}ー々_\-  ]|の|や|と|への|での|からの|までの)*$/u;
233
+ const LIST_ITEM_HEAD_END = /(?:^|の|や|と)\s*$/u;
234
+ function isProhibitedObject(text, index, length) {
235
+ const before = text.slice(Math.max(0, index - PROHIBITION_WINDOW), index);
236
+ const clause = before.slice(Math.max(...['。', '.', '!', '?', '!', '?', '\n'].map((mark) => before.lastIndexOf(mark))) + 1);
237
+ const clauseHead = clause.slice(Math.max(...['、', ',', ',', ';', ';'].map((mark) => clause.lastIndexOf(mark))) + 1);
238
+ const tailEnd = index + length + PROHIBITION_WINDOW + 1;
239
+ const sentenceTail = /^[^。.!?!?\n]*[。.!?!?\n]?/u.exec(text.slice(index + length, tailEnd))[0];
240
+ // A question, or a sentence too long to judge, is not a prohibition.
241
+ if (sentenceTail.length > PROHIBITION_WINDOW || /[??]$/u.test(sentenceTail))
242
+ return false;
243
+ const prohibition = JAPANESE_PROHIBITION_PREDICATE.exec(sentenceTail);
244
+ if (!prohibition)
245
+ return false;
246
+ // A quoted or nominalized prohibition (「支払いはしない、という制限は外して」
247
+ // 「支払いはしない、は撤回」) is being discussed, not imposed.
248
+ const predicateEnd = index + length + prohibition.index + prohibition[0].length;
249
+ if (/^[ねよ]?[\s))」』]*(?:[、,,;;。.!!]?\s*(?:と|って)|[、,,]\s*[はをがもの])/u.test(text.slice(predicateEnd, predicateEnd + 8)))
250
+ return false;
251
+ const between = sentenceTail.slice(0, prohibition.index);
252
+ if (/^\s*[、,,]/u.test(between) && !(LIST_ITEM_HEAD.test(clauseHead) && LIST_ITEM_HEAD_END.test(clauseHead)))
253
+ return false;
254
+ return PROHIBITED_TERM_TAIL.test(between);
255
+ }
256
+ // Polite prohibitions are already stripped from the positive request. A term
257
+ // that survives still counts unless a clause-final prohibition governs it
258
+ // directly, such as the plain form 「…支払い…などの外部変更は行わない。」.
259
+ function includesUnprohibitedTerm(request, terms) {
260
+ const normalized = request.toLocaleLowerCase('ja');
261
+ return terms.some((term) => {
262
+ const normalizedTerm = term.toLocaleLowerCase('ja');
263
+ if (/^[a-z0-9_.-]+$/u.test(normalizedTerm))
264
+ return includesTerm(request, [term]);
265
+ let prohibited = 0;
266
+ for (let index = normalized.indexOf(normalizedTerm); index >= 0; index = normalized.indexOf(normalizedTerm, index + normalizedTerm.length)) {
267
+ if (prohibited === MAX_PROHIBITED_OCCURRENCES || !isProhibitedObject(normalized, index, normalizedTerm.length))
268
+ return true;
269
+ prohibited += 1;
270
+ }
271
+ return false;
272
+ });
273
+ }
274
+ function responseAnnotationCommands(request) {
275
+ const commands = [];
276
+ for (const match of request.matchAll(/<response-annotations>([\s\S]*?)<\/response-annotations>/giu)) {
277
+ let annotations;
278
+ try {
279
+ annotations = JSON.parse(match[1]);
280
+ }
281
+ catch {
282
+ continue;
283
+ }
284
+ if (!Array.isArray(annotations))
285
+ continue;
286
+ for (const entry of annotations) {
287
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry))
288
+ continue;
289
+ if (typeof entry.annotation !== 'string' || !entry.annotation.trim())
290
+ continue;
291
+ commands.push(entry.annotation.trim());
292
+ }
293
+ }
294
+ return commands;
295
+ }
296
+ function classificationRequest(request) {
297
+ const annotationCommands = responseAnnotationCommands(request);
298
+ const withoutStructuredMaterial = request
299
+ .replace(/<response-annotations>[\s\S]*?<\/response-annotations>/giu, ' ')
300
+ .replace(/```[\s\S]*?```/gu, ' ')
301
+ .replace(/^>.*$/gmu, ' ');
302
+ const paragraphs = withoutStructuredMaterial.split(/\n\s*\n/u);
303
+ const materialStart = paragraphs.findIndex((paragraph, index) => index > 0 && (/(?:^|\n)\s*(?:添付|引用|会話ログ|ログ|参考|資料)\s*[::]/u.test(paragraph)
304
+ || /(?:^|\n)\s*(?:\d{1,2}:\d{2}|\d{4}[./-]\d{1,2}[./-]\d{1,2}|\d{4}\.\d{2}\.\d{2}\s+.+曜日)/u.test(paragraph)));
305
+ const commandParagraphs = materialStart < 0 ? paragraphs : paragraphs.slice(0, materialStart);
306
+ return [...commandParagraphs, ...annotationCommands].join('\n\n').trim();
307
+ }
308
+ function positiveClassificationRequest(request, manifest) {
309
+ const positiveCommandTerms = manifest.semantic_matchers.positive_commands;
310
+ return request
311
+ .split(/(?<=[。!?.!?\n])/u)
312
+ .map((sentence) => {
313
+ const japaneseBoundary = /(?:しないでください|行わないでください|実行しないでください|しないこと|するな|避けてください|しません|行いません|実行しません|禁止(?:です|されています)?(?=[、,;;。!?\s]|$)|不可(?:です)?(?=[、,;;。!?\s]|$))/u.exec(sentence);
314
+ if (japaneseBoundary) {
315
+ const beforeNegation = sentence.slice(0, japaneseBoundary.index);
316
+ const clauseBoundary = Math.max(beforeNegation.lastIndexOf('、'), beforeNegation.lastIndexOf(','), beforeNegation.lastIndexOf(';'), beforeNegation.lastIndexOf(';'));
317
+ const positivePrefix = clauseBoundary >= 0
318
+ ? beforeNegation.slice(0, clauseBoundary).trim()
319
+ : '';
320
+ const keepPrefix = includesPositiveCommandClause(positivePrefix, positiveCommandTerms)
321
+ ? positivePrefix
322
+ : '';
323
+ const afterNegation = sentence.slice(japaneseBoundary.index + japaneseBoundary[0].length);
324
+ const tailBoundary = /^[、,;;]/u.exec(afterNegation);
325
+ const tailCandidate = tailBoundary
326
+ ? afterNegation.slice(tailBoundary[0].length).replace(/^[、,;;。!?\s]+/u, '')
327
+ : '';
328
+ const positiveTail = includesPositiveCommandClause(tailCandidate, positiveCommandTerms)
329
+ ? tailCandidate
330
+ : '';
331
+ return [keepPrefix, positiveTail].filter(Boolean).join('。');
332
+ }
333
+ const englishBoundary = /\b(?:do not|don't|must not|never|no(?!-)|is prohibited|are prohibited|is forbidden|are forbidden)\b/iu.exec(sentence);
334
+ if (englishBoundary) {
335
+ const beforeNegation = sentence.slice(0, englishBoundary.index);
336
+ const suffixProhibition = /^(?:is|are) (?:prohibited|forbidden)$/iu.test(englishBoundary[0]);
337
+ const clauseBoundary = Math.max(beforeNegation.lastIndexOf(','), beforeNegation.lastIndexOf(';'));
338
+ const positiveMaterial = suffixProhibition
339
+ ? (clauseBoundary >= 0 ? beforeNegation.slice(0, clauseBoundary) : '')
340
+ : beforeNegation;
341
+ const positivePrefix = positiveMaterial
342
+ .replace(/\b(?:but|and)\s*$/iu, '')
343
+ .replace(/[,;:\s]+$/u, '')
344
+ .trim();
345
+ const keepPrefix = includesPositiveCommandClause(positivePrefix, positiveCommandTerms)
346
+ ? positivePrefix
347
+ : '';
348
+ const afterNegation = sentence.slice(englishBoundary.index + englishBoundary[0].length);
349
+ const tailBoundary = /(?:[;;]|,\s*(?:but|and(?: then)?)\s+)/iu.exec(afterNegation);
350
+ const tailCandidate = tailBoundary
351
+ ? afterNegation.slice(tailBoundary.index + tailBoundary[0].length).replace(/^[,.;:!?\s]+/u, '')
352
+ : '';
353
+ const positiveTail = includesPositiveCommandClause(tailCandidate, positiveCommandTerms)
354
+ ? tailCandidate
355
+ : '';
356
+ return [keepPrefix, positiveTail].filter(Boolean).join('. ');
357
+ }
358
+ return sentence;
359
+ })
360
+ .join('')
361
+ .trim();
362
+ }
363
+ function sortByOrder(values, order) {
364
+ const indexes = new Map(order.map((value, index) => [value, index]));
365
+ return [...values].sort((left, right) => (indexes.get(left) ?? Number.MAX_SAFE_INTEGER) - (indexes.get(right) ?? Number.MAX_SAFE_INTEGER));
366
+ }
367
+ function validatedClassification(value, name, manifest) {
368
+ exactFields(value, CLASSIFICATION_FIELDS, name);
369
+ const domains = uniqueEnumArray(value.domains, `${name}.domains`, DOMAINS);
370
+ return {
371
+ intent: enumValue(value.intent, `${name}.intent`, INTENTS),
372
+ domains: sortByOrder(domains, manifest.selectors.domain_order),
373
+ action_kind: enumValue(value.action_kind, `${name}.action_kind`, new Set(ACTIONS)),
374
+ risk: enumValue(value.risk, `${name}.risk`, new Set(RISKS)),
375
+ confidence: enumValue(value.confidence, `${name}.confidence`, CONFIDENCES),
376
+ signals: sortByOrder(uniqueEnumArray(value.signals, `${name}.signals`, SIGNALS, { optional: true }), manifest.selectors.signal_order)
377
+ };
378
+ }
379
+ function validatePolicy(policy, seen) {
380
+ if (!policy || typeof policy !== 'object' || Array.isArray(policy) || typeof policy.id !== 'string' || !policy.id || seen.has(policy.id))
381
+ throw new TypeError('judgment manifest has duplicate or invalid policy id');
382
+ seen.add(policy.id);
383
+ if (typeof policy.version !== 'string' || !policy.version)
384
+ throw new TypeError(`policy ${policy.id} version is invalid`);
385
+ if (!Number.isInteger(policy.priority))
386
+ throw new TypeError(`policy ${policy.id} priority must be an integer`);
387
+ if (!['hard', 'soft'].includes(policy.strength))
388
+ throw new TypeError(`policy ${policy.id} strength is invalid`);
389
+ if (!policy.scope || !(policy.scope.type in SCOPE_SPECIFICITY))
390
+ throw new TypeError(`policy ${policy.id} scope is invalid`);
391
+ if (policy.scope.type === 'global' ? policy.scope.id !== null : typeof policy.scope.id !== 'string' || !policy.scope.id)
392
+ throw new TypeError(`policy ${policy.id} scope id is invalid`);
393
+ if (!POLICY_VISIBILITIES.has(policy.visibility))
394
+ throw new TypeError(`policy ${policy.id} visibility is invalid`);
395
+ if (policy.owner_person_id !== null && (typeof policy.owner_person_id !== 'string' || !policy.owner_person_id))
396
+ throw new TypeError(`policy ${policy.id} owner is invalid`);
397
+ if (policy.visibility === 'owner' && typeof policy.owner_person_id !== 'string')
398
+ throw new TypeError(`policy ${policy.id} owner visibility requires an owner`);
399
+ if (typeof policy.evidence_requirement !== 'string' || !policy.evidence_requirement)
400
+ throw new TypeError(`policy ${policy.id} evidence requirement is invalid`);
401
+ if (!policy.effect || typeof policy.effect.target !== 'string' || !policy.effect.target)
402
+ throw new TypeError(`policy ${policy.id} effect target is invalid`);
403
+ const decision = policy.effect?.decision;
404
+ if (policy.strength === 'hard' && !['require', 'forbid'].includes(decision))
405
+ throw new TypeError(`policy ${policy.id} hard effect is invalid`);
406
+ if (policy.strength === 'soft' && decision !== 'prefer')
407
+ throw new TypeError(`policy ${policy.id} soft effect is invalid`);
408
+ if (typeof policy.instruction !== 'string' || !policy.instruction)
409
+ throw new TypeError(`policy ${policy.id} instruction is invalid`);
410
+ if (policy.human_approval !== undefined) {
411
+ const approval = policy.human_approval;
412
+ if (!approval || typeof approval !== 'object' || Array.isArray(approval))
413
+ throw new TypeError(`policy ${policy.id} human_approval is invalid`);
414
+ const keys = Object.keys(approval);
415
+ if (keys.length === 0 || keys.some((key) => !['action_kinds', 'risks'].includes(key)))
416
+ throw new TypeError(`policy ${policy.id} human_approval is invalid`);
417
+ if (approval.action_kinds !== undefined && (!Array.isArray(approval.action_kinds) || approval.action_kinds.length === 0 || approval.action_kinds.some((kind) => !ACTIONS.includes(kind)))) {
418
+ throw new TypeError(`policy ${policy.id} human_approval action_kinds is invalid`);
419
+ }
420
+ if (approval.risks !== undefined && (!Array.isArray(approval.risks) || approval.risks.length === 0 || approval.risks.some((risk) => !RISKS.includes(risk)))) {
421
+ throw new TypeError(`policy ${policy.id} human_approval risks is invalid`);
422
+ }
423
+ }
424
+ }
425
+ function validateStringTerms(value, name, { allowEmpty = false } = {}) {
426
+ if (!Array.isArray(value) || (!allowEmpty && value.length === 0) || value.some((entry) => typeof entry !== 'string' || !entry)) {
427
+ throw new TypeError(`${name} terms are invalid`);
428
+ }
429
+ }
430
+ function validateExactKeys(value, expected, name) {
431
+ if (!value || typeof value !== 'object' || Array.isArray(value))
432
+ throw new TypeError(`${name} is invalid`);
433
+ const actual = Object.keys(value);
434
+ if (actual.length !== expected.size || actual.some((key) => !expected.has(key)))
435
+ throw new TypeError(`${name} references an unsupported selector or matcher`);
436
+ }
437
+ function validateSelectableGraphs(manifest) {
438
+ const signalDagIds = Object.values(manifest.selectors.signal_dags);
439
+ const commonDagIds = [...signalDagIds, manifest.selectors.authority_dag];
440
+ const domainGroups = [
441
+ [manifest.selectors.domain_dags.general],
442
+ manifest.selectors.domain_order
443
+ .filter((domain) => domain !== 'general')
444
+ .map((domain) => manifest.selectors.domain_dags[domain])
445
+ ];
446
+ if (manifest.selectors.engineering_implementation_dag) {
447
+ domainGroups.push(domainGroups[1].map((id) => id === manifest.selectors.domain_dags.engineering
448
+ ? manifest.selectors.engineering_implementation_dag : id));
449
+ }
450
+ for (const domainDagIds of domainGroups) {
451
+ try {
452
+ buildGraph([...new Set([...domainDagIds, ...commonDagIds])], manifest);
453
+ }
454
+ catch (error) {
455
+ if (error instanceof TypeError && error.message === 'active judgment graph contains a cycle') {
456
+ throw new TypeError('selectable judgment graph contains a cycle');
457
+ }
458
+ throw error;
459
+ }
460
+ }
461
+ }
462
+ export function validateManifestLock(lock, previous = null, current = null) {
463
+ if (!lock || lock.schema_version !== 'brainbase-judgment-manifest-lock-v1' || !Array.isArray(lock.entries) || lock.entries.length === 0) {
464
+ throw new TypeError('judgment manifest lock schema is invalid');
465
+ }
466
+ const versions = new Set();
467
+ for (const entry of lock.entries) {
468
+ if (!entry || typeof entry.runtime_version !== 'string' || !entry.runtime_version)
469
+ throw new TypeError('judgment manifest lock runtime version is invalid');
470
+ if (versions.has(entry.runtime_version))
471
+ throw new TypeError(`judgment manifest lock duplicate runtime version '${entry.runtime_version}'`);
472
+ versions.add(entry.runtime_version);
473
+ if (typeof entry.manifest_digest !== 'string' || !/^[a-f0-9]{64}$/.test(entry.manifest_digest))
474
+ throw new TypeError('judgment manifest lock digest is invalid');
475
+ }
476
+ if (previous) {
477
+ if (previous.schema_version !== lock.schema_version || !Array.isArray(previous.entries) || lock.entries.length < previous.entries.length) {
478
+ throw new TypeError('judgment manifest lock is append-only');
479
+ }
480
+ for (let index = 0; index < previous.entries.length; index += 1) {
481
+ if (canonicalJson(previous.entries[index]) !== canonicalJson(lock.entries[index]))
482
+ throw new TypeError('judgment manifest lock is append-only');
483
+ }
484
+ }
485
+ if (current) {
486
+ const last = lock.entries.at(-1);
487
+ if (last.runtime_version !== current.runtimeVersion || last.manifest_digest !== current.manifestDigest)
488
+ throw new TypeError('judgment manifest lock current pair does not match');
489
+ }
490
+ return true;
491
+ }
492
+ export function validateManifest(manifest, lock) {
493
+ if (!manifest || manifest.schema_version !== 'brainbase-judgment-runtime-v1' || typeof manifest.runtime_version !== 'string' || !manifest.runtime_version)
494
+ throw new TypeError('judgment manifest schema is invalid');
495
+ if (!Array.isArray(manifest.host_bindings) || !Array.isArray(manifest.policies) || !Array.isArray(manifest.nodes) || !Array.isArray(manifest.dags) || !Array.isArray(manifest.composition_edges))
496
+ throw new TypeError('judgment manifest collections are invalid');
497
+ const hostIds = new Set();
498
+ for (const host of manifest.host_bindings) {
499
+ if (!host || !ADAPTER_ID_PATTERN.test(host.adapter_id) || !ADAPTER_VERSION_PATTERN.test(host.adapter_version))
500
+ throw new TypeError('judgment manifest host binding is invalid');
501
+ const key = `${host.adapter_id}:${host.adapter_version}`;
502
+ if (hostIds.has(key))
503
+ throw new TypeError('judgment manifest has duplicate host binding');
504
+ hostIds.add(key);
505
+ }
506
+ const policies = new Set();
507
+ manifest.policies.forEach((policy) => validatePolicy(policy, policies));
508
+ const nodes = new Set();
509
+ for (const node of manifest.nodes) {
510
+ if (!node || typeof node.id !== 'string' || !node.id || nodes.has(node.id))
511
+ throw new TypeError('judgment manifest has duplicate or invalid node id');
512
+ nodes.add(node.id);
513
+ if (node.execution_contract !== undefined && node.execution_contract !== 'judgment-node-evidence-v1')
514
+ throw new TypeError(`judgment node ${node.id} execution contract is invalid`);
515
+ if (!NODE_KINDS.has(node.kind))
516
+ throw new TypeError(`judgment node ${node.id} kind is invalid`);
517
+ if (typeof node.instruction !== 'string' || !node.instruction)
518
+ throw new TypeError(`judgment node ${node.id} instruction is invalid`);
519
+ if (node.required_capability_template !== null && (typeof node.required_capability_template !== 'string' || !node.required_capability_template))
520
+ throw new TypeError(`judgment node ${node.id} capability reference is invalid`);
521
+ if (node.kind === 'capability' && typeof node.required_capability_template !== 'string')
522
+ throw new TypeError(`judgment node ${node.id} capability reference is required`);
523
+ }
524
+ const dags = new Set();
525
+ for (const dag of manifest.dags) {
526
+ if (!dag || typeof dag.id !== 'string' || dags.has(dag.id) || !Array.isArray(dag.path) || dag.path.length === 0)
527
+ throw new TypeError('judgment manifest has duplicate, invalid, or empty DAG');
528
+ dags.add(dag.id);
529
+ if (!DAG_KINDS.has(dag.kind) || !Array.isArray(dag.policy_ids))
530
+ throw new TypeError(`judgment DAG ${dag.id} schema is invalid`);
531
+ if (new Set(dag.path).size !== dag.path.length)
532
+ throw new TypeError(`judgment DAG ${dag.id} contains a cycle`);
533
+ if (new Set(dag.policy_ids).size !== dag.policy_ids.length)
534
+ throw new TypeError(`judgment DAG ${dag.id} contains duplicate policy references`);
535
+ for (const nodeId of dag.path)
536
+ if (!nodes.has(nodeId))
537
+ throw new TypeError(`judgment DAG ${dag.id} references missing node ${nodeId}`);
538
+ for (const policyId of dag.policy_ids)
539
+ if (!policies.has(policyId))
540
+ throw new TypeError(`judgment DAG ${dag.id} references missing policy ${policyId}`);
541
+ }
542
+ const compositionEdges = new Set();
543
+ for (const edge of manifest.composition_edges) {
544
+ if (!Array.isArray(edge) || edge.length !== 2 || edge.some((nodeId) => typeof nodeId !== 'string' || !nodes.has(nodeId))) {
545
+ throw new TypeError('judgment composition edge references a missing or invalid node');
546
+ }
547
+ if (edge[0] === edge[1])
548
+ throw new TypeError('judgment composition edge cannot reference itself');
549
+ const key = `${edge[0]}\u0000${edge[1]}`;
550
+ if (compositionEdges.has(key))
551
+ throw new TypeError('judgment manifest has duplicate composition edge');
552
+ compositionEdges.add(key);
553
+ }
554
+ const selectors = manifest.selectors;
555
+ validateStringTerms(selectors?.domain_order, 'judgment domain order');
556
+ validateStringTerms(selectors?.signal_order, 'judgment signal order');
557
+ if (new Set(selectors.domain_order).size !== selectors.domain_order.length || selectors.domain_order.length !== DOMAINS.size || selectors.domain_order.some((value) => !DOMAINS.has(value)))
558
+ throw new TypeError('judgment domain order is invalid');
559
+ if (new Set(selectors.signal_order).size !== selectors.signal_order.length || selectors.signal_order.length !== SIGNALS.size || selectors.signal_order.some((value) => !SIGNALS.has(value)))
560
+ throw new TypeError('judgment signal order is invalid');
561
+ validateExactKeys(selectors.domain_dags, DOMAINS, 'judgment domain selectors');
562
+ validateExactKeys(selectors.signal_dags, SIGNALS, 'judgment signal selectors');
563
+ const selectorDagIds = [
564
+ ...Object.values(selectors.domain_dags),
565
+ ...Object.values(selectors.signal_dags),
566
+ selectors.authority_dag,
567
+ selectors.clarification_dag
568
+ ];
569
+ if (Object.hasOwn(selectors, 'engineering_implementation_dag'))
570
+ selectorDagIds.push(selectors.engineering_implementation_dag);
571
+ for (const dagId of selectorDagIds)
572
+ if (!dags.has(dagId))
573
+ throw new TypeError(`judgment selector references missing DAG ${dagId}`);
574
+ const matchers = manifest.semantic_matchers;
575
+ validateExactKeys(matchers?.intents, INTENT_MATCHERS, 'judgment intent matchers');
576
+ validateExactKeys(matchers?.domains, DOMAIN_MATCHERS, 'judgment domain matchers');
577
+ validateExactKeys(matchers?.signals, SIGNALS, 'judgment signal matchers');
578
+ validateExactKeys(matchers?.safety, SAFETY_MATCHERS, 'judgment safety matchers');
579
+ for (const [key, terms] of Object.entries(matchers.intents))
580
+ validateStringTerms(terms, `judgment intent matcher ${key}`);
581
+ for (const [key, terms] of Object.entries(matchers.domains))
582
+ validateStringTerms(terms, `judgment domain matcher ${key}`);
583
+ for (const [key, terms] of Object.entries(matchers.signals))
584
+ validateStringTerms(terms, `judgment signal matcher ${key}`);
585
+ for (const [key, terms] of Object.entries(matchers.safety))
586
+ validateStringTerms(terms, `judgment safety matcher ${key}`);
587
+ validateStringTerms(matchers.positive_commands, 'judgment positive command matcher');
588
+ validateStringTerms(matchers.follow_up, 'judgment follow-up matcher');
589
+ const autonomy = manifest.autonomy;
590
+ if (!autonomy || autonomy.schema_version !== 'brainbase-autonomy-policy-v1')
591
+ throw new TypeError('judgment autonomy policy is invalid');
592
+ validateStringTerms(autonomy.continue_risks, 'judgment autonomy continue risks');
593
+ validateStringTerms(autonomy.escalate_risks, 'judgment autonomy escalate risks', { allowEmpty: true });
594
+ validateStringTerms(autonomy.escalate_action_kinds, 'judgment autonomy escalate action kinds', { allowEmpty: true });
595
+ validateStringTerms(autonomy.runtime_escalation_reasons, 'judgment autonomy runtime escalation reasons');
596
+ // escalate_risks / escalate_action_kinds are no longer consulted by autonomyResolution
597
+ // (classification alone never auto-escalates); they stay schema-validated so a stale
598
+ // manifest cannot silently reintroduce a hidden auto-escalation source.
599
+ if (canonicalJson(autonomy.continue_risks) !== canonicalJson(['low', 'medium'])
600
+ || autonomy.escalate_risks.some((risk) => !RISKS.includes(risk))
601
+ || autonomy.escalate_action_kinds.some((kind) => !ACTIONS.includes(kind))
602
+ || autonomy.runtime_escalation_reasons.length !== RUNTIME_ESCALATION_REASONS.size
603
+ || autonomy.runtime_escalation_reasons.some((reason) => !RUNTIME_ESCALATION_REASONS.has(reason))) {
604
+ throw new TypeError('judgment autonomy policy boundary is invalid');
605
+ }
606
+ validateSelectableGraphs(manifest);
607
+ const digest = sha256Hex(canonicalJson(manifest));
608
+ validateManifestLock(lock, null, { runtimeVersion: manifest.runtime_version, manifestDigest: digest });
609
+ return digest;
610
+ }
611
+ // Policies whose `human_approval` rule matches this turn's classification, in
612
+ // deterministic policy-id order. A non-empty result is the only source of a
613
+ // risk_or_external escalation now — classification alone never auto-escalates.
614
+ function humanApprovalPolicyIds(classification, applicablePolicies) {
615
+ const matched = [];
616
+ for (const policy of applicablePolicies) {
617
+ const rule = policy?.human_approval;
618
+ if (!rule)
619
+ continue;
620
+ const riskMatch = Array.isArray(rule.risks) && rule.risks.includes(classification.risk);
621
+ const actionMatch = Array.isArray(rule.action_kinds) && rule.action_kinds.includes(classification.action_kind);
622
+ if (riskMatch || actionMatch)
623
+ matched.push(policy.id);
624
+ }
625
+ return matched.sort(compareCodePoints);
626
+ }
627
+ export function autonomyResolution(status, classification, manifest, applicablePolicies = []) {
628
+ let decision;
629
+ let reasonCode;
630
+ let policyIds = [];
631
+ if (status === 'needs_classification') {
632
+ decision = 'escalate';
633
+ reasonCode = 'classification_missing';
634
+ }
635
+ else if (status === 'needs_policy_resolution') {
636
+ decision = 'escalate';
637
+ reasonCode = 'policy_conflict';
638
+ }
639
+ else {
640
+ policyIds = humanApprovalPolicyIds(classification, applicablePolicies);
641
+ if (policyIds.length > 0) {
642
+ decision = 'escalate';
643
+ reasonCode = 'risk_or_external';
644
+ }
645
+ else {
646
+ decision = 'continue';
647
+ reasonCode = 'routine_in_scope';
648
+ }
649
+ }
650
+ if (!AUTONOMY_DECISIONS.has(decision) || !AUTONOMY_REASON_CODES.has(reasonCode)) {
651
+ throw new TypeError('judgment autonomy resolution is invalid');
652
+ }
653
+ return {
654
+ autonomy_decision: decision,
655
+ autonomy_reason_code: reasonCode,
656
+ allowed_runtime_escalation_reasons: decision === 'continue'
657
+ ? [...manifest.autonomy.runtime_escalation_reasons]
658
+ : [],
659
+ autonomy_policy_ids: policyIds
660
+ };
661
+ }
662
+ export function validateRuntimeInput(rawInput, manifest) {
663
+ exactFields(rawInput, INPUT_FIELDS, 'input');
664
+ const request = requiredText(rawInput.request, 'request');
665
+ const turnId = requiredString(rawInput.turn_id, 'turn_id');
666
+ if (turnId.length > 128 || /[\u0000-\u001f\u007f]/u.test(turnId))
667
+ fail('turn_id is invalid');
668
+ if (rawInput.conversation_context === undefined) {
669
+ fail('conversation_context is required');
670
+ }
671
+ let conversationContext = null;
672
+ {
673
+ exactFields(rawInput.conversation_context, CONVERSATION_CONTEXT_FIELDS, 'conversation_context');
674
+ if (rawInput.conversation_context.schema_version !== 'brainbase-conversation-context-v1') {
675
+ fail('conversation_context.schema_version is invalid');
676
+ }
677
+ const messages = rawInput.conversation_context.messages;
678
+ if (!Array.isArray(messages))
679
+ fail('conversation_context.messages must be an array');
680
+ const validatedMessages = messages.map((message, index) => {
681
+ exactFields(message, MESSAGE_FIELDS, `conversation_context.messages[${index}]`);
682
+ if (message.sequence !== index)
683
+ fail('conversation_context.messages sequence is invalid');
684
+ if (!['user', 'assistant'].includes(message.role))
685
+ fail('conversation_context.messages role is invalid');
686
+ if (message.turn_id !== null && (typeof message.turn_id !== 'string' || !message.turn_id))
687
+ fail('conversation_context.messages turn_id is invalid');
688
+ if (message.phase !== null && (typeof message.phase !== 'string' || !message.phase))
689
+ fail('conversation_context.messages phase is invalid');
690
+ return {
691
+ sequence: index,
692
+ turn_id: message.turn_id,
693
+ role: message.role,
694
+ phase: message.phase,
695
+ text: requiredText(message.text, `conversation_context.messages[${index}].text`)
696
+ };
697
+ });
698
+ const priorReceipts = rawInput.conversation_context.prior_receipts;
699
+ if (!Array.isArray(priorReceipts))
700
+ fail('conversation_context.prior_receipts must be an array');
701
+ const validatedPriorReceipts = priorReceipts.map((receipt, index) => {
702
+ exactFields(receipt, PRIOR_RECEIPT_FIELDS, `conversation_context.prior_receipts[${index}]`);
703
+ const digestFields = ['request_digest', 'plan_digest'];
704
+ for (const field of digestFields)
705
+ if (!/^[a-f0-9]{64}$/u.test(String(receipt[field])))
706
+ fail(`conversation_context.prior_receipts[${index}].${field} is invalid`);
707
+ if (receipt.context_digest !== null && !/^[a-f0-9]{64}$/u.test(String(receipt.context_digest)))
708
+ fail(`conversation_context.prior_receipts[${index}].context_digest is invalid`);
709
+ return {
710
+ turn_id: requiredString(receipt.turn_id, `conversation_context.prior_receipts[${index}].turn_id`),
711
+ resolution_id: requiredString(receipt.resolution_id, `conversation_context.prior_receipts[${index}].resolution_id`),
712
+ request_digest: receipt.request_digest,
713
+ context_digest: receipt.context_digest,
714
+ plan_digest: receipt.plan_digest,
715
+ classification: validatedClassification(receipt.classification, `conversation_context.prior_receipts[${index}].classification`, manifest),
716
+ selected_dag_ids: stringArray(receipt.selected_dag_ids, `conversation_context.prior_receipts[${index}].selected_dag_ids`)
717
+ };
718
+ });
719
+ exactFields(rawInput.conversation_context.runtime, RUNTIME_FIELDS, 'conversation_context.runtime');
720
+ const runtime = rawInput.conversation_context.runtime;
721
+ if (!RUNTIME_HOSTS.has(runtime.host))
722
+ fail('conversation_context.runtime.host is invalid');
723
+ if (runtime.entrypoint !== undefined && !RUNTIME_ENTRYPOINTS.has(runtime.entrypoint)) {
724
+ fail('conversation_context.runtime.entrypoint is invalid');
725
+ }
726
+ for (const nullable of ['model', 'permission_mode', 'project_binding']) {
727
+ if (runtime[nullable] !== null && (typeof runtime[nullable] !== 'string' || !runtime[nullable]))
728
+ fail(`conversation_context.runtime.${nullable} is invalid`);
729
+ }
730
+ const instructionBindings = rawInput.conversation_context.instruction_bindings;
731
+ if (!Array.isArray(instructionBindings))
732
+ fail('conversation_context.instruction_bindings must be an array');
733
+ const validatedInstructionBindings = instructionBindings.map((binding, index) => {
734
+ exactFields(binding, INSTRUCTION_BINDING_FIELDS, `conversation_context.instruction_bindings[${index}]`);
735
+ if (!/^[a-f0-9]{64}$/u.test(String(binding.digest)))
736
+ fail(`conversation_context.instruction_bindings[${index}].digest is invalid`);
737
+ return {
738
+ scope: requiredString(binding.scope, `conversation_context.instruction_bindings[${index}].scope`),
739
+ source_ref: requiredString(binding.source_ref, `conversation_context.instruction_bindings[${index}].source_ref`),
740
+ digest: binding.digest
741
+ };
742
+ });
743
+ if (!['complete', 'partial'].includes(rawInput.conversation_context.completeness))
744
+ fail('conversation_context.completeness is invalid');
745
+ if (!/^[a-f0-9]{64}$/u.test(String(rawInput.conversation_context.session_ref)))
746
+ fail('conversation_context.session_ref is invalid');
747
+ if (!/^[a-f0-9]{64}$/u.test(String(rawInput.conversation_context.source_digest)))
748
+ fail('conversation_context.source_digest is invalid');
749
+ const currentMessages = validatedMessages.filter((message) => message.turn_id === turnId);
750
+ const currentMessage = validatedMessages.at(-1);
751
+ if (currentMessages.length !== 1 || currentMessage?.role !== 'user' || currentMessage.turn_id !== turnId || currentMessage.text !== request) {
752
+ fail('conversation_context must end with the exact current request exactly once');
753
+ }
754
+ const { source_digest: _sourceDigest, ...sourceContext } = rawInput.conversation_context;
755
+ if (sha256Hex(canonicalJson(sourceContext)) !== rawInput.conversation_context.source_digest) {
756
+ fail('conversation_context.source_digest does not match canonical context');
757
+ }
758
+ if (rawInput.project_code !== undefined && runtime.project_binding !== rawInput.project_code) {
759
+ fail('conversation_context.runtime.project_binding must match project_code');
760
+ }
761
+ const normalizedRuntime = {
762
+ host: runtime.host,
763
+ model: runtime.model,
764
+ permission_mode: runtime.permission_mode,
765
+ project_binding: runtime.project_binding
766
+ };
767
+ if (runtime.entrypoint !== undefined)
768
+ normalizedRuntime.entrypoint = runtime.entrypoint;
769
+ conversationContext = {
770
+ schema_version: rawInput.conversation_context.schema_version,
771
+ session_ref: rawInput.conversation_context.session_ref,
772
+ messages: validatedMessages,
773
+ prior_receipts: validatedPriorReceipts,
774
+ runtime: normalizedRuntime,
775
+ instruction_bindings: validatedInstructionBindings,
776
+ completeness: rawInput.conversation_context.completeness,
777
+ source_digest: rawInput.conversation_context.source_digest
778
+ };
779
+ }
780
+ return {
781
+ request,
782
+ turn_id: turnId,
783
+ project_code: rawInput.project_code === undefined ? null : requiredString(rawInput.project_code, 'project_code'),
784
+ conversation_context: conversationContext,
785
+ model_interpretation: rawInput.model_interpretation === undefined
786
+ ? null
787
+ : validatedClassification(rawInput.model_interpretation, 'model_interpretation', manifest)
788
+ };
789
+ }
790
+ export function reconcileModelInterpretation(input, manifest) {
791
+ if (!input.model_interpretation) {
792
+ return {
793
+ status: 'needs_classification', classification: null, assurance: 'unknown',
794
+ reasons: ['model_interpretation_missing'],
795
+ evidence: { source: 'resolver', source_turn_ids: [], matcher_ids: [] }
796
+ };
797
+ }
798
+ // Host matchers are monotonic safety rails: they may only add risk,
799
+ // action floors, signals, and capabilities. They never replace or weaken
800
+ // the model's semantic interpretation.
801
+ const hardSignals = classify(input, manifest);
802
+ const hardClassification = hardSignals.classification;
803
+ const hardIsSpecific = hardClassification
804
+ && !(hardClassification.intent === 'answer' && hardClassification.domains.length === 1 && hardClassification.domains[0] === 'general');
805
+ const classification = {
806
+ ...input.model_interpretation,
807
+ intent: input.model_interpretation.intent,
808
+ domains: hardIsSpecific
809
+ ? (input.model_interpretation.domains.length === 1 && input.model_interpretation.domains[0] === 'general'
810
+ ? hardClassification.domains
811
+ : sortByOrder([...new Set([...input.model_interpretation.domains, ...hardClassification.domains])], manifest.selectors.domain_order))
812
+ : input.model_interpretation.domains,
813
+ action_kind: hardClassification
814
+ ? indexFloor(ACTIONS, input.model_interpretation.action_kind, hardClassification.action_kind)
815
+ : input.model_interpretation.action_kind,
816
+ risk: hardClassification
817
+ ? indexFloor(RISKS, input.model_interpretation.risk, hardClassification.risk)
818
+ : input.model_interpretation.risk,
819
+ signals: sortByOrder([
820
+ ...new Set([
821
+ ...input.model_interpretation.signals,
822
+ ...(hardClassification?.signals ?? [])
823
+ ])
824
+ ], manifest.selectors.signal_order)
825
+ };
826
+ // Project discovery belongs to knowledge retrieval. An unbound task still
827
+ // has a valid semantic classification; this receipt grants no data access.
828
+ return {
829
+ status: 'resolved',
830
+ classification,
831
+ assurance: classification.confidence === 'confirmed' ? 'verified' : 'bounded',
832
+ reasons: hardSignals.reasons,
833
+ evidence: {
834
+ source: 'current_request', source_turn_ids: [input.turn_id],
835
+ matcher_ids: [...new Set(['model_interpretation', ...(hardSignals.evidence?.matcher_ids ?? [])])]
836
+ }
837
+ };
838
+ }
839
+ function matchingKeys(text, matchers, order) {
840
+ return order.filter((key) => includesTerm(text, matchers[key]));
841
+ }
842
+ // Write/operate intent matchers must describe a requested command. State or
843
+ // temporal mentions still remain available to the independent safety floors.
844
+ function intentCommandTerms(terms) {
845
+ return terms.flatMap((term) => {
846
+ if (/^[a-z0-9_.-]+$/iu.test(term))
847
+ return [term];
848
+ if (/[、,;;。!?!?]$/u.test(term) || /(?:して|って|んで|いて|せよ|しろ)$/u.test(term)) {
849
+ return [term];
850
+ }
851
+ return [`${term}して`, `${term}しろ`, `${term}せよ`];
852
+ });
853
+ }
854
+ function includesRequestedIntentTerm(request, terms) {
855
+ return includesPositiveCommandClause(request, intentCommandTerms(terms));
856
+ }
857
+ function matchingIntent(text, manifest) {
858
+ return INTENT_ORDER.find((intent) => {
859
+ const terms = manifest.semantic_matchers.intents[intent];
860
+ return ['implement', 'operate'].includes(intent)
861
+ ? includesRequestedIntentTerm(text, terms)
862
+ : includesTerm(text, terms);
863
+ }) || null;
864
+ }
865
+ function includesFollowUpTerm(text, terms) {
866
+ const normalized = text.toLocaleLowerCase('ja');
867
+ return terms.some((term) => {
868
+ const normalizedTerm = term.toLocaleLowerCase('ja');
869
+ if (normalizedTerm === 'これ' || normalizedTerm === 'こちら') {
870
+ return new RegExp(`${escapeRegExp(normalizedTerm)}(?:は(?:[??]|どう|何|どれ|どこ|いつ|誰|なぜ)|(?!は))`, 'u').test(normalized);
871
+ }
872
+ if (normalizedTerm === 'では') {
873
+ return /^(?:では)(?:[、,\s]|$)/u.test(normalized.trimStart());
874
+ }
875
+ return includesTerm(normalized, [normalizedTerm]);
876
+ });
877
+ }
878
+ function classificationFromPriorContext(input, manifest) {
879
+ const context = input.conversation_context;
880
+ if (!context)
881
+ return null;
882
+ const priorReceipts = [...context.prior_receipts].reverse().filter((receipt) => receipt.turn_id !== input.turn_id);
883
+ const domainReceipt = priorReceipts.find((receipt) => receipt.classification.domains.some((domain) => domain !== 'general'));
884
+ if (domainReceipt)
885
+ return { classification: domainReceipt.classification, turn_ids: [domainReceipt.turn_id], source: 'prior_receipt' };
886
+ for (let index = context.messages.length - 1; index >= 0; index -= 1) {
887
+ const message = context.messages[index];
888
+ if (message.role !== 'user' || message.turn_id === input.turn_id)
889
+ continue;
890
+ const domains = matchingKeys(message.text, manifest.semantic_matchers.domains, manifest.selectors.domain_order.filter((domain) => domain !== 'general'));
891
+ if (domains.length === 0)
892
+ continue;
893
+ const signals = matchingKeys(message.text, manifest.semantic_matchers.signals, manifest.selectors.signal_order);
894
+ const intent = matchingIntent(message.text, manifest) || 'answer';
895
+ return {
896
+ classification: {
897
+ intent,
898
+ domains,
899
+ action_kind: actionFloor(intent),
900
+ risk: actionFloor(intent) === 'write' ? 'medium' : 'low',
901
+ confidence: 'inferred',
902
+ signals
903
+ },
904
+ turn_ids: message.turn_id ? [message.turn_id] : [],
905
+ source: 'prior_message'
906
+ };
907
+ }
908
+ const fallbackReceipt = priorReceipts[0];
909
+ if (fallbackReceipt)
910
+ return { classification: fallbackReceipt.classification, turn_ids: [fallbackReceipt.turn_id], source: 'prior_receipt' };
911
+ return null;
912
+ }
913
+ export function classify(input, manifest) {
914
+ const matchers = manifest.semantic_matchers;
915
+ const request = positiveClassificationRequest(classificationRequest(input.request), manifest);
916
+ const detectedDomains = matchingKeys(request, matchers.domains, manifest.selectors.domain_order.filter((domain) => domain !== 'general'));
917
+ const detectedSignals = matchingKeys(request, matchers.signals, manifest.selectors.signal_order);
918
+ const detectedIntent = matchingIntent(request, manifest);
919
+ const followsPrior = includesFollowUpTerm(request, matchers.follow_up);
920
+ const prior = followsPrior ? classificationFromPriorContext(input, manifest) : null;
921
+ const inheritedDomains = detectedDomains.length === 0 && prior ? prior.classification.domains.filter((domain) => domain !== 'general') : [];
922
+ const inheritedSignals = detectedSignals.length === 0 && prior ? prior.classification.signals : [];
923
+ const domains = detectedDomains.length > 0
924
+ ? detectedDomains
925
+ : inheritedDomains.length > 0
926
+ ? inheritedDomains
927
+ : followsPrior && !prior
928
+ ? []
929
+ : ['general'];
930
+ const signals = detectedSignals.length > 0 ? detectedSignals : inheritedSignals;
931
+ const intent = detectedIntent || (prior ? prior.classification.intent : followsPrior ? null : 'answer');
932
+ if (!intent || domains.length === 0) {
933
+ return {
934
+ status: 'needs_classification', classification: null, assurance: 'unknown',
935
+ reasons: ['conversation_referent_missing'],
936
+ evidence: { source: 'resolver', source_turn_ids: [], matcher_ids: [] }
937
+ };
938
+ }
939
+ const detectedAction = includesRequestedEffectTerm(request, matchers.safety.external)
940
+ ? 'external'
941
+ : includesRequestedEffectTerm(request, matchers.safety.write)
942
+ ? 'write'
943
+ : 'none';
944
+ const minimumAction = indexFloor(ACTIONS, actionFloor(intent), detectedAction);
945
+ const minimumRisk = includesUnprohibitedTerm(request, matchers.safety.critical)
946
+ ? 'critical'
947
+ : minimumAction === 'external'
948
+ ? 'high'
949
+ : minimumAction === 'write'
950
+ ? 'medium'
951
+ : 'low';
952
+ const inherited = detectedDomains.length === 0 && Boolean(prior);
953
+ const matcherIds = [
954
+ ...(detectedIntent ? [`intent:${detectedIntent}`] : []),
955
+ ...detectedDomains.map((domain) => `domain:${domain}`),
956
+ ...detectedSignals.map((signal) => `signal:${signal}`),
957
+ ...(detectedAction !== 'none' ? [`effect:${detectedAction}`] : [])
958
+ ];
959
+ return {
960
+ status: 'resolved',
961
+ classification: {
962
+ intent,
963
+ domains: sortByOrder(domains, manifest.selectors.domain_order),
964
+ action_kind: minimumAction,
965
+ risk: minimumRisk,
966
+ confidence: inherited ? 'inferred' : 'confirmed',
967
+ signals: sortByOrder(signals, manifest.selectors.signal_order)
968
+ },
969
+ assurance: inherited ? 'bounded' : 'verified',
970
+ reasons: inherited ? ['classification_inherited_from_prior_turn'] : [],
971
+ evidence: {
972
+ source: inherited ? prior.source : 'current_request',
973
+ source_turn_ids: inherited ? prior.turn_ids : [input.turn_id],
974
+ matcher_ids: matcherIds
975
+ }
976
+ };
977
+ }
978
+ function policyScopeId(policy, ownerPersonId) {
979
+ return policy.scope.id === '$personal_owner' ? ownerPersonId : policy.scope.id;
980
+ }
981
+ export function materializePolicy(policy, ownerPersonId) {
982
+ return {
983
+ ...policy,
984
+ scope: { ...policy.scope, id: policyScopeId(policy, ownerPersonId) },
985
+ owner_person_id: policy.owner_person_id === '$personal_owner' ? ownerPersonId : policy.owner_person_id
986
+ };
987
+ }
988
+ function policyComparator(left, right) {
989
+ if (left.strength !== right.strength)
990
+ return left.strength === 'hard' ? -1 : 1;
991
+ if (left.priority !== right.priority)
992
+ return right.priority - left.priority;
993
+ const scopeDelta = SCOPE_SPECIFICITY[right.scope.type] - SCOPE_SPECIFICITY[left.scope.type];
994
+ if (scopeDelta !== 0)
995
+ return scopeDelta;
996
+ return compareCodePoints(left.id, right.id);
997
+ }
998
+ export function mergePolicies(policies) {
999
+ const sorted = [...policies].sort(policyComparator);
1000
+ const applicable = [];
1001
+ const suppressed = [];
1002
+ let conflict = false;
1003
+ const byTarget = new Map();
1004
+ for (const policy of sorted) {
1005
+ const target = policy.effect.target;
1006
+ if (!byTarget.has(target))
1007
+ byTarget.set(target, []);
1008
+ byTarget.get(target).push(policy);
1009
+ }
1010
+ for (const group of byTarget.values()) {
1011
+ const hard = group.filter((policy) => policy.strength === 'hard');
1012
+ const soft = group.filter((policy) => policy.strength === 'soft');
1013
+ let winningHard = hard;
1014
+ const decisions = new Set(hard.map((policy) => policy.effect.decision));
1015
+ if (decisions.has('require') && decisions.has('forbid')) {
1016
+ const winner = hard[0];
1017
+ const tied = hard.filter((policy) => policy.priority === winner.priority && SCOPE_SPECIFICITY[policy.scope.type] === SCOPE_SPECIFICITY[winner.scope.type]);
1018
+ if (new Set(tied.map((policy) => policy.effect.decision)).size > 1) {
1019
+ conflict = true;
1020
+ winningHard = tied;
1021
+ for (const policy of hard.filter((candidate) => !tied.includes(candidate))) {
1022
+ suppressed.push({
1023
+ policy_id: policy.id,
1024
+ suppressed_by_policy_id: winner.id,
1025
+ reason: policy.priority < winner.priority ? 'lower_priority' : 'lower_specificity'
1026
+ });
1027
+ }
1028
+ }
1029
+ else {
1030
+ winningHard = [winner];
1031
+ for (const policy of hard.slice(1)) {
1032
+ suppressed.push({
1033
+ policy_id: policy.id,
1034
+ suppressed_by_policy_id: winner.id,
1035
+ reason: policy.priority < winner.priority ? 'lower_priority' : 'lower_specificity'
1036
+ });
1037
+ }
1038
+ }
1039
+ }
1040
+ applicable.push(...winningHard);
1041
+ if (hard.length > 0) {
1042
+ for (const policy of soft)
1043
+ suppressed.push({ policy_id: policy.id, suppressed_by_policy_id: hard[0].id, reason: 'hard_over_soft' });
1044
+ }
1045
+ else {
1046
+ applicable.push(...soft);
1047
+ }
1048
+ }
1049
+ return { applicable: applicable.sort(policyComparator), suppressed, conflict };
1050
+ }
1051
+ export function selectedDags(classification, manifest) {
1052
+ const selected = [];
1053
+ for (const domain of classification.domains) {
1054
+ if (classification.intent === 'answer'
1055
+ && ['none', 'read'].includes(classification.action_kind)
1056
+ && classification.risk === 'low'
1057
+ && ['engineering', 'operations'].includes(domain)) {
1058
+ selected.push(manifest.selectors.domain_dags.general);
1059
+ continue;
1060
+ }
1061
+ const implementationDag = domain === 'engineering' && classification.intent === 'implement'
1062
+ ? manifest.selectors.engineering_implementation_dag : null;
1063
+ selected.push(implementationDag ?? manifest.selectors.domain_dags[domain]);
1064
+ }
1065
+ for (const signal of classification.signals)
1066
+ selected.push(manifest.selectors.signal_dags[signal]);
1067
+ if (['high', 'critical'].includes(classification.risk) || ['write', 'external'].includes(classification.action_kind) || classification.signals.includes('authority_boundary')) {
1068
+ selected.push(manifest.selectors.authority_dag);
1069
+ }
1070
+ return [...new Set(selected)];
1071
+ }
1072
+ export function buildGraph(dagIds, manifest, { clarification = false } = {}) {
1073
+ if (clarification) {
1074
+ return {
1075
+ active_nodes: ['entry', 'reconcile', 'clarification', 'receipt'],
1076
+ active_edges: [['entry', 'reconcile'], ['reconcile', 'clarification'], ['clarification', 'receipt']]
1077
+ };
1078
+ }
1079
+ const dagMap = new Map(manifest.dags.map((dag) => [dag.id, dag]));
1080
+ const activeNodes = ['entry', 'reconcile'];
1081
+ const activeEdges = [['entry', 'reconcile']];
1082
+ const addNode = (node) => { if (!activeNodes.includes(node))
1083
+ activeNodes.push(node); };
1084
+ const addEdge = (from, to) => {
1085
+ if (!activeEdges.some((edge) => edge[0] === from && edge[1] === to))
1086
+ activeEdges.push([from, to]);
1087
+ };
1088
+ for (const dagId of dagIds) {
1089
+ const path = dagMap.get(dagId).path;
1090
+ path.forEach(addNode);
1091
+ addEdge('reconcile', path[0]);
1092
+ for (let index = 1; index < path.length; index += 1)
1093
+ addEdge(path[index - 1], path[index]);
1094
+ addEdge(path.at(-1), 'merge');
1095
+ }
1096
+ addNode('merge');
1097
+ addNode('receipt');
1098
+ addEdge('merge', 'receipt');
1099
+ const activeNodeSet = new Set(activeNodes);
1100
+ for (const [from, to] of manifest.composition_edges) {
1101
+ if (activeNodeSet.has(from) && activeNodeSet.has(to))
1102
+ addEdge(from, to);
1103
+ }
1104
+ const orderedNodes = topologicallySortNodes(activeNodes, activeEdges);
1105
+ return { active_nodes: orderedNodes, active_edges: activeEdges };
1106
+ }
1107
+ export function materializeActiveNodeDefinitions(activeNodes, manifest, { evidenceRequired = true } = {}) {
1108
+ const nodeMap = new Map(manifest.nodes.map((node) => [node.id, node]));
1109
+ return activeNodes.map((nodeId) => {
1110
+ const node = nodeMap.get(nodeId);
1111
+ if (!node)
1112
+ throw new TypeError(`active judgment graph references missing node ${nodeId}`);
1113
+ return {
1114
+ id: node.id,
1115
+ kind: node.kind,
1116
+ instruction: node.instruction,
1117
+ required_capability_template: node.required_capability_template,
1118
+ ...(evidenceRequired && ['problem-frame', 'observe', 'falsify'].every((id) => activeNodes.includes(id))
1119
+ && node.execution_contract ? { execution_contract: node.execution_contract } : {})
1120
+ };
1121
+ });
1122
+ }
1123
+ export function topologicallySortNodes(nodes, edges) {
1124
+ const indegree = new Map(nodes.map((node) => [node, 0]));
1125
+ const outgoing = new Map(nodes.map((node) => [node, []]));
1126
+ const originalOrder = new Map(nodes.map((node, index) => [node, index]));
1127
+ for (const [from, to] of edges) {
1128
+ if (!indegree.has(from) || !indegree.has(to))
1129
+ throw new TypeError('active judgment graph references a missing node');
1130
+ indegree.set(to, indegree.get(to) + 1);
1131
+ outgoing.get(from).push(to);
1132
+ }
1133
+ const queue = nodes.filter((node) => indegree.get(node) === 0);
1134
+ const ordered = [];
1135
+ while (queue.length > 0) {
1136
+ const node = queue.shift();
1137
+ ordered.push(node);
1138
+ for (const target of outgoing.get(node)) {
1139
+ indegree.set(target, indegree.get(target) - 1);
1140
+ if (indegree.get(target) === 0) {
1141
+ queue.push(target);
1142
+ queue.sort((left, right) => originalOrder.get(left) - originalOrder.get(right));
1143
+ }
1144
+ }
1145
+ }
1146
+ if (ordered.length !== nodes.length)
1147
+ throw new TypeError('active judgment graph contains a cycle');
1148
+ return ordered;
1149
+ }
1150
+ // ADR-014 F1: every resolved turn selects the philosophy, objectives, and
1151
+ // world models that bear on the response and records how each was used.
1152
+ // Emitted only to clients that declared support, because older MCP receipt
1153
+ // validators reject unknown capabilities and would break every turn.
1154
+ export const JUDGMENT_FRAME_CAPABILITY = 'judgment.frame';
1155
+ const JUDGMENT_FRAME_INTENTS = new Set(['design', 'review', 'operate']);
1156
+ const JUDGMENT_FRAME_DOMAINS = new Set(['personal_judgment', 'organization']);
1157
+ export function isJudgmentBearing(classification) {
1158
+ if (!classification)
1159
+ return false;
1160
+ return JUDGMENT_FRAME_INTENTS.has(classification.intent)
1161
+ || classification.domains.some((domain) => JUDGMENT_FRAME_DOMAINS.has(domain));
1162
+ }
1163
+ export function frameCapabilities(input, classification, clientCapabilities, status) {
1164
+ if (status !== 'resolved' || !clientCapabilities.has(JUDGMENT_FRAME_CAPABILITY) || !classification)
1165
+ return [];
1166
+ return [{
1167
+ capability: JUDGMENT_FRAME_CAPABILITY,
1168
+ status: 'required',
1169
+ input: {
1170
+ kinds: ['philosophy', 'objective', 'model'],
1171
+ project_code: input.project_code
1172
+ },
1173
+ receipt_required: true
1174
+ }];
1175
+ }
1176
+ export function knowledgeCapabilities(input, classification) {
1177
+ if (!classification?.domains.includes('knowledge'))
1178
+ return [];
1179
+ const contentType = classification.domains.includes('personal_judgment')
1180
+ ? 'personal_knowledge'
1181
+ : classification.domains.includes('operations')
1182
+ ? 'operational_state'
1183
+ : 'unknown';
1184
+ return [{
1185
+ capability: 'knowledge.resolve',
1186
+ status: 'required',
1187
+ input: {
1188
+ intent: 'lookup',
1189
+ audience: classification.domains.includes('personal_judgment') ? 'personal' : 'team',
1190
+ content_type: contentType,
1191
+ project_code: input.project_code
1192
+ },
1193
+ receipt_required: true
1194
+ }];
1195
+ }
1196
+ /**
1197
+ * Resolve the deterministic runtime plan from normalized, already-authorized
1198
+ * inputs. `applicablePolicies` must have been filtered by the Org/Host adapter;
1199
+ * this core deliberately has no access or tenant lookup and never broadens a
1200
+ * deny decision.
1201
+ */
1202
+ export function resolveJudgmentRuntimeCore({ input, manifest, applicablePolicies = [], ownerPersonId = null, clientCapabilities = [] }) {
1203
+ const reconciliation = reconcileModelInterpretation(input, manifest);
1204
+ const needsClassification = reconciliation.status === 'needs_classification';
1205
+ const dagIds = needsClassification
1206
+ ? [manifest.selectors.clarification_dag]
1207
+ : selectedDags(reconciliation.classification, manifest);
1208
+ const graph = buildGraph(dagIds, manifest, { clarification: needsClassification });
1209
+ const policies = needsClassification
1210
+ ? { applicable: [], suppressed: [], conflict: false }
1211
+ : mergePolicies(applicablePolicies.map((policy) => materializePolicy(policy, ownerPersonId)));
1212
+ const status = needsClassification
1213
+ ? 'needs_classification'
1214
+ : policies.conflict
1215
+ ? 'needs_policy_resolution'
1216
+ : 'resolved';
1217
+ const autonomy = autonomyResolution(status, reconciliation.classification, manifest, policies.applicable);
1218
+ const activeNodeDefinitions = materializeActiveNodeDefinitions(graph.active_nodes, manifest, {
1219
+ evidenceRequired: !(reconciliation.classification?.intent === 'answer'
1220
+ && ['none', 'read'].includes(reconciliation.classification?.action_kind)
1221
+ && reconciliation.classification?.risk === 'low')
1222
+ });
1223
+ return {
1224
+ status,
1225
+ ...autonomy,
1226
+ reconciliation,
1227
+ selected_dag_ids: dagIds,
1228
+ applicable_policies: policies.applicable,
1229
+ suppressed_policies: policies.suppressed,
1230
+ policy_conflict: policies.conflict,
1231
+ required_capabilities: status === 'needs_classification' ? [] : [
1232
+ ...knowledgeCapabilities(input, reconciliation.classification),
1233
+ ...frameCapabilities(input, reconciliation.classification, new Set(clientCapabilities), status)
1234
+ ],
1235
+ ...graph,
1236
+ active_node_definitions: activeNodeDefinitions,
1237
+ unresolved: status === 'needs_classification'
1238
+ ? reconciliation.reasons
1239
+ : status === 'needs_policy_resolution'
1240
+ ? ['policy_conflict']
1241
+ : [],
1242
+ rationale: status === 'needs_classification'
1243
+ ? ['Model semantic interpretation is required before the server can issue a TurnContract.']
1244
+ : status === 'needs_policy_resolution'
1245
+ ? ['Equally authoritative hard policies conflict; resolve policy before proceeding.']
1246
+ : ['The server-owned manifest selected only the judgment branches required by this turn.']
1247
+ };
1248
+ }