@unson/brainbase-mcp 0.6.0 → 0.7.0

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 (80) hide show
  1. package/dist/canonical-task-contract.d.ts +9 -0
  2. package/dist/canonical-task-contract.js +39 -0
  3. package/dist/canonical-task-principal.d.ts +16 -0
  4. package/dist/canonical-task-principal.js +36 -0
  5. package/dist/canonical-task-service.d.ts +251 -0
  6. package/dist/canonical-task-service.js +809 -0
  7. package/dist/company-os-evaluation.d.ts +193 -0
  8. package/dist/company-os-evaluation.js +1150 -0
  9. package/dist/company-os-impact-review.d.ts +248 -0
  10. package/dist/company-os-impact-review.js +663 -0
  11. package/dist/company-os-learning-adoption.d.ts +288 -0
  12. package/dist/company-os-learning-adoption.js +944 -0
  13. package/dist/company-os-objectives.d.ts +52 -0
  14. package/dist/company-os-objectives.js +197 -0
  15. package/dist/company-os-receipt-adapter.d.ts +109 -0
  16. package/dist/company-os-receipt-adapter.js +636 -0
  17. package/dist/constraint-exception-store.d.ts +23 -0
  18. package/dist/constraint-exception-store.js +108 -0
  19. package/dist/constraint-resolution.d.ts +175 -0
  20. package/dist/constraint-resolution.js +922 -0
  21. package/dist/decision-adapter.d.ts +145 -0
  22. package/dist/decision-adapter.js +586 -0
  23. package/dist/durable-wait-http.d.ts +51 -0
  24. package/dist/durable-wait-http.js +497 -0
  25. package/dist/durable-waits.d.ts +234 -0
  26. package/dist/durable-waits.js +944 -0
  27. package/dist/execution-authority-http.d.ts +28 -0
  28. package/dist/execution-authority-http.js +359 -0
  29. package/dist/execution-authority.d.ts +201 -0
  30. package/dist/execution-authority.js +634 -0
  31. package/dist/foundation-catalog.d.ts +26 -0
  32. package/dist/foundation-catalog.js +206 -0
  33. package/dist/foundation-constraint-store.d.ts +50 -0
  34. package/dist/foundation-constraint-store.js +215 -0
  35. package/dist/foundation-http.d.ts +90 -0
  36. package/dist/foundation-http.js +439 -0
  37. package/dist/foundation-store.d.ts +132 -0
  38. package/dist/foundation-store.js +654 -0
  39. package/dist/judgment-dag-composition.d.ts +224 -0
  40. package/dist/judgment-dag-composition.js +731 -0
  41. package/dist/judgment-dag.d.ts +2 -0
  42. package/dist/judgment-dag.js +1 -0
  43. package/dist/judgment-problem-snapshot.d.ts +185 -0
  44. package/dist/judgment-problem-snapshot.js +777 -0
  45. package/dist/judgment-view-http.d.ts +22 -0
  46. package/dist/judgment-view-http.js +128 -0
  47. package/dist/judgment-view.d.ts +222 -0
  48. package/dist/judgment-view.js +627 -0
  49. package/dist/knowledge-adapter.d.ts +112 -0
  50. package/dist/knowledge-adapter.js +691 -0
  51. package/dist/ontology-foundation.d.ts +209 -0
  52. package/dist/ontology-foundation.js +1000 -0
  53. package/dist/ontology.d.ts +13 -0
  54. package/dist/ontology.js +14 -0
  55. package/dist/organization-connection.d.ts +164 -0
  56. package/dist/organization-connection.js +428 -0
  57. package/dist/personal-knowledge.d.ts +120 -0
  58. package/dist/personal-knowledge.js +750 -0
  59. package/dist/problem-candidates.d.ts +149 -0
  60. package/dist/problem-candidates.js +898 -0
  61. package/dist/problem-selection.d.ts +238 -0
  62. package/dist/problem-selection.js +995 -0
  63. package/dist/resource-reservation-http.d.ts +31 -0
  64. package/dist/resource-reservation-http.js +371 -0
  65. package/dist/resource-reservations.d.ts +221 -0
  66. package/dist/resource-reservations.js +577 -0
  67. package/dist/server.d.ts +97 -0
  68. package/dist/server.js +180 -1
  69. package/dist/ssot.d.ts +6 -1
  70. package/dist/ssot.js +63 -5
  71. package/dist/streamable-http.d.ts +42 -0
  72. package/dist/streamable-http.js +288 -0
  73. package/dist/types.d.ts +20 -0
  74. package/dist/world-model.d.ts +181 -0
  75. package/dist/world-model.js +873 -0
  76. package/package.json +139 -3
  77. package/ui/judgment-view.css +238 -0
  78. package/ui/judgment-view.js +508 -0
  79. package/ui/objective-editor.css +180 -0
  80. package/ui/objective-editor.js +1139 -0
