@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,635 @@
1
+ // @ts-nocheck
2
+ import { createHash } from 'node:crypto';
3
+ const CONTRACT_VERSION = 'run_receipt.v1';
4
+ const SOURCE_TYPES = new Set(['mana', 'codex_automations', 'github_actions', 'salestailor', 'openryoko', 'openclaw']);
5
+ const RUN_STATUSES = new Set(['success', 'failed', 'blocked', 'waiting_human', 'cancelled']);
6
+ const EVIDENCE_STATES = new Set(['confirmed', 'unconfirmed', 'no_data']);
7
+ const OBSERVATION_KINDS = new Set(['source_run', 'connector_observation']);
8
+ const ACTIONS = new Set([
9
+ 'none',
10
+ 'check_error',
11
+ 'resolve_blocker',
12
+ 'review_run',
13
+ 'retry_run',
14
+ 'reauthorize',
15
+ 'contact_owner'
16
+ ]);
17
+ const EVIDENCE_KINDS = new Set(['url', 'artifact_ref', 'log_ref']);
18
+ const JUDGMENT_TRACE_SCHEMA_VERSION = 'meeting_judgment_trace.v1';
19
+ const TRACE_COVERAGE_STATES = new Set(['confirmed', 'partial', 'unknown']);
20
+ const TRACE_GRAPH_PLAYBOOK_STATUSES = new Set(['executed', 'partial', 'not_executed', 'unknown']);
21
+ const TRACE_NODE_STATUSES = new Set(['completed', 'skipped', 'blocked', 'unknown']);
22
+ const TRACE_FEEDBACK_ACTIONS = new Set(['adopt', 'correct', 'reject', 'not_useful']);
23
+ const FORBIDDEN_KEYS = new Set([
24
+ 'content',
25
+ 'body',
26
+ 'raw_log',
27
+ 'rawLog',
28
+ 'transcript',
29
+ 'customer_text',
30
+ 'customerText',
31
+ 'payload'
32
+ ]);
33
+ const TOP_LEVEL_KEYS = new Set(['contract_version', 'source', 'run', 'delivery']);
34
+ const SOURCE_KEYS = new Set(['type', 'workflow_id', 'name', 'runtime_target']);
35
+ const RUN_KEYS = new Set([
36
+ 'project_id',
37
+ 'org_id',
38
+ 'external_run_id',
39
+ 'parent_external_run_id',
40
+ 'workflow_name',
41
+ 'status',
42
+ 'evidence_state',
43
+ 'started_at',
44
+ 'finished_at',
45
+ 'summary',
46
+ 'blocker_reason',
47
+ 'action_required',
48
+ 'observation_kind',
49
+ 'metrics',
50
+ 'evidence_refs',
51
+ 'judgment_trace'
52
+ ]);
53
+ const DELIVERY_KEYS = new Set(['idempotency_key', 'attempt', 'sent_at']);
54
+ const EVIDENCE_KEYS = new Set(['kind', 'ref', 'label']);
55
+ const TRACE_KEYS = new Set([
56
+ 'schema_version',
57
+ 'dag',
58
+ 'graph_playbook_status',
59
+ 'nodes',
60
+ 'glossary',
61
+ 'quality',
62
+ 'corrections',
63
+ 'replay'
64
+ ]);
65
+ const TRACE_DAG_KEYS = new Set(['id', 'version']);
66
+ const TRACE_NODE_KEYS = new Set([
67
+ 'id',
68
+ 'node_id',
69
+ 'status',
70
+ 'outcome',
71
+ 'event_id',
72
+ 'sequence',
73
+ 'node_version',
74
+ 'next_branch',
75
+ 'started_at',
76
+ 'finished_at',
77
+ 'input_refs',
78
+ 'evidence_refs'
79
+ ]);
80
+ const TRACE_GLOSSARY_KEYS = new Set(['coverage', 'term_refs', 'unresolved_count']);
81
+ const TRACE_TERM_KEYS = new Set(['term_id', 'surface_form', 'entity_ref', 'evidence_refs']);
82
+ const TRACE_STATE_KEYS = new Set(['status', 'evidence_refs', 'issue_codes']);
83
+ const TRACE_CORRECTION_KEYS = new Set([
84
+ 'feedback_id',
85
+ 'corrects_event_id',
86
+ 'action',
87
+ 'reason',
88
+ 'evidence_refs',
89
+ 'replacement'
90
+ ]);
91
+ const TRACE_REPLACEMENT_KEYS = new Set(['event_id', 'subject_type', 'subject_id', 'summary', 'evidence_refs']);
92
+ const TRACE_REPLAY_KEYS = new Set([
93
+ 'status',
94
+ 'evidence_refs',
95
+ 'verified_run_ids',
96
+ 'changed_version_refs',
97
+ 'original_failure_refs',
98
+ 'separate_case_refs'
99
+ ]);
100
+ const OPAQUE_REF_PATTERN = /^[a-z][a-z0-9_+.-]{1,31}:[^\s]{1,2000}$/;
101
+ const ROUTINE_ARTIFACT_REF_PATTERN = /^routine-artifacts\/[a-z0-9_-]+\/[a-f0-9]{64}\.json$/;
102
+ const EMBEDDED_CREDENTIAL_PATTERN = /^[a-z][a-z0-9+.-]{1,31}:(?:\/\/)?[^/?#\s@]+(?::[^/?#\s@]*)?@/i;
103
+ const RFC3339_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
104
+ const CONTROL_OR_NEWLINE_PATTERN = /[\u0000-\u001f\u007f]/;
105
+ const METRIC_CONTENT_PATTERN = /(?:^|[_-])(content|body|raw|log|transcript|customer)(?:$|[_-])/i;
106
+ export class RunReceiptContractError extends Error {
107
+ code;
108
+ details;
109
+ constructor(code, message, details = {}) {
110
+ super(message);
111
+ this.name = 'RunReceiptContractError';
112
+ this.code = code;
113
+ this.details = details;
114
+ }
115
+ }
116
+ function fail(code, message, details = {}) {
117
+ throw new RunReceiptContractError(code, message, details);
118
+ }
119
+ function isObject(value) {
120
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
121
+ }
122
+ function requireObject(value, path) {
123
+ if (!isObject(value))
124
+ fail('invalid_object', `${path} must be an object`, { path });
125
+ return value;
126
+ }
127
+ function rejectForbiddenKeys(value, path = 'receipt') {
128
+ if (Array.isArray(value)) {
129
+ value.forEach((item, index) => rejectForbiddenKeys(item, `${path}[${index}]`));
130
+ return;
131
+ }
132
+ if (!isObject(value))
133
+ return;
134
+ Object.entries(value).forEach(([key, child]) => {
135
+ if (FORBIDDEN_KEYS.has(key)) {
136
+ fail('forbidden_key', `${path}.${key} is forbidden`, { path: `${path}.${key}` });
137
+ }
138
+ rejectForbiddenKeys(child, `${path}.${key}`);
139
+ });
140
+ }
141
+ function rejectUnknownKeys(value, allowed, path) {
142
+ Object.keys(value).forEach((key) => {
143
+ if (!allowed.has(key))
144
+ fail('unknown_field', `${path}.${key} is not supported`, { path: `${path}.${key}` });
145
+ });
146
+ }
147
+ function requireString(value, path, maxLength = 200) {
148
+ if (typeof value !== 'string' || value.trim() === '') {
149
+ fail('missing_string', `${path} is required`, { path });
150
+ }
151
+ if (value !== value.trim())
152
+ fail('invalid_string', `${path} must not have surrounding whitespace`, { path });
153
+ if (value.length > maxLength)
154
+ fail('string_too_long', `${path} exceeds ${maxLength} characters`, { path, max_length: maxLength });
155
+ if (CONTROL_OR_NEWLINE_PATTERN.test(value))
156
+ fail('invalid_string', `${path} must be single-line and control-character free`, { path });
157
+ return value;
158
+ }
159
+ function optionalString(value, path, maxLength) {
160
+ if (value === undefined || value === null)
161
+ return undefined;
162
+ return requireString(value, path, maxLength);
163
+ }
164
+ function requireEnum(value, path, allowed, code) {
165
+ const normalized = requireString(value, path);
166
+ if (!allowed.has(normalized))
167
+ fail(code, `${path}=${normalized} is not supported`, { path, value: normalized });
168
+ return normalized;
169
+ }
170
+ function optionalEnum(value, path, allowed, code) {
171
+ if (value === undefined || value === null)
172
+ return undefined;
173
+ return requireEnum(value, path, allowed, code);
174
+ }
175
+ function validateTimestamp(value, path, required = false) {
176
+ if (value === undefined || value === null) {
177
+ if (required)
178
+ fail('missing_timestamp', `${path} is required`, { path });
179
+ return undefined;
180
+ }
181
+ const timestamp = requireString(value, path, 64);
182
+ const epoch = Date.parse(timestamp);
183
+ if (!RFC3339_PATTERN.test(timestamp) || !Number.isFinite(epoch)) {
184
+ fail('invalid_timestamp', `${path} must be RFC 3339`, { path });
185
+ }
186
+ return { timestamp, epoch };
187
+ }
188
+ function sha256(value) {
189
+ return createHash('sha256').update(value).digest('hex');
190
+ }
191
+ function stableValue(value) {
192
+ if (Array.isArray(value))
193
+ return value.map(stableValue);
194
+ if (!isObject(value))
195
+ return value;
196
+ return Object.fromEntries(Object.keys(value)
197
+ .filter((key) => value[key] !== undefined)
198
+ .sort()
199
+ .map((key) => [key, stableValue(value[key])]));
200
+ }
201
+ function compactJson(value) {
202
+ return JSON.stringify(stableValue(value));
203
+ }
204
+ function sortEvidenceRefs(refs) {
205
+ return [...refs].sort((left, right) => (left.kind.localeCompare(right.kind)
206
+ || left.ref.localeCompare(right.ref)
207
+ || String(left.label || '').localeCompare(String(right.label || ''))));
208
+ }
209
+ function hasIndependentEvidence(leftRefs, rightRefs) {
210
+ const left = new Set(leftRefs.map((ref) => `${ref.kind}:${ref.ref}`));
211
+ return rightRefs.some((ref) => !left.has(`${ref.kind}:${ref.ref}`));
212
+ }
213
+ function validateMetrics(value) {
214
+ if (value === undefined || value === null)
215
+ return undefined;
216
+ const metrics = requireObject(value, 'run.metrics');
217
+ const normalized = {};
218
+ Object.keys(metrics).sort().forEach((key) => {
219
+ requireString(key, `run.metrics.${key}`, 120);
220
+ if (METRIC_CONTENT_PATTERN.test(key) || FORBIDDEN_KEYS.has(key)) {
221
+ fail('forbidden_metric_name', `run.metrics.${key} is content-like`, { path: `run.metrics.${key}` });
222
+ }
223
+ const metric = metrics[key];
224
+ if (metric !== null && typeof metric !== 'boolean' && !(typeof metric === 'number' && Number.isFinite(metric))) {
225
+ fail('invalid_metric_value', `run.metrics.${key} must be a finite number, boolean, or null`, {
226
+ path: `run.metrics.${key}`
227
+ });
228
+ }
229
+ normalized[key] = metric;
230
+ });
231
+ return normalized;
232
+ }
233
+ function validateEvidenceRefs(value, path = 'run.evidence_refs', maxLength = null) {
234
+ if (value === undefined || value === null)
235
+ return [];
236
+ if (!Array.isArray(value))
237
+ fail('invalid_array', `${path} must be an array`, { path });
238
+ if (maxLength !== null && value.length > maxLength) {
239
+ fail('array_too_long', `${path} exceeds ${maxLength} entries`, { path, max_length: maxLength });
240
+ }
241
+ const refs = value.map((entry, index) => {
242
+ const entryPath = `${path}[${index}]`;
243
+ const ref = requireObject(entry, entryPath);
244
+ rejectUnknownKeys(ref, EVIDENCE_KEYS, entryPath);
245
+ const kind = requireEnum(ref.kind, `${entryPath}.kind`, EVIDENCE_KINDS, 'unsupported_evidence_kind');
246
+ const reference = requireString(ref.ref, `${entryPath}.ref`, 2048);
247
+ const label = optionalString(ref.label, `${entryPath}.label`, 120);
248
+ if (EMBEDDED_CREDENTIAL_PATTERN.test(reference)) {
249
+ fail('invalid_evidence_ref', `${entryPath}.ref must not contain embedded credentials`, { path: `${entryPath}.ref` });
250
+ }
251
+ if (kind === 'url') {
252
+ let parsed;
253
+ try {
254
+ parsed = new URL(reference);
255
+ }
256
+ catch {
257
+ fail('invalid_evidence_ref', `${entryPath}.ref must be an absolute HTTPS URL`, { path: `${entryPath}.ref` });
258
+ }
259
+ if (parsed.protocol !== 'https:' || parsed.username || parsed.password) {
260
+ fail('invalid_evidence_ref', `${entryPath}.ref must be an absolute HTTPS URL without credentials`, { path: `${entryPath}.ref` });
261
+ }
262
+ }
263
+ else if (!OPAQUE_REF_PATTERN.test(reference) && !ROUTINE_ARTIFACT_REF_PATTERN.test(reference)) {
264
+ fail('invalid_evidence_ref', `${entryPath}.ref must be a source-owned opaque reference`, { path: `${entryPath}.ref` });
265
+ }
266
+ return { kind, ref: reference, ...(label ? { label } : {}) };
267
+ });
268
+ return sortEvidenceRefs(refs);
269
+ }
270
+ function validateCount(value, path) {
271
+ if (value === undefined || value === null)
272
+ return null;
273
+ if (!Number.isSafeInteger(value) || value < 0) {
274
+ fail('invalid_count', `${path} must be a non-negative safe integer`, { path });
275
+ }
276
+ return value;
277
+ }
278
+ function validateCodeList(value, path, maxLength = 32) {
279
+ if (value === undefined || value === null)
280
+ return [];
281
+ if (!Array.isArray(value))
282
+ fail('invalid_array', `${path} must be an array`, { path });
283
+ if (value.length > maxLength)
284
+ fail('array_too_long', `${path} exceeds ${maxLength} entries`, { path, max_length: maxLength });
285
+ return value.map((entry, index) => requireString(entry, `${path}[${index}]`, 120));
286
+ }
287
+ function validateStringList(value, path, maxLength = 32, itemMaxLength = 320) {
288
+ if (value === undefined || value === null)
289
+ return [];
290
+ if (!Array.isArray(value))
291
+ fail('invalid_array', `${path} must be an array`, { path });
292
+ if (value.length > maxLength)
293
+ fail('array_too_long', `${path} exceeds ${maxLength} entries`, {
294
+ path,
295
+ max_length: maxLength
296
+ });
297
+ return value.map((entry, index) => requireString(entry, `${path}[${index}]`, itemMaxLength));
298
+ }
299
+ function validateTraceState(value, path) {
300
+ const state = value === undefined || value === null ? {} : requireObject(value, path);
301
+ rejectUnknownKeys(state, TRACE_STATE_KEYS, path);
302
+ const status = optionalEnum(state.status, `${path}.status`, TRACE_COVERAGE_STATES, 'unsupported_trace_status') || 'unknown';
303
+ const evidenceRefs = validateEvidenceRefs(state.evidence_refs, `${path}.evidence_refs`, 32);
304
+ if (status === 'confirmed' && evidenceRefs.length === 0) {
305
+ fail('missing_trace_evidence', `${path}.status=confirmed requires evidence_refs`, { path });
306
+ }
307
+ return {
308
+ status,
309
+ evidence_refs: evidenceRefs,
310
+ issue_codes: validateCodeList(state.issue_codes, `${path}.issue_codes`)
311
+ };
312
+ }
313
+ function validateReplacement(value, path) {
314
+ if (value === undefined || value === null)
315
+ return undefined;
316
+ const replacement = requireObject(value, path);
317
+ rejectUnknownKeys(replacement, TRACE_REPLACEMENT_KEYS, path);
318
+ const subjectType = optionalString(replacement.subject_type, `${path}.subject_type`, 120);
319
+ const subjectId = optionalString(replacement.subject_id, `${path}.subject_id`, 300);
320
+ const summary = optionalString(replacement.summary, `${path}.summary`, 500);
321
+ const eventId = optionalString(replacement.event_id, `${path}.event_id`, 300);
322
+ const evidenceRefs = validateEvidenceRefs(replacement.evidence_refs, `${path}.evidence_refs`, 32);
323
+ return {
324
+ ...(eventId ? { event_id: eventId } : {}),
325
+ ...(subjectType ? { subject_type: subjectType } : {}),
326
+ ...(subjectId ? { subject_id: subjectId } : {}),
327
+ ...(summary ? { summary } : {}),
328
+ evidence_refs: evidenceRefs
329
+ };
330
+ }
331
+ function validateJudgmentTrace(value) {
332
+ if (value === undefined || value === null)
333
+ return undefined;
334
+ const trace = requireObject(value, 'run.judgment_trace');
335
+ rejectUnknownKeys(trace, TRACE_KEYS, 'run.judgment_trace');
336
+ const schemaVersion = requireString(trace.schema_version, 'run.judgment_trace.schema_version', 80);
337
+ if (schemaVersion !== JUDGMENT_TRACE_SCHEMA_VERSION) {
338
+ fail('unsupported_judgment_trace_version', `run.judgment_trace.schema_version must be ${JUDGMENT_TRACE_SCHEMA_VERSION}`);
339
+ }
340
+ const dag = requireObject(trace.dag, 'run.judgment_trace.dag');
341
+ rejectUnknownKeys(dag, TRACE_DAG_KEYS, 'run.judgment_trace.dag');
342
+ const dagId = requireString(dag.id, 'run.judgment_trace.dag.id', 200);
343
+ const dagVersion = requireString(dag.version, 'run.judgment_trace.dag.version', 120);
344
+ const graphPlaybookStatus = optionalEnum(trace.graph_playbook_status, 'run.judgment_trace.graph_playbook_status', TRACE_GRAPH_PLAYBOOK_STATUSES, 'unsupported_graph_playbook_status') || 'unknown';
345
+ if (!Array.isArray(trace.nodes))
346
+ fail('invalid_array', 'run.judgment_trace.nodes must be an array', { path: 'run.judgment_trace.nodes' });
347
+ if (trace.nodes.length > 256)
348
+ fail('array_too_long', 'run.judgment_trace.nodes exceeds 256 entries', {
349
+ path: 'run.judgment_trace.nodes',
350
+ max_length: 256
351
+ });
352
+ const nodes = trace.nodes.map((nodeValue, index) => {
353
+ const path = `run.judgment_trace.nodes[${index}]`;
354
+ const node = requireObject(nodeValue, path);
355
+ rejectUnknownKeys(node, TRACE_NODE_KEYS, path);
356
+ const id = requireString(node.id, `${path}.id`, 200);
357
+ const nodeId = optionalString(node.node_id, `${path}.node_id`, 200);
358
+ const status = requireEnum(node.status, `${path}.status`, TRACE_NODE_STATUSES, 'unsupported_trace_node_status');
359
+ const outcome = optionalString(node.outcome, `${path}.outcome`, 120);
360
+ const eventId = optionalString(node.event_id, `${path}.event_id`, 300);
361
+ const sequence = node.sequence === undefined || node.sequence === null
362
+ ? undefined
363
+ : validateCount(node.sequence, `${path}.sequence`);
364
+ const nodeVersion = optionalString(node.node_version, `${path}.node_version`, 120);
365
+ const nextBranch = optionalString(node.next_branch, `${path}.next_branch`, 160);
366
+ const startedAt = validateTimestamp(node.started_at, `${path}.started_at`);
367
+ const finishedAt = validateTimestamp(node.finished_at, `${path}.finished_at`);
368
+ const inputRefs = validateStringList(node.input_refs, `${path}.input_refs`, 32, 320);
369
+ return {
370
+ id,
371
+ ...(nodeId ? { node_id: nodeId } : {}),
372
+ status,
373
+ ...(outcome ? { outcome } : {}),
374
+ ...(eventId ? { event_id: eventId } : {}),
375
+ ...(sequence !== undefined && sequence !== null ? { sequence } : {}),
376
+ ...(nodeVersion ? { node_version: nodeVersion } : {}),
377
+ ...(nextBranch ? { next_branch: nextBranch } : {}),
378
+ ...(startedAt ? { started_at: startedAt.timestamp } : {}),
379
+ ...(finishedAt ? { finished_at: finishedAt.timestamp } : {}),
380
+ ...(inputRefs.length > 0 ? { input_refs: inputRefs } : {}),
381
+ evidence_refs: validateEvidenceRefs(node.evidence_refs, `${path}.evidence_refs`, 32)
382
+ };
383
+ });
384
+ const glossaryValue = trace.glossary === undefined || trace.glossary === null ? {} : requireObject(trace.glossary, 'run.judgment_trace.glossary');
385
+ rejectUnknownKeys(glossaryValue, TRACE_GLOSSARY_KEYS, 'run.judgment_trace.glossary');
386
+ const glossaryCoverage = optionalEnum(glossaryValue.coverage, 'run.judgment_trace.glossary.coverage', TRACE_COVERAGE_STATES, 'unsupported_glossary_coverage') || 'unknown';
387
+ const termValues = glossaryValue.term_refs === undefined || glossaryValue.term_refs === null
388
+ ? []
389
+ : glossaryValue.term_refs;
390
+ if (!Array.isArray(termValues))
391
+ fail('invalid_array', 'run.judgment_trace.glossary.term_refs must be an array', {
392
+ path: 'run.judgment_trace.glossary.term_refs'
393
+ });
394
+ if (termValues.length > 256)
395
+ fail('array_too_long', 'run.judgment_trace.glossary.term_refs exceeds 256 entries', {
396
+ path: 'run.judgment_trace.glossary.term_refs',
397
+ max_length: 256
398
+ });
399
+ const termRefs = termValues.map((termValue, index) => {
400
+ const path = `run.judgment_trace.glossary.term_refs[${index}]`;
401
+ const term = requireObject(termValue, path);
402
+ rejectUnknownKeys(term, TRACE_TERM_KEYS, path);
403
+ const termId = requireString(term.term_id, `${path}.term_id`, 300);
404
+ const surfaceForm = optionalString(term.surface_form, `${path}.surface_form`, 160);
405
+ const entityRef = optionalString(term.entity_ref, `${path}.entity_ref`, 300);
406
+ return {
407
+ term_id: termId,
408
+ ...(surfaceForm ? { surface_form: surfaceForm } : {}),
409
+ ...(entityRef ? { entity_ref: entityRef } : {}),
410
+ evidence_refs: validateEvidenceRefs(term.evidence_refs, `${path}.evidence_refs`, 16)
411
+ };
412
+ });
413
+ const correctionValues = trace.corrections === undefined || trace.corrections === null ? [] : trace.corrections;
414
+ if (!Array.isArray(correctionValues))
415
+ fail('invalid_array', 'run.judgment_trace.corrections must be an array', {
416
+ path: 'run.judgment_trace.corrections'
417
+ });
418
+ if (correctionValues.length > 128)
419
+ fail('array_too_long', 'run.judgment_trace.corrections exceeds 128 entries', {
420
+ path: 'run.judgment_trace.corrections',
421
+ max_length: 128
422
+ });
423
+ const corrections = correctionValues.map((correctionValue, index) => {
424
+ const path = `run.judgment_trace.corrections[${index}]`;
425
+ const correction = requireObject(correctionValue, path);
426
+ rejectUnknownKeys(correction, TRACE_CORRECTION_KEYS, path);
427
+ const feedbackId = optionalString(correction.feedback_id, `${path}.feedback_id`, 300);
428
+ const correctsEventId = requireString(correction.corrects_event_id, `${path}.corrects_event_id`, 300);
429
+ const action = requireEnum(correction.action, `${path}.action`, TRACE_FEEDBACK_ACTIONS, 'unsupported_trace_feedback_action');
430
+ const reason = optionalString(correction.reason, `${path}.reason`, 500);
431
+ const replacement = validateReplacement(correction.replacement, `${path}.replacement`);
432
+ return {
433
+ ...(feedbackId ? { feedback_id: feedbackId } : {}),
434
+ corrects_event_id: correctsEventId,
435
+ action,
436
+ ...(reason ? { reason } : {}),
437
+ evidence_refs: validateEvidenceRefs(correction.evidence_refs, `${path}.evidence_refs`, 32),
438
+ ...(replacement ? { replacement } : {})
439
+ };
440
+ });
441
+ const replayValue = trace.replay === undefined || trace.replay === null ? {} : requireObject(trace.replay, 'run.judgment_trace.replay');
442
+ rejectUnknownKeys(replayValue, TRACE_REPLAY_KEYS, 'run.judgment_trace.replay');
443
+ const replayStatus = optionalEnum(replayValue.status, 'run.judgment_trace.replay.status', TRACE_COVERAGE_STATES, 'unsupported_replay_status') || 'unknown';
444
+ const replayEvidenceRefs = validateEvidenceRefs(replayValue.evidence_refs, 'run.judgment_trace.replay.evidence_refs', 64);
445
+ const changedVersionRefs = validateEvidenceRefs(replayValue.changed_version_refs, 'run.judgment_trace.replay.changed_version_refs', 16);
446
+ const originalFailureRefs = validateEvidenceRefs(replayValue.original_failure_refs, 'run.judgment_trace.replay.original_failure_refs', 16);
447
+ const separateCaseRefs = validateEvidenceRefs(replayValue.separate_case_refs, 'run.judgment_trace.replay.separate_case_refs', 16);
448
+ if (replayStatus === 'confirmed'
449
+ && (replayEvidenceRefs.length === 0
450
+ || changedVersionRefs.length === 0
451
+ || originalFailureRefs.length === 0
452
+ || separateCaseRefs.length === 0
453
+ || !hasIndependentEvidence(originalFailureRefs, separateCaseRefs))) {
454
+ fail('missing_replay_evidence', 'run.judgment_trace.replay.status=confirmed requires changed version, original failure, and separate case evidence', {
455
+ path: 'run.judgment_trace.replay'
456
+ });
457
+ }
458
+ const verifiedRunIds = replayValue.verified_run_ids === undefined || replayValue.verified_run_ids === null
459
+ ? []
460
+ : replayValue.verified_run_ids;
461
+ if (!Array.isArray(verifiedRunIds))
462
+ fail('invalid_array', 'run.judgment_trace.replay.verified_run_ids must be an array', {
463
+ path: 'run.judgment_trace.replay.verified_run_ids'
464
+ });
465
+ if (verifiedRunIds.length > 32)
466
+ fail('array_too_long', 'run.judgment_trace.replay.verified_run_ids exceeds 32 entries', {
467
+ path: 'run.judgment_trace.replay.verified_run_ids',
468
+ max_length: 32
469
+ });
470
+ return {
471
+ schema_version: schemaVersion,
472
+ dag: { id: dagId, version: dagVersion },
473
+ graph_playbook_status: graphPlaybookStatus,
474
+ nodes,
475
+ glossary: {
476
+ coverage: glossaryCoverage,
477
+ term_refs: termRefs,
478
+ unresolved_count: validateCount(glossaryValue.unresolved_count, 'run.judgment_trace.glossary.unresolved_count')
479
+ },
480
+ quality: validateTraceState(trace.quality, 'run.judgment_trace.quality'),
481
+ corrections,
482
+ replay: {
483
+ status: replayStatus,
484
+ evidence_refs: replayEvidenceRefs,
485
+ verified_run_ids: verifiedRunIds.map((value, index) => requireString(value, `run.judgment_trace.replay.verified_run_ids[${index}]`, 300)),
486
+ changed_version_refs: changedVersionRefs,
487
+ original_failure_refs: originalFailureRefs,
488
+ separate_case_refs: separateCaseRefs
489
+ }
490
+ };
491
+ }
492
+ export function createRunReceiptIdentity({ projectId, sourceType, externalRunId, sourceWorkflowId }) {
493
+ const digest = sha256(JSON.stringify([projectId, sourceType, externalRunId]));
494
+ const workflowDigest = sha256(JSON.stringify([projectId, sourceType, sourceWorkflowId]));
495
+ return {
496
+ digest,
497
+ idempotency_key: `rr1_${digest}`,
498
+ run_id: `run_receipt_run_${digest.slice(0, 32)}`,
499
+ workflow_id: `run_receipt_wf_${workflowDigest.slice(0, 32)}`
500
+ };
501
+ }
502
+ function projectStatus(status, sourceAction) {
503
+ const defaults = {
504
+ success: ['success', 'closed', 'none', false],
505
+ failed: ['failed', 'needs_action', 'check_error', false],
506
+ blocked: ['needs_action', 'needs_action', 'resolve_blocker', false],
507
+ waiting_human: ['waiting_human', 'open', 'review_run', true],
508
+ cancelled: ['cancelled', 'closed', 'none', false]
509
+ };
510
+ const [wmcStatus, closureState, defaultAction, humanWaiting] = defaults[status];
511
+ return {
512
+ status: wmcStatus,
513
+ closure_state: closureState,
514
+ action_required: sourceAction && sourceAction !== 'none' ? sourceAction : defaultAction,
515
+ human_waiting: humanWaiting
516
+ };
517
+ }
518
+ export function normalizeRunReceipt(payload) {
519
+ const envelope = requireObject(payload, 'receipt');
520
+ rejectForbiddenKeys(envelope);
521
+ rejectUnknownKeys(envelope, TOP_LEVEL_KEYS, 'receipt');
522
+ if (envelope.contract_version !== CONTRACT_VERSION) {
523
+ fail('unsupported_contract_version', `contract_version must be ${CONTRACT_VERSION}`);
524
+ }
525
+ const sourceValue = requireObject(envelope.source, 'source');
526
+ const runValue = requireObject(envelope.run, 'run');
527
+ const deliveryValue = requireObject(envelope.delivery, 'delivery');
528
+ rejectUnknownKeys(sourceValue, SOURCE_KEYS, 'source');
529
+ rejectUnknownKeys(runValue, RUN_KEYS, 'run');
530
+ rejectUnknownKeys(deliveryValue, DELIVERY_KEYS, 'delivery');
531
+ const source = {
532
+ type: requireEnum(sourceValue.type, 'source.type', SOURCE_TYPES, 'unsupported_source_type'),
533
+ workflow_id: requireString(sourceValue.workflow_id, 'source.workflow_id'),
534
+ ...(optionalString(sourceValue.name, 'source.name', 120) ? { name: sourceValue.name } : {}),
535
+ ...(optionalString(sourceValue.runtime_target, 'source.runtime_target', 120)
536
+ ? { runtime_target: sourceValue.runtime_target }
537
+ : {})
538
+ };
539
+ const startedAt = validateTimestamp(runValue.started_at, 'run.started_at');
540
+ const finishedAt = validateTimestamp(runValue.finished_at, 'run.finished_at');
541
+ if (!startedAt && !finishedAt)
542
+ fail('missing_timestamp', 'run.started_at or run.finished_at is required');
543
+ if (startedAt && finishedAt && finishedAt.epoch < startedAt.epoch) {
544
+ fail('invalid_timestamp_order', 'run.finished_at may not precede run.started_at');
545
+ }
546
+ const evidenceRefs = validateEvidenceRefs(runValue.evidence_refs);
547
+ const metrics = validateMetrics(runValue.metrics);
548
+ const judgmentTrace = validateJudgmentTrace(runValue.judgment_trace);
549
+ const actionRequired = optionalEnum(runValue.action_required, 'run.action_required', ACTIONS, 'unsupported_action_required');
550
+ const run = {
551
+ project_id: requireString(runValue.project_id, 'run.project_id'),
552
+ external_run_id: requireString(runValue.external_run_id, 'run.external_run_id'),
553
+ status: requireEnum(runValue.status, 'run.status', RUN_STATUSES, 'unsupported_run_status'),
554
+ evidence_state: requireEnum(runValue.evidence_state, 'run.evidence_state', EVIDENCE_STATES, 'unsupported_evidence_state'),
555
+ observation_kind: optionalEnum(runValue.observation_kind, 'run.observation_kind', OBSERVATION_KINDS, 'unsupported_observation_kind') || 'source_run',
556
+ ...(optionalString(runValue.org_id, 'run.org_id', 200) ? { org_id: runValue.org_id } : {}),
557
+ ...(optionalString(runValue.parent_external_run_id, 'run.parent_external_run_id', 200)
558
+ ? { parent_external_run_id: runValue.parent_external_run_id }
559
+ : {}),
560
+ ...(optionalString(runValue.workflow_name, 'run.workflow_name', 120)
561
+ ? { workflow_name: runValue.workflow_name }
562
+ : {}),
563
+ ...(startedAt ? { started_at: startedAt.timestamp } : {}),
564
+ ...(finishedAt ? { finished_at: finishedAt.timestamp } : {}),
565
+ ...(optionalString(runValue.summary, 'run.summary', 500) ? { summary: runValue.summary } : {}),
566
+ ...(optionalString(runValue.blocker_reason, 'run.blocker_reason', 300)
567
+ ? { blocker_reason: runValue.blocker_reason }
568
+ : {}),
569
+ ...(actionRequired ? { action_required: actionRequired } : {}),
570
+ ...(metrics !== undefined ? { metrics } : {}),
571
+ ...(judgmentTrace !== undefined ? { judgment_trace: judgmentTrace } : {}),
572
+ evidence_refs: evidenceRefs
573
+ };
574
+ if (source.type === 'openclaw') {
575
+ const taskRunId = /^cron:([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}):([0-9]{13}):([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i.exec(run.external_run_id);
576
+ if (!taskRunId || taskRunId[1] !== source.workflow_id) {
577
+ fail('invalid_openclaw_run_identity', 'OpenClaw run.external_run_id must be a complete cron task run ID for source.workflow_id');
578
+ }
579
+ }
580
+ if (run.evidence_state === 'confirmed' && evidenceRefs.length === 0) {
581
+ fail('missing_confirmed_evidence', 'evidence_state=confirmed requires at least one evidence reference');
582
+ }
583
+ if (['failed', 'blocked'].includes(run.status)
584
+ && !run.blocker_reason
585
+ && (!run.action_required || run.action_required === 'none')) {
586
+ fail('missing_failure_action', `run.status=${run.status} requires blocker_reason or non-none action_required`);
587
+ }
588
+ const isConnectorObservation = run.observation_kind === 'connector_observation';
589
+ const usesConnectorObservationIdentity = source.workflow_id === '__connector_observation__';
590
+ if (isConnectorObservation !== usesConnectorObservationIdentity) {
591
+ fail('invalid_connector_observation', 'connector_observation identity and kind must match');
592
+ }
593
+ if (isConnectorObservation) {
594
+ const valid = run.status === 'blocked'
595
+ && ['no_data', 'unconfirmed'].includes(run.evidence_state)
596
+ && Boolean(run.blocker_reason);
597
+ if (!valid)
598
+ fail('invalid_connector_observation', 'connector_observation invariants are not satisfied');
599
+ }
600
+ const identity = createRunReceiptIdentity({
601
+ projectId: run.project_id,
602
+ sourceType: source.type,
603
+ externalRunId: run.external_run_id,
604
+ sourceWorkflowId: source.workflow_id
605
+ });
606
+ const idempotencyKey = requireString(deliveryValue.idempotency_key, 'delivery.idempotency_key', 68);
607
+ if (idempotencyKey !== identity.idempotency_key) {
608
+ fail('invalid_idempotency_key', 'delivery.idempotency_key does not match the canonical receipt identity');
609
+ }
610
+ if (deliveryValue.attempt !== undefined
611
+ && (!Number.isSafeInteger(deliveryValue.attempt) || deliveryValue.attempt < 1)) {
612
+ fail('invalid_delivery_attempt', 'delivery.attempt must be a positive safe integer');
613
+ }
614
+ const sentAt = validateTimestamp(deliveryValue.sent_at, 'delivery.sent_at');
615
+ const delivery = {
616
+ idempotency_key: idempotencyKey,
617
+ ...(deliveryValue.attempt !== undefined ? { attempt: deliveryValue.attempt } : {}),
618
+ ...(sentAt ? { sent_at: sentAt.timestamp } : {})
619
+ };
620
+ const immutable = stableValue({
621
+ contract_version: CONTRACT_VERSION,
622
+ source,
623
+ run
624
+ });
625
+ return {
626
+ contract_version: CONTRACT_VERSION,
627
+ source,
628
+ run,
629
+ delivery,
630
+ immutable,
631
+ identity,
632
+ payload_digest: sha256(compactJson(immutable)),
633
+ projection: projectStatus(run.status, run.action_required)
634
+ };
635
+ }