@@ -0,0 +1,1150 @@
1
+ import { validateEvaluationCompatibility, validateFoundationDefinition } from './ontology-foundation.js';
2
+ import { findFoundationRecord, findLatestFoundationRecord, foundationRef, foundationCatalogOrEmpty } from './foundation-catalog.js';
3
+ import { computeJudgmentProblemSnapshotId, JudgmentProblemSnapshotError, loadJudgmentProblemSnapshot } from './judgment-problem-snapshot.js';
4
+ import { loadPersonalOs, mutatePersonalOsWithSidecar, readPersonalOsSidecar } from './ssot.js';
5
+ /**
6
+ * Evaluation is an evidence record over a pinned Problem. It does not own
7
+ * Objective or Variable definitions and it does not close an OutcomeCase.
8
+ */
9
+ export const COMPANY_OS_EVALUATION_CONTRACT_VERSION = '0.1.0';
10
+ export const COMPANY_OS_EVALUATION_RECORD_CATALOG_VERSION = 1;
11
+ export const COMPANY_OS_EVALUATION_SIDECAR = 'evidence/company-os-evaluation.json';
12
+ export function createFoundationDefinitionLoadPort(store) {
13
+ if (!store || typeof store.read !== 'function') {
14
+ throw new TypeError('A foundation store with read is required');
15
+ }
16
+ return {
17
+ async readExact(reference, context) {
18
+ const record = await store.read({
19
+ id: reference.id,
20
+ type: reference.type,
21
+ revision: reference.revision
22
+ }, context);
23
+ if (!record)
24
+ return null;
25
+ if (record.digest !== reference.digest) {
26
+ throw new CompanyOsEvaluationError('integrity_mismatch', `Foundation ${reference.type}/${reference.id}@${reference.revision} has a different digest`);
27
+ }
28
+ return record;
29
+ }
30
+ };
31
+ }
32
+ export class CompanyOsEvaluationError extends Error {
33
+ code;
34
+ constructor(code, message) {
35
+ super(message);
36
+ this.name = 'CompanyOsEvaluationError';
37
+ this.code = code;
38
+ }
39
+ }
40
+ export function createCompanyOsEvaluationStore(options) {
41
+ if (!options || !isNonEmptyString(options.dataDir)) {
42
+ throw new CompanyOsEvaluationError('invalid_input', 'dataDir is required');
43
+ }
44
+ if (!options.foundation || typeof options.foundation.readExact !== 'function') {
45
+ throw new CompanyOsEvaluationError('invalid_input', 'foundation.readExact is required');
46
+ }
47
+ if (!options.outcomeCase || typeof options.outcomeCase.read !== 'function') {
48
+ throw new CompanyOsEvaluationError('invalid_input', 'outcomeCase.read is required');
49
+ }
50
+ if (!options.snapshotReferenceProvider || typeof options.snapshotReferenceProvider.resolve !== 'function') {
51
+ throw new CompanyOsEvaluationError('invalid_input', 'snapshotReferenceProvider.resolve is required');
52
+ }
53
+ return new GraphCompanyOsEvaluationStore(options);
54
+ }
55
+ class GraphCompanyOsEvaluationStore {
56
+ options;
57
+ constructor(options) {
58
+ this.options = options;
59
+ }
60
+ async evaluate(request) {
61
+ const access = assertAccessContext(request?.access);
62
+ assertEvaluationRequest(request);
63
+ const resolved = await resolveDefinitions(this.options.foundation, request.snapshot, access);
64
+ const outcomeCase = await readCanonicalOutcomeCase(this.options.outcomeCase, request.outcomeCase, access);
65
+ const predictions = await resolveMeasurements(request.predictions, resolved.variables, this.options.foundation, access, 'prediction', resolved.objective.evaluationPeriod);
66
+ const actuals = await resolveMeasurements(request.actuals, resolved.variables, this.options.foundation, access, 'actual', resolved.objective.evaluationPeriod);
67
+ const validity = normalizeJudgmentValidity(request.judgmentAtTimeValidity);
68
+ const record = buildEvaluationRecord({
69
+ id: request.id,
70
+ resolved,
71
+ outcomeCase,
72
+ predictions,
73
+ actuals,
74
+ validity,
75
+ evaluatedAt: request.evaluatedAt
76
+ });
77
+ await this.mutateCatalog((catalog, current) => {
78
+ if (catalog.records.some((existing) => existing.id === record.id)) {
79
+ throw new CompanyOsEvaluationError('revision_conflict', `Evaluation ${record.id} already exists`);
80
+ }
81
+ // The Foundation read above is repeated against the aggregate held by
82
+ // the canonical lock. This prevents a concurrent revision/ACL update
83
+ // from silently changing what the immutable evaluation points at.
84
+ assertCurrentFoundation(current, record.objectiveRef, resolved.objectiveRecord, access);
85
+ for (const variable of resolved.variables) {
86
+ assertCurrentFoundation(current, variable.reference, variable.record, access);
87
+ }
88
+ return {
89
+ ...catalog,
90
+ records: [...catalog.records, record]
91
+ };
92
+ });
93
+ const readback = await this.read(record.id, access);
94
+ if (!readback) {
95
+ throw new CompanyOsEvaluationError('readback_mismatch', `Evaluation ${record.id} was not readable after commit`);
96
+ }
97
+ return readback;
98
+ }
99
+ async read(id, access) {
100
+ const context = assertAccessContext(access);
101
+ if (!isNonEmptyString(id))
102
+ throw new CompanyOsEvaluationError('invalid_input', 'evaluation id is required');
103
+ const catalog = await this.loadCatalog();
104
+ const record = catalog.records.find((item) => item.id === id);
105
+ if (!record)
106
+ return null;
107
+ const snapshot = await loadEvaluationSnapshot(this.options, record, context);
108
+ assertSnapshotMatchesEvaluation(record, snapshot);
109
+ const resolved = await this.options.foundation.readExact(record.objectiveRef, context);
110
+ if (!resolved || resolved.definition.type !== 'objective') {
111
+ throw new CompanyOsEvaluationError('corrupt_record', `Objective for evaluation ${id} is missing or has the wrong type`);
112
+ }
113
+ await assertEvaluationRecordConsistency(record, resolved, this.options.foundation, context);
114
+ const outcomeCase = await readCanonicalOutcomeCase(this.options.outcomeCase, record.outcomeCaseRef, context);
115
+ if (!sameCaseReference(outcomeCase.reference, record.outcomeCaseRef)) {
116
+ throw new CompanyOsEvaluationError('integrity_mismatch', `OutcomeCase reference for evaluation ${id} changed`);
117
+ }
118
+ return cloneEvaluationRecord(record);
119
+ }
120
+ async list(access) {
121
+ const context = assertAccessContext(access);
122
+ const catalog = await this.loadCatalog();
123
+ const records = [];
124
+ for (const record of catalog.records) {
125
+ const visible = await this.read(record.id, context);
126
+ if (visible)
127
+ records.push(visible);
128
+ }
129
+ return Object.freeze(records);
130
+ }
131
+ async loadCatalog() {
132
+ try {
133
+ const os = await loadPersonalOs(this.options.dataDir);
134
+ if (os.graph.version !== 2) {
135
+ throw new CompanyOsEvaluationError('unsupported_graph', 'Evaluation records require canonical Graph v2');
136
+ }
137
+ return parseEvaluationCatalog(await readPersonalOsSidecar(this.options.dataDir, COMPANY_OS_EVALUATION_SIDECAR));
138
+ }
139
+ catch (error) {
140
+ if (error instanceof CompanyOsEvaluationError)
141
+ throw error;
142
+ throw new CompanyOsEvaluationError('corrupt_record', formatError(error));
143
+ }
144
+ }
145
+ async mutateCatalog(mutator) {
146
+ try {
147
+ await mutatePersonalOsWithSidecar(this.options.dataDir, COMPANY_OS_EVALUATION_SIDECAR, async (current, existingContent) => {
148
+ if (current.graph.version !== 2) {
149
+ throw new CompanyOsEvaluationError('unsupported_graph', 'Evaluation records require canonical Graph v2');
150
+ }
151
+ const catalog = parseEvaluationCatalog(existingContent);
152
+ const next = await mutator(catalog, current);
153
+ assertEvaluationCatalog(next);
154
+ return {
155
+ next: current,
156
+ sidecarContent: serializeEvaluationCatalog(next),
157
+ result: undefined
158
+ };
159
+ });
160
+ }
161
+ catch (error) {
162
+ if (error instanceof CompanyOsEvaluationError)
163
+ throw error;
164
+ throw new CompanyOsEvaluationError('corrupt_record', formatError(error));
165
+ }
166
+ }
167
+ }
168
+ async function resolveDefinitions(foundation, snapshotInput, access) {
169
+ const snapshot = await loadJudgmentProblemSnapshot({
170
+ root: snapshotInput.root,
171
+ ...(snapshotInput.snapshotId === undefined ? {} : { snapshot_id: snapshotInput.snapshotId }),
172
+ ...(snapshotInput.problemId === undefined ? {} : { problem_id: snapshotInput.problemId }),
173
+ ...(snapshotInput.revision === undefined ? {} : { revision: snapshotInput.revision }),
174
+ access,
175
+ ...(snapshotInput.accessProvider === undefined ? {} : { accessProvider: snapshotInput.accessProvider }),
176
+ reference_resolution: 'historical',
177
+ referenceProvider: snapshotInput.referenceProvider
178
+ });
179
+ const snapshotId = computeJudgmentProblemSnapshotId(snapshot);
180
+ const objectiveReferences = snapshot.references.filter((reference) => reference.kind === 'objective');
181
+ if (objectiveReferences.length !== 1) {
182
+ throw new CompanyOsEvaluationError('invalid_input', 'A Problem must pin exactly one Objective reference');
183
+ }
184
+ const objectiveRef = foundationReference(objectiveReferences[0]);
185
+ const objectiveRecord = await foundation.readExact(objectiveRef, access);
186
+ if (!objectiveRecord) {
187
+ throw new CompanyOsEvaluationError('not_found', `Objective ${objectiveRef.id}@${objectiveRef.revision} was not found`);
188
+ }
189
+ if (objectiveRecord.definition.type !== 'objective') {
190
+ throw new CompanyOsEvaluationError('integrity_mismatch', `Problem Objective reference resolved to ${objectiveRecord.definition.type}`);
191
+ }
192
+ const objective = objectiveRecord.definition;
193
+ const validation = validateFoundationDefinition(objective, { use: 'evaluation' });
194
+ if (!validation.valid) {
195
+ throw new CompanyOsEvaluationError('incompatible_measurement', `Objective cannot be evaluated: ${validation.issues.map((issue) => issue.message).join('; ')}`);
196
+ }
197
+ const criteriaReferences = snapshot.references.filter((reference) => reference.kind === 'criterion');
198
+ if (criteriaReferences.length !== objective.criteria.length || criteriaReferences.length === 0) {
199
+ throw new CompanyOsEvaluationError('integrity_mismatch', 'Problem criterion references do not match the pinned Objective criteria');
200
+ }
201
+ assertUniqueReferences(criteriaReferences, 'criterion');
202
+ const variableReferences = snapshot.references.filter((reference) => reference.kind === 'variable');
203
+ const variables = [];
204
+ for (const [criterionIndex, criterion] of objective.criteria.entries()) {
205
+ const candidates = variableReferences.filter((reference) => (reference.id === criterion.variableRef.id
206
+ && reference.revision === criterion.variableRef.revision));
207
+ if (candidates.length !== 1) {
208
+ throw new CompanyOsEvaluationError('integrity_mismatch', `Problem must pin exactly one Variable reference for criterion ${criterionIndex}`);
209
+ }
210
+ const reference = foundationReference(candidates[0]);
211
+ const record = await foundation.readExact(reference, access);
212
+ if (!record)
213
+ throw new CompanyOsEvaluationError('not_found', `Variable ${reference.id}@${reference.revision} was not found`);
214
+ if (record.definition.type !== 'variable') {
215
+ throw new CompanyOsEvaluationError('integrity_mismatch', `Criterion ${criterionIndex} resolved to ${record.definition.type}`);
216
+ }
217
+ const variableValidation = validateFoundationDefinition(record.definition, { use: 'evaluation' });
218
+ if (!variableValidation.valid) {
219
+ throw new CompanyOsEvaluationError('incompatible_measurement', `Variable ${reference.id} cannot be evaluated: ${variableValidation.issues.map((issue) => issue.message).join('; ')}`);
220
+ }
221
+ assertCriterionCompatibility(criterion, record.definition, criterionIndex);
222
+ variables.push({ criterionIndex, criterion, reference, definition: record.definition, record });
223
+ }
224
+ return { snapshot, snapshotId, objectiveRef, objective, objectiveRecord, variables };
225
+ }
226
+ /**
227
+ * A stored evaluation is only meaningful with the exact Problem snapshot that
228
+ * produced it. Re-resolve the canonical locator on every read instead of
229
+ * trusting the four fields copied into the evaluation sidecar. The resolver
230
+ * also rechecks current ACLs and exact reference digests for historical reads.
231
+ */
232
+ async function loadEvaluationSnapshot(options, record, access) {
233
+ let snapshot;
234
+ try {
235
+ snapshot = await loadJudgmentProblemSnapshot({
236
+ root: options.dataDir,
237
+ snapshot_id: record.snapshot.snapshotId,
238
+ problem_id: record.snapshot.problemId,
239
+ revision: record.snapshot.revision,
240
+ access,
241
+ reference_resolution: 'historical',
242
+ referenceProvider: options.snapshotReferenceProvider
243
+ });
244
+ }
245
+ catch (error) {
246
+ throw mapEvaluationSnapshotLoadError(error);
247
+ }
248
+ const canonicalDigest = computeJudgmentProblemSnapshotId(snapshot);
249
+ if (snapshot.problem_id !== record.snapshot.problemId
250
+ || snapshot.revision !== record.snapshot.revision
251
+ || canonicalDigest !== record.snapshot.snapshotId
252
+ || canonicalDigest !== record.snapshot.digest) {
253
+ throw new CompanyOsEvaluationError('integrity_mismatch', 'Evaluation snapshot locator or digest does not match the canonical Problem snapshot');
254
+ }
255
+ return snapshot;
256
+ }
257
+ function mapEvaluationSnapshotLoadError(error) {
258
+ if (error instanceof CompanyOsEvaluationError)
259
+ return error;
260
+ if (error instanceof JudgmentProblemSnapshotError) {
261
+ if (error.code === 'unauthorized') {
262
+ return new CompanyOsEvaluationError('authorization_denied', error.message);
263
+ }
264
+ if (error.code === 'not_found' || error.code === 'integrity_mismatch' || error.code === 'conflict') {
265
+ return new CompanyOsEvaluationError('integrity_mismatch', error.message);
266
+ }
267
+ return new CompanyOsEvaluationError('corrupt_record', error.message);
268
+ }
269
+ return new CompanyOsEvaluationError('corrupt_record', formatError(error));
270
+ }
271
+ function assertSnapshotMatchesEvaluation(record, snapshot) {
272
+ const objectiveReferences = snapshot.references.filter((reference) => reference.kind === 'objective');
273
+ if (objectiveReferences.length !== 1) {
274
+ throw new CompanyOsEvaluationError('integrity_mismatch', 'Evaluation snapshot must contain exactly one Objective reference');
275
+ }
276
+ if (!sameFoundationRef(record.objectiveRef, foundationReference(objectiveReferences[0]))) {
277
+ throw new CompanyOsEvaluationError('integrity_mismatch', 'Evaluation Objective reference does not match its Problem snapshot');
278
+ }
279
+ const variableKeys = new Set(snapshot.references
280
+ .filter((reference) => reference.kind === 'variable')
281
+ .map((reference) => foundationReferenceKey(foundationReference(reference))));
282
+ for (const criterion of record.criteria) {
283
+ if (!variableKeys.has(foundationReferenceKey(criterion.variableRef))) {
284
+ throw new CompanyOsEvaluationError('integrity_mismatch', `Evaluation criterion ${criterion.criterionIndex} is not pinned by its Problem snapshot`);
285
+ }
286
+ }
287
+ }
288
+ async function resolveMeasurements(inputs, variables, foundation, access, phase, expectedPeriod) {
289
+ if (!Array.isArray(inputs))
290
+ throw new CompanyOsEvaluationError('invalid_input', `${phase} measurements must be an array`);
291
+ const byVariable = new Map();
292
+ const normalized = [];
293
+ for (const input of inputs) {
294
+ const variable = await resolveMeasurementVariable(input, variables, foundation, access);
295
+ const measurement = normalizeMeasurement(input, variable, expectedPeriod);
296
+ const key = foundationReferenceKey(measurement.effectiveVariableRef);
297
+ if (byVariable.has(key)) {
298
+ throw new CompanyOsEvaluationError('invalid_input', `${phase} contains duplicate measurements for ${key}`);
299
+ }
300
+ byVariable.set(key, measurement);
301
+ normalized.push(measurement);
302
+ }
303
+ return { measurements: Object.freeze(normalized.map(cloneEvaluationMeasurement)), byVariable };
304
+ }
305
+ async function resolveMeasurementVariable(input, variables, foundation, access) {
306
+ if (!input || typeof input !== 'object') {
307
+ throw new CompanyOsEvaluationError('invalid_input', 'Measurement must be an object');
308
+ }
309
+ assertFoundationRef(input?.variableRef, 'measurement.variableRef');
310
+ const conversion = input.conversion;
311
+ const source = conversion?.sourceRef ?? input.variableRef;
312
+ if (!sameFoundationRef(source, input.variableRef)) {
313
+ throw new CompanyOsEvaluationError('integrity_mismatch', 'Conversion sourceRef must equal measurement.variableRef');
314
+ }
315
+ if (conversion !== undefined) {
316
+ assertFoundationRef(conversion.targetRef, 'measurement.conversion.targetRef');
317
+ if (!isNonEmptyString(conversion.provenance.id) || !isNonEmptyString(conversion.provenance.description)) {
318
+ throw new CompanyOsEvaluationError('invalid_input', 'A conversion requires explicit provenance id and description');
319
+ }
320
+ const sourceRecord = await foundation.readExact(source, access);
321
+ if (!sourceRecord)
322
+ throw new CompanyOsEvaluationError('not_found', `Conversion source ${source.id}@${source.revision} was not found`);
323
+ if (sourceRecord.definition.type !== 'variable') {
324
+ throw new CompanyOsEvaluationError('integrity_mismatch', `Conversion source ${source.id}@${source.revision} is not a Variable`);
325
+ }
326
+ }
327
+ const target = conversion?.targetRef ?? input.variableRef;
328
+ const matches = variables.filter((candidate) => sameFoundationRef(candidate.reference, target));
329
+ if (matches.length !== 1) {
330
+ throw new CompanyOsEvaluationError('incompatible_measurement', `Measurement ${input.variableRef.id}@${input.variableRef.revision} does not match a pinned criterion definition; an explicit conversion is required`);
331
+ }
332
+ if (conversion !== undefined && !sameFoundationRef(conversion.targetRef, matches[0].reference)) {
333
+ throw new CompanyOsEvaluationError('integrity_mismatch', 'Conversion target does not match the pinned criterion definition');
334
+ }
335
+ return matches[0];
336
+ }
337
+ function normalizeMeasurement(input, variable, expectedPeriod) {
338
+ if (!input || typeof input !== 'object')
339
+ throw new CompanyOsEvaluationError('invalid_input', 'Measurement must be an object');
340
+ if (!input.descriptor || typeof input.descriptor !== 'object' || Array.isArray(input.descriptor)
341
+ || Object.prototype.hasOwnProperty.call(input.descriptor, 'variableRef')) {
342
+ throw new CompanyOsEvaluationError('invalid_input', 'Measurement descriptor must not carry a variableRef');
343
+ }
344
+ if (!['observed', 'missing', 'not_arrived'].includes(input.status)) {
345
+ throw new CompanyOsEvaluationError('invalid_input', `Unknown measurement status ${String(input.status)}`);
346
+ }
347
+ if (!parseIsoTimestamp(input.recordedAt)) {
348
+ throw new CompanyOsEvaluationError('invalid_input', 'Measurement recordedAt must be an RFC3339 timestamp');
349
+ }
350
+ if (input.status === 'observed' && input.value === undefined) {
351
+ throw new CompanyOsEvaluationError('invalid_input', 'Observed measurements require a value');
352
+ }
353
+ if (input.status !== 'observed' && input.value !== undefined) {
354
+ throw new CompanyOsEvaluationError('invalid_input', 'Missing or not-arrived measurements cannot carry a value');
355
+ }
356
+ const descriptor = {
357
+ variableRef: {
358
+ id: variable.reference.id,
359
+ type: 'variable',
360
+ revision: variable.reference.revision
361
+ },
362
+ ...input.descriptor
363
+ };
364
+ const compatibility = validateEvaluationCompatibility(variable.definition, descriptor, { period: expectedPeriod });
365
+ // A measurement may not silently use a different period or definition. The
366
+ // caller can only use a different definition through an explicit conversion
367
+ // whose effective descriptor still matches the pinned criterion.
368
+ if (compatibility.issues.length > 0) {
369
+ throw new CompanyOsEvaluationError('incompatible_measurement', compatibility.issues.map((issue) => `${issue.path}: ${issue.message}`).join('; '));
370
+ }
371
+ assertValueKind(input.value, variable.definition.valueKind, input.status);
372
+ return deepFreeze({
373
+ variableRef: cloneFoundationRef(input.variableRef),
374
+ effectiveVariableRef: cloneFoundationRef(variable.reference),
375
+ descriptor: cloneJson(input.descriptor),
376
+ status: input.status,
377
+ ...(input.value === undefined ? {} : { value: input.value }),
378
+ recordedAt: input.recordedAt,
379
+ ...(input.evidenceRef === undefined ? {} : { evidenceRef: input.evidenceRef }),
380
+ ...(input.conversion === undefined ? {} : { conversion: cloneJson(input.conversion) })
381
+ });
382
+ }
383
+ function buildEvaluationRecord(input) {
384
+ if (!isNonEmptyString(input.id))
385
+ throw new CompanyOsEvaluationError('invalid_input', 'evaluation id is required');
386
+ if (!parseIsoTimestamp(input.evaluatedAt))
387
+ throw new CompanyOsEvaluationError('invalid_input', 'evaluatedAt must be an RFC3339 timestamp');
388
+ const criteria = [];
389
+ const predictionComparisons = [];
390
+ for (const variable of input.resolved.variables) {
391
+ const actual = input.actuals.byVariable.get(foundationReferenceKey(variable.reference));
392
+ const prediction = input.predictions.byVariable.get(foundationReferenceKey(variable.reference));
393
+ criteria.push(evaluateCriterion(variable, actual, input.evaluatedAt));
394
+ predictionComparisons.push(comparePrediction(variable, prediction, actual, input.evaluatedAt));
395
+ }
396
+ const achievement = aggregateAchievement(criteria);
397
+ return deepFreeze({
398
+ version: COMPANY_OS_EVALUATION_RECORD_CATALOG_VERSION,
399
+ id: input.id,
400
+ snapshot: {
401
+ snapshotId: input.resolved.snapshotId,
402
+ problemId: input.resolved.snapshot.problem_id,
403
+ revision: input.resolved.snapshot.revision,
404
+ digest: input.resolved.snapshotId
405
+ },
406
+ objectiveRef: cloneFoundationRef(input.resolved.objectiveRef),
407
+ outcomeCaseRef: cloneCaseReference(input.outcomeCase.reference),
408
+ predictions: input.predictions.measurements,
409
+ actuals: input.actuals.measurements,
410
+ criteria,
411
+ predictionComparisons,
412
+ achievement,
413
+ judgmentAtTimeValidity: input.validity,
414
+ evaluatedAt: input.evaluatedAt
415
+ });
416
+ }
417
+ function evaluateCriterion(variable, actual, evaluatedAt) {
418
+ const base = {
419
+ criterionIndex: variable.criterionIndex,
420
+ variableRef: cloneFoundationRef(variable.reference),
421
+ operator: variable.criterion.operator,
422
+ ...(variable.criterion.target === undefined ? {} : { target: variable.criterion.target })
423
+ };
424
+ if (!actual)
425
+ return { ...base, status: 'indeterminate', reason: 'actual_measurement_missing' };
426
+ if (actual.status !== 'observed')
427
+ return { ...base, status: 'indeterminate', reason: `actual_${actual.status}` };
428
+ if (Date.parse(actual.descriptor.period.until) > Date.parse(evaluatedAt)) {
429
+ return { ...base, status: 'indeterminate', reason: 'evaluation_period_not_complete' };
430
+ }
431
+ const target = variable.criterion.target;
432
+ if (target === undefined || actual.value === undefined) {
433
+ return { ...base, status: 'indeterminate', reason: 'criterion_value_unavailable' };
434
+ }
435
+ const achieved = variable.criterion.operator === 'at_least'
436
+ ? typeof actual.value === 'number' && typeof target === 'number' && actual.value >= target
437
+ : variable.criterion.operator === 'at_most'
438
+ ? typeof actual.value === 'number' && typeof target === 'number' && actual.value <= target
439
+ : Object.is(actual.value, target);
440
+ return {
441
+ ...base,
442
+ status: achieved ? 'achieved' : 'not_achieved',
443
+ reason: achieved ? 'criterion_satisfied' : 'criterion_not_satisfied'
444
+ };
445
+ }
446
+ function comparePrediction(variable, prediction, actual, evaluatedAt) {
447
+ const base = { criterionIndex: variable.criterionIndex, variableRef: cloneFoundationRef(variable.reference) };
448
+ if (!prediction || !actual)
449
+ return { ...base, status: 'indeterminate', reason: 'prediction_or_actual_missing' };
450
+ if (prediction.status !== 'observed' || actual.status !== 'observed') {
451
+ return {
452
+ ...base,
453
+ status: 'indeterminate',
454
+ reason: 'prediction_or_actual_not_observed',
455
+ predictionStatus: prediction.status,
456
+ actualStatus: actual.status
457
+ };
458
+ }
459
+ if (Date.parse(actual.descriptor.period.until) > Date.parse(evaluatedAt)) {
460
+ return {
461
+ ...base,
462
+ status: 'indeterminate',
463
+ reason: 'evaluation_period_not_complete',
464
+ predictionStatus: prediction.status,
465
+ actualStatus: actual.status,
466
+ predictionValue: prediction.value,
467
+ actualValue: actual.value
468
+ };
469
+ }
470
+ const matched = Object.is(prediction.value, actual.value);
471
+ return {
472
+ ...base,
473
+ status: matched ? 'matched' : 'missed',
474
+ reason: matched ? 'prediction_matches_actual' : 'prediction_differs_from_actual',
475
+ predictionStatus: prediction.status,
476
+ actualStatus: actual.status,
477
+ predictionValue: prediction.value,
478
+ actualValue: actual.value
479
+ };
480
+ }
481
+ function aggregateAchievement(criteria) {
482
+ if (criteria.some((criterion) => criterion.status === 'indeterminate'))
483
+ return 'indeterminate';
484
+ return criteria.every((criterion) => criterion.status === 'achieved') ? 'achieved' : 'not_achieved';
485
+ }
486
+ /**
487
+ * Rebuild all derived fields before returning an immutable record. The
488
+ * sidecar is deliberately treated as untrusted evidence: a syntactically
489
+ * valid JSON edit must not be able to turn a failed criterion into success or
490
+ * make a measurement refer to a different Foundation revision.
491
+ */
492
+ async function assertEvaluationRecordConsistency(record, objectiveRecord, foundation, access) {
493
+ if (objectiveRecord.definition.type !== 'objective') {
494
+ throw new CompanyOsEvaluationError('corrupt_record', 'Evaluation objective has the wrong type');
495
+ }
496
+ const objectiveReference = foundationRef(objectiveRecord);
497
+ if (!sameFoundationRef(record.objectiveRef, objectiveReference)) {
498
+ throw new CompanyOsEvaluationError('integrity_mismatch', 'Evaluation objective reference does not match the canonical definition');
499
+ }
500
+ const objective = objectiveRecord.definition;
501
+ const objectiveValidation = validateFoundationDefinition(objective, { use: 'evaluation' });
502
+ if (!objectiveValidation.valid) {
503
+ throw new CompanyOsEvaluationError('corrupt_record', `Evaluation objective is no longer evaluable: ${objectiveValidation.issues.map((issue) => issue.message).join('; ')}`);
504
+ }
505
+ if (record.criteria.length !== objective.criteria.length || record.criteria.length === 0
506
+ || record.predictionComparisons.length !== objective.criteria.length) {
507
+ throw new CompanyOsEvaluationError('corrupt_record', 'Evaluation derived criteria do not match the Objective');
508
+ }
509
+ const variables = [];
510
+ for (const [criterionIndex, criterion] of objective.criteria.entries()) {
511
+ const storedCriterion = record.criteria[criterionIndex];
512
+ if (!storedCriterion || storedCriterion.criterionIndex !== criterionIndex
513
+ || storedCriterion.variableRef.type !== 'variable'
514
+ || storedCriterion.variableRef.id !== criterion.variableRef.id
515
+ || storedCriterion.variableRef.revision !== criterion.variableRef.revision
516
+ || storedCriterion.operator !== criterion.operator
517
+ || !sameOptionalScalar(storedCriterion.target, criterion.target)) {
518
+ throw new CompanyOsEvaluationError('integrity_mismatch', `Evaluation criterion ${criterionIndex} does not match the Objective`);
519
+ }
520
+ const variableRecord = await foundation.readExact(storedCriterion.variableRef, access);
521
+ if (!variableRecord) {
522
+ throw new CompanyOsEvaluationError('corrupt_record', `Variable for evaluation criterion ${criterionIndex} is missing`);
523
+ }
524
+ if (variableRecord.definition.type !== 'variable') {
525
+ throw new CompanyOsEvaluationError('corrupt_record', `Evaluation criterion ${criterionIndex} resolved to ${variableRecord.definition.type}`);
526
+ }
527
+ const variableValidation = validateFoundationDefinition(variableRecord.definition, { use: 'evaluation' });
528
+ if (!variableValidation.valid) {
529
+ throw new CompanyOsEvaluationError('corrupt_record', `Variable for evaluation criterion ${criterionIndex} is not evaluable: ${variableValidation.issues.map((issue) => issue.message).join('; ')}`);
530
+ }
531
+ try {
532
+ assertCriterionCompatibility(criterion, variableRecord.definition, criterionIndex);
533
+ }
534
+ catch (error) {
535
+ throw new CompanyOsEvaluationError('corrupt_record', formatError(error));
536
+ }
537
+ variables.push({
538
+ criterionIndex,
539
+ criterion,
540
+ reference: foundationRef(variableRecord),
541
+ definition: variableRecord.definition,
542
+ record: variableRecord
543
+ });
544
+ }
545
+ const predictions = await validateStoredMeasurementSet(record.predictions, variables, foundation, access, objective.evaluationPeriod, 'prediction');
546
+ const actuals = await validateStoredMeasurementSet(record.actuals, variables, foundation, access, objective.evaluationPeriod, 'actual');
547
+ const expectedCriteria = variables.map((variable) => evaluateCriterion(variable, actuals.byVariable.get(foundationReferenceKey(variable.reference)), record.evaluatedAt));
548
+ const expectedComparisons = variables.map((variable) => comparePrediction(variable, predictions.byVariable.get(foundationReferenceKey(variable.reference)), actuals.byVariable.get(foundationReferenceKey(variable.reference)), record.evaluatedAt));
549
+ if (!sameJson(record.criteria, expectedCriteria)
550
+ || !sameJson(record.predictionComparisons, expectedComparisons)
551
+ || record.achievement !== aggregateAchievement(expectedCriteria)) {
552
+ throw new CompanyOsEvaluationError('integrity_mismatch', 'Evaluation derived results do not match its measurements and Objective');
553
+ }
554
+ }
555
+ async function validateStoredMeasurementSet(measurements, variables, foundation, access, expectedPeriod, phase) {
556
+ const byVariable = new Map();
557
+ for (const [index, measurement] of measurements.entries()) {
558
+ const variable = variables.find((candidate) => sameFoundationRef(candidate.reference, measurement.effectiveVariableRef));
559
+ if (!variable) {
560
+ throw new CompanyOsEvaluationError('integrity_mismatch', `${phase} measurement ${index} is not pinned to an Objective criterion`);
561
+ }
562
+ if (measurement.conversion !== undefined) {
563
+ const sourceRecord = await foundation.readExact(measurement.conversion.sourceRef, access);
564
+ if (!sourceRecord || sourceRecord.definition.type !== 'variable') {
565
+ throw new CompanyOsEvaluationError('corrupt_record', `${phase} measurement ${index} conversion source is missing or is not a Variable`);
566
+ }
567
+ }
568
+ else if (!sameFoundationRef(measurement.variableRef, variable.reference)) {
569
+ throw new CompanyOsEvaluationError('integrity_mismatch', `${phase} measurement ${index} changed definition without conversion provenance`);
570
+ }
571
+ const descriptor = {
572
+ variableRef: {
573
+ id: variable.reference.id,
574
+ type: 'variable',
575
+ revision: variable.reference.revision
576
+ },
577
+ ...measurement.descriptor
578
+ };
579
+ const compatibility = validateEvaluationCompatibility(variable.definition, descriptor, { period: expectedPeriod });
580
+ if (compatibility.issues.length > 0) {
581
+ throw new CompanyOsEvaluationError('integrity_mismatch', `${phase} measurement ${index} is incompatible: ${compatibility.issues.map((issue) => `${issue.path}: ${issue.message}`).join('; ')}`);
582
+ }
583
+ try {
584
+ assertValueKind(measurement.value, variable.definition.valueKind, measurement.status);
585
+ }
586
+ catch (error) {
587
+ throw new CompanyOsEvaluationError('integrity_mismatch', formatError(error));
588
+ }
589
+ const key = foundationReferenceKey(variable.reference);
590
+ if (byVariable.has(key)) {
591
+ throw new CompanyOsEvaluationError('corrupt_record', `${phase} contains duplicate measurements for ${key}`);
592
+ }
593
+ byVariable.set(key, measurement);
594
+ }
595
+ return { measurements, byVariable };
596
+ }
597
+ function sameOptionalScalar(left, right) {
598
+ return left === undefined || right === undefined ? left === right : Object.is(left, right);
599
+ }
600
+ function sameJson(left, right) {
601
+ return JSON.stringify(sortJson(left)) === JSON.stringify(sortJson(right));
602
+ }
603
+ function sortJson(value) {
604
+ if (Array.isArray(value))
605
+ return value.map(sortJson);
606
+ if (!isRecord(value))
607
+ return value;
608
+ return Object.fromEntries(Object.keys(value).sort().map((key) => [key, sortJson(value[key])]));
609
+ }
610
+ function assertCurrentFoundation(current, reference, expected, access) {
611
+ if (current.graph.version !== 2)
612
+ throw new CompanyOsEvaluationError('unsupported_graph', 'Foundation data requires Graph v2');
613
+ let actual;
614
+ let latest;
615
+ try {
616
+ const catalog = foundationCatalogOrEmpty(current.graph.foundation);
617
+ actual = findFoundationRecord(catalog, reference);
618
+ latest = findLatestFoundationRecord(catalog, reference.type, reference.id);
619
+ }
620
+ catch (error) {
621
+ throw new CompanyOsEvaluationError('corrupt_record', formatError(error));
622
+ }
623
+ if (!actual || !latest)
624
+ throw new CompanyOsEvaluationError('revision_conflict', `Foundation ${reference.type}/${reference.id}@${reference.revision} changed before evaluation commit`);
625
+ if (!aclAllowsRead(latest.definition.acl, access.principal)) {
626
+ throw new CompanyOsEvaluationError('authorization_denied', `Principal ${access.principal} cannot read ${reference.type}/${reference.id}`);
627
+ }
628
+ if (access.scope !== undefined && !scopeHasSubjectOverlap(latest.definition.scope, access.scope)) {
629
+ throw new CompanyOsEvaluationError('scope_violation', `Principal ${access.principal} cannot access ${reference.type}/${reference.id} outside the trusted scope`);
630
+ }
631
+ if (actual.digest !== expected.digest) {
632
+ throw new CompanyOsEvaluationError('revision_conflict', `Foundation ${reference.type}/${reference.id}@${reference.revision} changed before evaluation commit`);
633
+ }
634
+ }
635
+ async function readCanonicalOutcomeCase(port, input, access) {
636
+ assertOutcomeCaseReference(input);
637
+ let resolved;
638
+ try {
639
+ resolved = await port.read(input, access);
640
+ }
641
+ catch (error) {
642
+ throw error;
643
+ }
644
+ assertOutcomeCaseRead(resolved);
645
+ if (input.revision !== undefined && input.revision !== resolved.reference.revision) {
646
+ throw new CompanyOsEvaluationError('integrity_mismatch', 'OutcomeCase resolver returned a different requested revision');
647
+ }
648
+ if (input.digest !== undefined && input.digest !== resolved.reference.digest) {
649
+ throw new CompanyOsEvaluationError('integrity_mismatch', 'OutcomeCase resolver returned a different requested digest');
650
+ }
651
+ if (!aclAllowsRead(resolved.acl, access.principal)) {
652
+ throw new CompanyOsEvaluationError('authorization_denied', `Principal ${access.principal} cannot read OutcomeCase ${resolved.reference.id}`);
653
+ }
654
+ if (access.scope !== undefined && !scopeHasSubjectOverlap(resolved.scope, access.scope)) {
655
+ throw new CompanyOsEvaluationError('scope_violation', `Principal ${access.principal} cannot access OutcomeCase ${resolved.reference.id} outside the trusted scope`);
656
+ }
657
+ return {
658
+ reference: cloneCaseReference(resolved.reference),
659
+ source: cloneOutcomeCaseSource(resolved.source),
660
+ acl: cloneAcl(resolved.acl),
661
+ scope: cloneScope(resolved.scope)
662
+ };
663
+ }
664
+ function normalizeJudgmentValidity(input) {
665
+ if (!input || !['valid', 'invalid', 'indeterminate'].includes(input.status) || !isNonEmptyString(input.basis)) {
666
+ throw new CompanyOsEvaluationError('invalid_input', 'judgmentAtTimeValidity requires status and a non-empty basis');
667
+ }
668
+ if (input.evidenceRefs !== undefined && (!Array.isArray(input.evidenceRefs) || input.evidenceRefs.some((ref) => !isNonEmptyString(ref)))) {
669
+ throw new CompanyOsEvaluationError('invalid_input', 'judgmentAtTimeValidity.evidenceRefs must contain non-empty strings');
670
+ }
671
+ return deepFreeze({
672
+ status: input.status,
673
+ basis: input.basis,
674
+ ...(input.evidenceRefs === undefined ? {} : { evidenceRefs: [...input.evidenceRefs] })
675
+ });
676
+ }
677
+ function assertEvaluationRequest(request) {
678
+ if (!request || typeof request !== 'object')
679
+ throw new CompanyOsEvaluationError('invalid_input', 'Evaluation request is required');
680
+ if (!isNonEmptyString(request.id))
681
+ throw new CompanyOsEvaluationError('invalid_input', 'Evaluation id is required');
682
+ if (!request.snapshot || !isNonEmptyString(request.snapshot.root) || !request.snapshot.referenceProvider) {
683
+ throw new CompanyOsEvaluationError('invalid_input', 'A snapshot root and referenceProvider are required');
684
+ }
685
+ if (!Array.isArray(request.predictions) || !Array.isArray(request.actuals)) {
686
+ throw new CompanyOsEvaluationError('invalid_input', 'predictions and actuals must be arrays');
687
+ }
688
+ }
689
+ function assertOutcomeCaseReference(input) {
690
+ if (!input || !isNonEmptyString(input.id))
691
+ throw new CompanyOsEvaluationError('invalid_input', 'OutcomeCase id is required');
692
+ if (input.revision !== undefined && !/^[1-9]\d*$/.test(input.revision))
693
+ throw new CompanyOsEvaluationError('invalid_input', 'OutcomeCase revision must be positive');
694
+ if (input.digest !== undefined && !isDigest(input.digest))
695
+ throw new CompanyOsEvaluationError('invalid_input', 'OutcomeCase digest must be sha256:<64 lowercase hex>');
696
+ }
697
+ function assertOutcomeCaseRead(value) {
698
+ if (!value || typeof value !== 'object' || !value.reference || !isNonEmptyString(value.reference.id)
699
+ || !/^[1-9]\d*$/.test(value.reference.revision) || !isDigest(value.reference.digest)
700
+ || !isValidAcl(value.acl) || !isValidScope(value.scope)) {
701
+ throw new CompanyOsEvaluationError('integrity_mismatch', 'OutcomeCase resolver returned invalid canonical metadata');
702
+ }
703
+ assertOutcomeCaseSource(value.source);
704
+ }
705
+ function assertOutcomeCaseSource(value) {
706
+ if (!isJsonPlainObject(value) || !isNonEmptyString(value.state) || !Array.isArray(value.owner_refs)) {
707
+ throw new CompanyOsEvaluationError('integrity_mismatch', 'OutcomeCase resolver returned invalid canonical source metadata');
708
+ }
709
+ value.owner_refs.forEach((owner, index) => assertOutcomeCaseOwnerReference(owner, `source.owner_refs[${index}]`));
710
+ if (value.conditions !== undefined)
711
+ assertOutcomeCaseConditions(value.conditions, 'source.conditions');
712
+ }
713
+ function assertOutcomeCaseOwnerReference(value, label) {
714
+ if (!isJsonPlainObject(value) || (value.status !== 'typed' && value.status !== 'unknown')) {
715
+ throw new CompanyOsEvaluationError('integrity_mismatch', `${label} has an invalid status`);
716
+ }
717
+ if (value.status === 'unknown') {
718
+ if (!['not_recorded', 'legacy_untyped', 'source_unavailable'].includes(String(value.reason))) {
719
+ throw new CompanyOsEvaluationError('integrity_mismatch', `${label} has an invalid unknown reason`);
720
+ }
721
+ return;
722
+ }
723
+ if (!isJsonPlainObject(value.ref)
724
+ || !isNonEmptyString(value.ref.id)
725
+ || !isNonEmptyString(value.ref.type)
726
+ || (value.ref.revision !== undefined && !isNonEmptyString(value.ref.revision))
727
+ || (value.ref.digest !== undefined && !isNonEmptyString(value.ref.digest))) {
728
+ throw new CompanyOsEvaluationError('integrity_mismatch', `${label}.ref has an invalid shape`);
729
+ }
730
+ }
731
+ function assertOutcomeCaseConditions(value, label) {
732
+ if (!isJsonPlainObject(value)) {
733
+ throw new CompanyOsEvaluationError('integrity_mismatch', `${label} must be an object`);
734
+ }
735
+ assertOutcomeCaseConditionValue(value, label);
736
+ }
737
+ function assertOutcomeCaseConditionValue(value, label) {
738
+ if (value === null || typeof value === 'string' || typeof value === 'boolean')
739
+ return;
740
+ if (typeof value === 'number') {
741
+ if (Number.isFinite(value))
742
+ return;
743
+ throw new CompanyOsEvaluationError('integrity_mismatch', `${label} contains a non-finite number`);
744
+ }
745
+ if (Array.isArray(value)) {
746
+ value.forEach((item, index) => assertOutcomeCaseConditionValue(item, `${label}[${index}]`));
747
+ return;
748
+ }
749
+ if (isJsonPlainObject(value)) {
750
+ for (const [key, item] of Object.entries(value)) {
751
+ if (!isNonEmptyString(key)) {
752
+ throw new CompanyOsEvaluationError('integrity_mismatch', `${label} contains an empty key`);
753
+ }
754
+ assertOutcomeCaseConditionValue(item, `${label}.${key}`);
755
+ }
756
+ return;
757
+ }
758
+ throw new CompanyOsEvaluationError('integrity_mismatch', `${label} contains a non-JSON value`);
759
+ }
760
+ function assertCriterionCompatibility(criterion, variable, index) {
761
+ if (criterion.target === undefined)
762
+ throw new CompanyOsEvaluationError('incompatible_measurement', `Criterion ${index} has no target`);
763
+ if ((criterion.operator === 'at_least' || criterion.operator === 'at_most')
764
+ && (variable.valueKind !== 'number' || typeof criterion.target !== 'number' || !Number.isFinite(criterion.target))) {
765
+ throw new CompanyOsEvaluationError('incompatible_measurement', `Criterion ${index} requires a numeric Variable and finite number target`);
766
+ }
767
+ if (criterion.operator === 'equals' && !valueMatchesKind(criterion.target, variable.valueKind)) {
768
+ throw new CompanyOsEvaluationError('incompatible_measurement', `Criterion ${index} target does not match Variable valueKind`);
769
+ }
770
+ }
771
+ function assertValueKind(value, kind, status) {
772
+ if (value === undefined || status !== 'observed')
773
+ return;
774
+ if (!valueMatchesKind(value, kind))
775
+ throw new CompanyOsEvaluationError('incompatible_measurement', `Measurement value does not match Variable valueKind ${kind}`);
776
+ }
777
+ function valueMatchesKind(value, kind) {
778
+ if (kind === 'number')
779
+ return typeof value === 'number' && Number.isFinite(value);
780
+ if (kind === 'boolean')
781
+ return typeof value === 'boolean';
782
+ return typeof value === 'string';
783
+ }
784
+ function foundationReference(reference) {
785
+ if (!['objective', 'variable', 'model', 'constraint'].includes(reference.kind)) {
786
+ throw new CompanyOsEvaluationError('invalid_input', `Reference ${reference.kind} is not a Foundation definition`);
787
+ }
788
+ return {
789
+ id: reference.id,
790
+ type: reference.kind,
791
+ revision: reference.revision,
792
+ digest: reference.digest
793
+ };
794
+ }
795
+ function assertFoundationRef(value, label) {
796
+ if (!value || typeof value !== 'object' || !isNonEmptyString(value.id)
797
+ || !['objective', 'variable', 'model', 'constraint'].includes(value.type)
798
+ || !/^[1-9]\d*$/.test(String(value.revision))
799
+ || !isDigest(value.digest)) {
800
+ throw new CompanyOsEvaluationError('invalid_input', `${label} must contain id, type, revision, and digest`);
801
+ }
802
+ }
803
+ function assertUniqueReferences(references, label) {
804
+ const keys = new Set();
805
+ for (const reference of references) {
806
+ const key = `${reference.id}@${reference.revision}:${reference.digest}`;
807
+ if (keys.has(key))
808
+ throw new CompanyOsEvaluationError('integrity_mismatch', `Duplicate ${label} reference ${key}`);
809
+ keys.add(key);
810
+ }
811
+ }
812
+ function assertAccessContext(value) {
813
+ if (!value || !isNonEmptyString(value.principal))
814
+ throw new CompanyOsEvaluationError('authorization_denied', 'A trusted principal is required');
815
+ return { principal: value.principal, ...(value.scope === undefined ? {} : { scope: cloneScope(value.scope) }) };
816
+ }
817
+ function foundationReferenceKey(reference) {
818
+ return JSON.stringify([reference.type, reference.id, reference.revision, reference.digest]);
819
+ }
820
+ function sameFoundationRef(left, right) {
821
+ return left.type === right.type && left.id === right.id && left.revision === right.revision && left.digest === right.digest;
822
+ }
823
+ function sameCaseReference(left, right) {
824
+ return left.id === right.id && left.revision === right.revision && left.digest === right.digest;
825
+ }
826
+ function cloneFoundationRef(reference) {
827
+ return { id: reference.id, type: reference.type, revision: reference.revision, digest: reference.digest };
828
+ }
829
+ function cloneCaseReference(reference) {
830
+ return { id: reference.id, revision: reference.revision, digest: reference.digest };
831
+ }
832
+ function cloneOutcomeCaseSource(source) {
833
+ return {
834
+ state: source.state,
835
+ owner_refs: source.owner_refs.map((owner) => owner.status === 'typed'
836
+ ? {
837
+ status: 'typed',
838
+ ref: {
839
+ id: owner.ref.id,
840
+ type: owner.ref.type,
841
+ ...(owner.ref.revision === undefined ? {} : { revision: owner.ref.revision }),
842
+ ...(owner.ref.digest === undefined ? {} : { digest: owner.ref.digest })
843
+ }
844
+ }
845
+ : { status: 'unknown', reason: owner.reason }),
846
+ ...(source.conditions === undefined ? {} : { conditions: cloneOutcomeCaseConditions(source.conditions) })
847
+ };
848
+ }
849
+ function cloneOutcomeCaseConditions(conditions) {
850
+ return cloneOutcomeCaseConditionValue(conditions);
851
+ }
852
+ function cloneOutcomeCaseConditionValue(value) {
853
+ if (Array.isArray(value))
854
+ return value.map(cloneOutcomeCaseConditionValue);
855
+ if (isRecord(value)) {
856
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, cloneOutcomeCaseConditionValue(child)]));
857
+ }
858
+ return value;
859
+ }
860
+ function cloneEvaluationMeasurement(measurement) {
861
+ return deepFreeze(cloneJson(measurement));
862
+ }
863
+ function cloneEvaluationRecord(record) {
864
+ return deepFreeze(cloneJson(record));
865
+ }
866
+ function cloneAcl(acl) {
867
+ return { ownerId: acl.ownerId, visibility: acl.visibility, readerIds: [...acl.readerIds], writerIds: [...acl.writerIds] };
868
+ }
869
+ function cloneScope(scope) {
870
+ return { subjectIds: [...scope.subjectIds], validFrom: scope.validFrom, ...(scope.validUntil === undefined ? {} : { validUntil: scope.validUntil }) };
871
+ }
872
+ function aclAllowsRead(acl, principal) {
873
+ return acl.visibility === 'public' || acl.ownerId === principal || acl.readerIds.includes(principal) || acl.writerIds.includes(principal);
874
+ }
875
+ function scopeHasSubjectOverlap(resource, trusted) {
876
+ const trustedSubjects = new Set(trusted.subjectIds);
877
+ return resource.subjectIds.some((subjectId) => trustedSubjects.has(subjectId));
878
+ }
879
+ function isDigest(value) {
880
+ return typeof value === 'string' && /^sha256:[0-9a-f]{64}$/.test(value);
881
+ }
882
+ function isValidAcl(value) {
883
+ if (!value || typeof value !== 'object')
884
+ return false;
885
+ const candidate = value;
886
+ return isNonEmptyString(candidate.ownerId)
887
+ && ['private', 'project', 'organization', 'public'].includes(candidate.visibility)
888
+ && Array.isArray(candidate.readerIds) && candidate.readerIds.every(isNonEmptyString)
889
+ && Array.isArray(candidate.writerIds) && candidate.writerIds.every(isNonEmptyString);
890
+ }
891
+ function isValidScope(value) {
892
+ if (!value || typeof value !== 'object')
893
+ return false;
894
+ const candidate = value;
895
+ const from = parseStrictRfc3339(candidate.validFrom);
896
+ const until = candidate.validUntil === undefined ? undefined : parseStrictRfc3339(candidate.validUntil);
897
+ return Array.isArray(candidate.subjectIds) && candidate.subjectIds.length > 0
898
+ && candidate.subjectIds.every(isNonEmptyString)
899
+ && from !== undefined
900
+ && (candidate.validUntil === undefined || (until !== undefined
901
+ && until > from));
902
+ }
903
+ function parseIsoTimestamp(value) {
904
+ return typeof value === 'string' && parseStrictRfc3339(value) !== undefined;
905
+ }
906
+ function parseStrictRfc3339(value) {
907
+ const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,9}))?(Z|[+-]\d{2}:\d{2})$/.exec(value);
908
+ if (!match)
909
+ return undefined;
910
+ const year = Number(match[1]);
911
+ const month = Number(match[2]);
912
+ const day = Number(match[3]);
913
+ const hour = Number(match[4]);
914
+ const minute = Number(match[5]);
915
+ const second = Number(match[6]);
916
+ const fraction = match[7] ?? '';
917
+ const zone = match[8];
918
+ if (month < 1 || month > 12 || day < 1 || day > daysInMonth(year, month))
919
+ return undefined;
920
+ if (hour > 23 || minute > 59 || second > 59)
921
+ return undefined;
922
+ let offsetMinutes = 0;
923
+ if (zone !== 'Z') {
924
+ const sign = zone[0] === '-' ? -1 : 1;
925
+ const offsetHour = Number(zone.slice(1, 3));
926
+ const offsetMinute = Number(zone.slice(4, 6));
927
+ if (offsetHour > 23 || offsetMinute > 59)
928
+ return undefined;
929
+ offsetMinutes = sign * (offsetHour * 60 + offsetMinute);
930
+ }
931
+ const milliseconds = Number((fraction + '000').slice(0, 3));
932
+ const timestamp = Date.UTC(year, month - 1, day, hour, minute, second, milliseconds)
933
+ - offsetMinutes * 60_000;
934
+ return Number.isFinite(timestamp) ? timestamp : undefined;
935
+ }
936
+ function daysInMonth(year, month) {
937
+ if (month === 2) {
938
+ return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) ? 29 : 28;
939
+ }
940
+ return [4, 6, 9, 11].includes(month) ? 30 : 31;
941
+ }
942
+ function isNonEmptyString(value) {
943
+ return typeof value === 'string' && value.trim().length > 0 && !value.includes('\0');
944
+ }
945
+ function cloneJson(value) {
946
+ return JSON.parse(JSON.stringify(value));
947
+ }
948
+ function deepFreeze(value) {
949
+ if (!value || typeof value !== 'object' || Object.isFrozen(value))
950
+ return value;
951
+ for (const child of Object.values(value))
952
+ deepFreeze(child);
953
+ return Object.freeze(value);
954
+ }
955
+ function isRecord(value) {
956
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
957
+ }
958
+ function isJsonPlainObject(value) {
959
+ if (!isRecord(value))
960
+ return false;
961
+ try {
962
+ const prototype = Object.getPrototypeOf(value);
963
+ return prototype === Object.prototype || prototype === null;
964
+ }
965
+ catch {
966
+ return false;
967
+ }
968
+ }
969
+ function isEvaluationScalar(value) {
970
+ return typeof value === 'string'
971
+ || typeof value === 'boolean'
972
+ || (typeof value === 'number' && Number.isFinite(value));
973
+ }
974
+ function assertStoredMeasurement(value, label) {
975
+ if (!isRecord(value) || !value.variableRef || !value.effectiveVariableRef
976
+ || !value.descriptor || !isNonEmptyString(value.status)
977
+ || !parseIsoTimestamp(value.recordedAt)) {
978
+ throw new CompanyOsEvaluationError('corrupt_record', `${label} is invalid`);
979
+ }
980
+ assertFoundationRef(value.variableRef, `${label}.variableRef`);
981
+ assertFoundationRef(value.effectiveVariableRef, `${label}.effectiveVariableRef`);
982
+ if (value.variableRef.type !== 'variable' || value.effectiveVariableRef.type !== 'variable') {
983
+ throw new CompanyOsEvaluationError('corrupt_record', `${label} must reference Variables`);
984
+ }
985
+ if (!['observed', 'missing', 'not_arrived'].includes(value.status)) {
986
+ throw new CompanyOsEvaluationError('corrupt_record', `${label}.status is invalid`);
987
+ }
988
+ const descriptor = value.descriptor;
989
+ if (!isRecord(descriptor) || Object.prototype.hasOwnProperty.call(descriptor, 'variableRef')
990
+ || (descriptor.unit !== undefined && typeof descriptor.unit !== 'string')
991
+ || !isFoundationAggregation(descriptor.aggregation)
992
+ || !isNonEmptyString(descriptor.granularity)
993
+ || !isValidScope(descriptor.scope)
994
+ || !isValidPeriod(descriptor.period)) {
995
+ throw new CompanyOsEvaluationError('corrupt_record', `${label}.descriptor is invalid`);
996
+ }
997
+ if (value.status === 'observed' && !isEvaluationScalar(value.value)) {
998
+ throw new CompanyOsEvaluationError('corrupt_record', `${label} observed value is invalid`);
999
+ }
1000
+ if (value.status !== 'observed' && value.value !== undefined) {
1001
+ throw new CompanyOsEvaluationError('corrupt_record', `${label} non-observed value must be absent`);
1002
+ }
1003
+ if (value.evidenceRef !== undefined && !isNonEmptyString(value.evidenceRef)) {
1004
+ throw new CompanyOsEvaluationError('corrupt_record', `${label}.evidenceRef is invalid`);
1005
+ }
1006
+ if (value.conversion !== undefined) {
1007
+ if (!isRecord(value.conversion) || !value.conversion.sourceRef || !value.conversion.targetRef
1008
+ || !isRecord(value.conversion.provenance)) {
1009
+ throw new CompanyOsEvaluationError('corrupt_record', `${label}.conversion is invalid`);
1010
+ }
1011
+ assertFoundationRef(value.conversion.sourceRef, `${label}.conversion.sourceRef`);
1012
+ assertFoundationRef(value.conversion.targetRef, `${label}.conversion.targetRef`);
1013
+ if (value.conversion.sourceRef.type !== 'variable' || value.conversion.targetRef.type !== 'variable'
1014
+ || !sameFoundationRef(value.conversion.sourceRef, value.variableRef)
1015
+ || !sameFoundationRef(value.conversion.targetRef, value.effectiveVariableRef)
1016
+ || !isNonEmptyString(value.conversion.provenance.id)
1017
+ || !isNonEmptyString(value.conversion.provenance.description)) {
1018
+ throw new CompanyOsEvaluationError('corrupt_record', `${label}.conversion provenance or references are invalid`);
1019
+ }
1020
+ }
1021
+ else if (!sameFoundationRef(value.variableRef, value.effectiveVariableRef)) {
1022
+ throw new CompanyOsEvaluationError('corrupt_record', `${label} changed definition without conversion provenance`);
1023
+ }
1024
+ }
1025
+ function assertStoredCriterion(value, label) {
1026
+ if (!isRecord(value) || typeof value.criterionIndex !== 'number' || !Number.isInteger(value.criterionIndex) || value.criterionIndex < 0
1027
+ || !value.variableRef || !isNonEmptyString(value.operator)
1028
+ || !isNonEmptyString(value.status) || !isNonEmptyString(value.reason)) {
1029
+ throw new CompanyOsEvaluationError('corrupt_record', `${label} is invalid`);
1030
+ }
1031
+ assertFoundationRef(value.variableRef, `${label}.variableRef`);
1032
+ if (value.variableRef.type !== 'variable' || !['at_least', 'at_most', 'equals'].includes(value.operator)
1033
+ || !['achieved', 'not_achieved', 'indeterminate'].includes(value.status)) {
1034
+ throw new CompanyOsEvaluationError('corrupt_record', `${label} contains an invalid criterion contract`);
1035
+ }
1036
+ if (value.target !== undefined && !isEvaluationScalar(value.target)) {
1037
+ throw new CompanyOsEvaluationError('corrupt_record', `${label}.target is invalid`);
1038
+ }
1039
+ }
1040
+ function assertStoredPredictionComparison(value, label) {
1041
+ if (!isRecord(value) || typeof value.criterionIndex !== 'number' || !Number.isInteger(value.criterionIndex) || value.criterionIndex < 0
1042
+ || !value.variableRef || !isNonEmptyString(value.status) || !isNonEmptyString(value.reason)) {
1043
+ throw new CompanyOsEvaluationError('corrupt_record', `${label} is invalid`);
1044
+ }
1045
+ assertFoundationRef(value.variableRef, `${label}.variableRef`);
1046
+ if (value.variableRef.type !== 'variable' || !['matched', 'missed', 'indeterminate'].includes(value.status)) {
1047
+ throw new CompanyOsEvaluationError('corrupt_record', `${label} contains an invalid comparison contract`);
1048
+ }
1049
+ for (const status of ['predictionStatus', 'actualStatus']) {
1050
+ const statusValue = value[status];
1051
+ if (statusValue !== undefined && (typeof statusValue !== 'string' || !['observed', 'missing', 'not_arrived'].includes(statusValue))) {
1052
+ throw new CompanyOsEvaluationError('corrupt_record', `${label}.${status} is invalid`);
1053
+ }
1054
+ }
1055
+ for (const field of ['predictionValue', 'actualValue']) {
1056
+ if (value[field] !== undefined && !isEvaluationScalar(value[field])) {
1057
+ throw new CompanyOsEvaluationError('corrupt_record', `${label}.${field} is invalid`);
1058
+ }
1059
+ }
1060
+ }
1061
+ function assertStoredJudgmentValidity(value, label) {
1062
+ if (!isRecord(value) || typeof value.status !== 'string' || !['valid', 'invalid', 'indeterminate'].includes(value.status)
1063
+ || !isNonEmptyString(value.basis)
1064
+ || (value.evidenceRefs !== undefined
1065
+ && (!Array.isArray(value.evidenceRefs) || value.evidenceRefs.some((ref) => !isNonEmptyString(ref))))) {
1066
+ throw new CompanyOsEvaluationError('corrupt_record', `${label} is invalid`);
1067
+ }
1068
+ }
1069
+ function assertOutcomeCaseCanonicalReference(value, label) {
1070
+ if (!isRecord(value) || !isNonEmptyString(value.id) || !/^[1-9]\d*$/.test(String(value.revision)) || !isDigest(value.digest)) {
1071
+ throw new CompanyOsEvaluationError('corrupt_record', `${label} must contain a canonical id, revision, and digest`);
1072
+ }
1073
+ }
1074
+ function isFoundationAggregation(value) {
1075
+ return ['none', 'sum', 'average', 'count', 'min', 'max', 'last', 'custom'].includes(String(value));
1076
+ }
1077
+ function isValidPeriod(value) {
1078
+ if (!isRecord(value))
1079
+ return false;
1080
+ const from = typeof value.from === 'string' ? parseStrictRfc3339(value.from) : undefined;
1081
+ const until = typeof value.until === 'string' ? parseStrictRfc3339(value.until) : undefined;
1082
+ return isRecord(value)
1083
+ && from !== undefined
1084
+ && until !== undefined
1085
+ && until > from;
1086
+ }
1087
+ function parseEvaluationCatalog(rawText) {
1088
+ if (rawText === undefined)
1089
+ return { version: COMPANY_OS_EVALUATION_RECORD_CATALOG_VERSION, records: [] };
1090
+ try {
1091
+ const raw = JSON.parse(rawText);
1092
+ assertEvaluationCatalog(raw);
1093
+ return deepFreeze(cloneJson(raw));
1094
+ }
1095
+ catch (error) {
1096
+ if (error instanceof CompanyOsEvaluationError) {
1097
+ // Persisted JSON is untrusted input. A shape helper may share the
1098
+ // request-side invalid_input code, but callers must see a corrupt record
1099
+ // when the sidecar itself is malformed.
1100
+ if (error.code === 'invalid_input') {
1101
+ throw new CompanyOsEvaluationError('corrupt_record', error.message);
1102
+ }
1103
+ throw error;
1104
+ }
1105
+ throw new CompanyOsEvaluationError('corrupt_record', formatError(error));
1106
+ }
1107
+ }
1108
+ function assertEvaluationCatalog(value) {
1109
+ if (!value || typeof value !== 'object' || value.version !== COMPANY_OS_EVALUATION_RECORD_CATALOG_VERSION
1110
+ || !Array.isArray(value.records)) {
1111
+ throw new CompanyOsEvaluationError('corrupt_record', 'Evaluation evidence must contain version 1 records');
1112
+ }
1113
+ const ids = new Set();
1114
+ for (const record of value.records) {
1115
+ if (!record || typeof record !== 'object' || !isNonEmptyString(record.id) || ids.has(record.id)
1116
+ || record.version !== COMPANY_OS_EVALUATION_RECORD_CATALOG_VERSION
1117
+ || !record.snapshot || !isNonEmptyString(record.snapshot.snapshotId)
1118
+ || !isNonEmptyString(record.snapshot.problemId) || !/^[1-9]\d*$/.test(record.snapshot.revision)
1119
+ || !isNonEmptyString(record.snapshot.digest) || !record.objectiveRef
1120
+ || !record.outcomeCaseRef || !Array.isArray(record.predictions) || !Array.isArray(record.actuals)
1121
+ || !Array.isArray(record.criteria) || !Array.isArray(record.predictionComparisons)
1122
+ || !['achieved', 'not_achieved', 'indeterminate'].includes(record.achievement)
1123
+ || !record.judgmentAtTimeValidity || !parseIsoTimestamp(record.evaluatedAt)) {
1124
+ throw new CompanyOsEvaluationError('corrupt_record', 'Evaluation evidence contains an invalid immutable record');
1125
+ }
1126
+ assertFoundationRef(record.objectiveRef, 'record.objectiveRef');
1127
+ assertOutcomeCaseCanonicalReference(record.outcomeCaseRef, 'record.outcomeCaseRef');
1128
+ if (!isDigest(record.snapshot.snapshotId) || !isDigest(record.snapshot.digest)) {
1129
+ throw new CompanyOsEvaluationError('corrupt_record', 'Evaluation snapshot ids must be sha256 digests');
1130
+ }
1131
+ if (record.snapshot.digest !== record.snapshot.snapshotId) {
1132
+ throw new CompanyOsEvaluationError('integrity_mismatch', 'Evaluation snapshot digest must equal its snapshot id');
1133
+ }
1134
+ if (record.criteria.length === 0 || record.criteria.length !== record.predictionComparisons.length) {
1135
+ throw new CompanyOsEvaluationError('corrupt_record', 'Evaluation criteria and prediction comparisons are inconsistent');
1136
+ }
1137
+ record.predictions.forEach((measurement, index) => assertStoredMeasurement(measurement, `record.predictions[${index}]`));
1138
+ record.actuals.forEach((measurement, index) => assertStoredMeasurement(measurement, `record.actuals[${index}]`));
1139
+ record.criteria.forEach((criterion, index) => assertStoredCriterion(criterion, `record.criteria[${index}]`));
1140
+ record.predictionComparisons.forEach((comparison, index) => assertStoredPredictionComparison(comparison, `record.predictionComparisons[${index}]`));
1141
+ assertStoredJudgmentValidity(record.judgmentAtTimeValidity, 'record.judgmentAtTimeValidity');
1142
+ ids.add(record.id);
1143
+ }
1144
+ }
1145
+ function serializeEvaluationCatalog(catalog) {
1146
+ return `${JSON.stringify(catalog, null, 2)}\n`;
1147
+ }
1148
+ function formatError(error) {
1149
+ return error instanceof Error ? error.message : String(error);
1150
+ }