@unson/brainbase-mcp 0.6.0 → 0.8.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 (148) hide show
  1. package/contracts/foundation/graph-history.sql +634 -0
  2. package/contracts/judgment-value-proof/schema.json +43 -1
  3. package/dist/canonical-task-contract.d.ts +9 -0
  4. package/dist/canonical-task-contract.js +39 -0
  5. package/dist/canonical-task-principal.d.ts +16 -0
  6. package/dist/canonical-task-principal.js +36 -0
  7. package/dist/canonical-task-service.d.ts +251 -0
  8. package/dist/canonical-task-service.js +820 -0
  9. package/dist/cli.d.ts +2 -0
  10. package/dist/cli.js +107 -0
  11. package/dist/company-os-evaluation.d.ts +193 -0
  12. package/dist/company-os-evaluation.js +1150 -0
  13. package/dist/company-os-impact-review.d.ts +248 -0
  14. package/dist/company-os-impact-review.js +663 -0
  15. package/dist/company-os-learning-adoption.d.ts +288 -0
  16. package/dist/company-os-learning-adoption.js +944 -0
  17. package/dist/company-os-objectives.d.ts +63 -0
  18. package/dist/company-os-objectives.js +234 -0
  19. package/dist/company-os-receipt-adapter.d.ts +109 -0
  20. package/dist/company-os-receipt-adapter.js +636 -0
  21. package/dist/constraint-exception-store.d.ts +23 -0
  22. package/dist/constraint-exception-store.js +108 -0
  23. package/dist/constraint-resolution.d.ts +175 -0
  24. package/dist/constraint-resolution.js +922 -0
  25. package/dist/decision-adapter.d.ts +145 -0
  26. package/dist/decision-adapter.js +586 -0
  27. package/dist/durable-wait-http.d.ts +51 -0
  28. package/dist/durable-wait-http.js +497 -0
  29. package/dist/durable-waits.d.ts +234 -0
  30. package/dist/durable-waits.js +944 -0
  31. package/dist/execution-authority-http.d.ts +28 -0
  32. package/dist/execution-authority-http.js +359 -0
  33. package/dist/execution-authority.d.ts +201 -0
  34. package/dist/execution-authority.js +634 -0
  35. package/dist/foundation-authenticated-tools.d.ts +56 -0
  36. package/dist/foundation-authenticated-tools.js +347 -0
  37. package/dist/foundation-catalog.d.ts +26 -0
  38. package/dist/foundation-catalog.js +206 -0
  39. package/dist/foundation-constraint-store.d.ts +50 -0
  40. package/dist/foundation-constraint-store.js +215 -0
  41. package/dist/foundation-graph-http.d.ts +43 -0
  42. package/dist/foundation-graph-http.js +243 -0
  43. package/dist/foundation-graph-write.d.ts +90 -0
  44. package/dist/foundation-graph-write.js +447 -0
  45. package/dist/foundation-http.d.ts +90 -0
  46. package/dist/foundation-http.js +439 -0
  47. package/dist/foundation-public-provider.d.ts +140 -0
  48. package/dist/foundation-public-provider.js +192 -0
  49. package/dist/foundation-store.d.ts +132 -0
  50. package/dist/foundation-store.js +654 -0
  51. package/dist/graph-corrections.d.ts +157 -0
  52. package/dist/graph-corrections.js +620 -0
  53. package/dist/graph-foundation-reader.d.ts +60 -0
  54. package/dist/graph-foundation-reader.js +494 -0
  55. package/dist/graph-retrieval.d.ts +6 -1
  56. package/dist/graph-revision-reader.d.ts +80 -0
  57. package/dist/graph-revision-reader.js +246 -0
  58. package/dist/graph-web-http.d.ts +26 -0
  59. package/dist/graph-web-http.js +215 -0
  60. package/dist/graph-web.d.ts +364 -0
  61. package/dist/graph-web.js +639 -0
  62. package/dist/judgment-dag-composition.d.ts +224 -0
  63. package/dist/judgment-dag-composition.js +731 -0
  64. package/dist/judgment-dag.d.ts +2 -0
  65. package/dist/judgment-dag.js +1 -0
  66. package/dist/judgment-problem-snapshot.d.ts +213 -0
  67. package/dist/judgment-problem-snapshot.js +1073 -0
  68. package/dist/judgment-value-proof-review.d.ts +166 -0
  69. package/dist/judgment-value-proof-review.js +437 -0
  70. package/dist/judgment-value-proof.d.ts +28 -1
  71. package/dist/judgment-value-proof.js +53 -8
  72. package/dist/judgment-view-http.d.ts +22 -0
  73. package/dist/judgment-view-http.js +128 -0
  74. package/dist/judgment-view.d.ts +222 -0
  75. package/dist/judgment-view.js +627 -0
  76. package/dist/knowledge-adapter.d.ts +112 -0
  77. package/dist/knowledge-adapter.js +691 -0
  78. package/dist/knowledge-continuation.d.ts +127 -0
  79. package/dist/knowledge-continuation.js +446 -0
  80. package/dist/knowledge-lookup.d.ts +178 -0
  81. package/dist/knowledge-lookup.js +758 -0
  82. package/dist/local-web-fetch-bridge.d.ts +5 -0
  83. package/dist/local-web-fetch-bridge.js +37 -0
  84. package/dist/local-web-host.d.ts +109 -0
  85. package/dist/local-web-host.js +688 -0
  86. package/dist/local-web-security.d.ts +25 -0
  87. package/dist/local-web-security.js +95 -0
  88. package/dist/ontology-foundation.d.ts +209 -0
  89. package/dist/ontology-foundation.js +1000 -0
  90. package/dist/ontology.d.ts +13 -0
  91. package/dist/ontology.js +14 -0
  92. package/dist/organization-connection.d.ts +164 -0
  93. package/dist/organization-connection.js +428 -0
  94. package/dist/organization-graph.d.ts +15 -0
  95. package/dist/organization-graph.js +24 -4
  96. package/dist/personal-knowledge.d.ts +132 -0
  97. package/dist/personal-knowledge.js +779 -0
  98. package/dist/personal-memory-handover.d.ts +108 -0
  99. package/dist/personal-memory-handover.js +369 -0
  100. package/dist/philosophy-revision-reader.d.ts +85 -0
  101. package/dist/philosophy-revision-reader.js +241 -0
  102. package/dist/portable-graph.d.ts +24 -0
  103. package/dist/portable-graph.js +77 -0
  104. package/dist/problem-candidates.d.ts +149 -0
  105. package/dist/problem-candidates.js +898 -0
  106. package/dist/problem-selection.d.ts +238 -0
  107. package/dist/problem-selection.js +995 -0
  108. package/dist/resource-reservation-http.d.ts +31 -0
  109. package/dist/resource-reservation-http.js +371 -0
  110. package/dist/resource-reservations.d.ts +221 -0
  111. package/dist/resource-reservations.js +577 -0
  112. package/dist/server.d.ts +104 -2
  113. package/dist/server.js +186 -4
  114. package/dist/ssot.d.ts +6 -1
  115. package/dist/ssot.js +63 -5
  116. package/dist/streamable-http.d.ts +42 -0
  117. package/dist/streamable-http.js +288 -0
  118. package/dist/types.d.ts +20 -0
  119. package/dist/value-proof-review-http.d.ts +25 -0
  120. package/dist/value-proof-review-http.js +185 -0
  121. package/dist/world-model.d.ts +199 -0
  122. package/dist/world-model.js +955 -0
  123. package/package.json +230 -3
  124. package/ui/brainbase-tokens.css +56 -0
  125. package/ui/graph-own-share.js +367 -0
  126. package/ui/graph-projects-view.css +23 -0
  127. package/ui/graph-projects-view.js +730 -0
  128. package/ui/graph-registry-view.css +101 -0
  129. package/ui/graph-registry-view.js +804 -0
  130. package/ui/graph-view-shared.css +522 -0
  131. package/ui/graph-view-shared.js +1227 -0
  132. package/ui/judgment-view.css +312 -0
  133. package/ui/judgment-view.js +508 -0
  134. package/ui/local-web-shell.css +396 -0
  135. package/ui/local-web-shell.js +378 -0
  136. package/ui/objective-editor-http-port.js +154 -0
  137. package/ui/objective-editor.css +286 -0
  138. package/ui/objective-editor.js +1586 -0
  139. package/ui/outcome-knowledge.css +50 -14
  140. package/ui/outcome-knowledge.js +15 -1
  141. package/ui/outcome-mana.css +91 -14
  142. package/ui/outcome-mana.js +958 -61
  143. package/ui/value-proof-review.css +504 -0
  144. package/ui/value-proof-review.js +1104 -0
  145. package/ui/workspace-kit.css +485 -0
  146. package/ui/workspace-kit.js +229 -0
  147. package/ui/world-model-view.css +188 -0
  148. package/ui/world-model-view.js +486 -0
@@ -0,0 +1,1073 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { link, open, lstat, mkdir, readdir, readFile, unlink } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { validateFoundationDefinition, validateEvaluationCompatibility } from './ontology-foundation.js';
5
+ import { checkObjectiveReadiness } from './company-os-objectives.js';
6
+ /**
7
+ * An immutable input bundle for one judgment.
8
+ *
9
+ * The bundle deliberately stores references to canonical objects instead of
10
+ * copying Objective, Model, or Constraint definitions into a second SSOT.
11
+ * Providers validate those references at the boundary of save/load.
12
+ */
13
+ export const JUDGMENT_PROBLEM_SNAPSHOT_VERSION = 'judgment-problem-snapshot.v1';
14
+ export class JudgmentProblemSnapshotError extends Error {
15
+ code;
16
+ reference;
17
+ constructor(code, message, options = {}) {
18
+ super(message);
19
+ this.name = 'JudgmentProblemSnapshotError';
20
+ this.code = code;
21
+ this.reference = options.reference;
22
+ }
23
+ }
24
+ const SNAPSHOT_ID_PATTERN = /^sha256:([0-9a-f]{64})$/u;
25
+ const POSITIVE_REVISION_PATTERN = /^[1-9]\d*$/u;
26
+ const SCOPE_TYPES = ['personal', 'project', 'organization'];
27
+ const REFERENCE_KINDS = [
28
+ 'entity',
29
+ 'edge',
30
+ 'objective',
31
+ 'criterion',
32
+ 'variable',
33
+ 'observation',
34
+ 'model',
35
+ 'constraint',
36
+ 'philosophy',
37
+ 'authority',
38
+ 'resource',
39
+ 'deadline',
40
+ 'dag',
41
+ 'evidence'
42
+ ];
43
+ const SNAPSHOT_KEYS = [
44
+ 'created_at',
45
+ 'execution_permission',
46
+ 'owner_scope',
47
+ 'problem_id',
48
+ 'question',
49
+ 'read_policy',
50
+ 'references',
51
+ 'revision',
52
+ 'snapshot_version'
53
+ ];
54
+ const ENVELOPE_KEYS = ['snapshot', 'snapshot_id', 'snapshot_version'];
55
+ const REFERENCE_KEYS = ['digest', 'evidence', 'id', 'kind', 'measurement', 'revision', 'scope', 'valid_from', 'valid_to'];
56
+ const EVIDENCE_KEYS = ['access', 'content', 'digest', 'mode', 'reference_id', 'reference_revision'];
57
+ const SCOPE_KEYS = ['id', 'type'];
58
+ const ACL_KEYS = ['ownerId', 'readerIds', 'visibility', 'writerIds'];
59
+ const LOCATOR_DIRECTORY = 'judgment-problem-snapshots';
60
+ const REVISION_DIRECTORY = 'revisions';
61
+ function fail(code, message, reference) {
62
+ throw new JudgmentProblemSnapshotError(code, message, reference ? { reference } : undefined);
63
+ }
64
+ function isPlainRecord(value) {
65
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
66
+ return false;
67
+ const prototype = Object.getPrototypeOf(value);
68
+ return prototype === Object.prototype || prototype === null;
69
+ }
70
+ function hasExactKeys(value, expected) {
71
+ const keys = Object.keys(value).sort();
72
+ const sorted = [...expected].sort();
73
+ return keys.length === sorted.length && keys.every((key, index) => key === sorted[index]);
74
+ }
75
+ function hasOnlyKeys(value, allowed) {
76
+ const allowedSet = new Set(allowed);
77
+ return Object.keys(value).every((key) => allowedSet.has(key));
78
+ }
79
+ function requireString(value, label) {
80
+ if (typeof value !== 'string' || value.trim().length === 0 || value.includes('\0')) {
81
+ fail('invalid_request', `${label} must be a non-empty string`);
82
+ }
83
+ return value;
84
+ }
85
+ function requireRevision(value, label) {
86
+ const revision = requireString(value, label);
87
+ if (!POSITIVE_REVISION_PATTERN.test(revision)) {
88
+ fail('invalid_request', `${label} must be a positive canonical decimal revision`);
89
+ }
90
+ return revision;
91
+ }
92
+ function requireSnapshotId(value, label) {
93
+ if (typeof value !== 'string' || !SNAPSHOT_ID_PATTERN.test(value)) {
94
+ fail('invalid_request', `${label} must be sha256:<64 lowercase hex>`);
95
+ }
96
+ return value;
97
+ }
98
+ function requireDate(value, label) {
99
+ const date = requireString(value, label);
100
+ if (Number.isNaN(Date.parse(date)))
101
+ fail('invalid_request', `${label} must be an ISO date`);
102
+ return date;
103
+ }
104
+ function assertScope(value, label) {
105
+ if (!isPlainRecord(value) || !hasExactKeys(value, SCOPE_KEYS) ||
106
+ !SCOPE_TYPES.includes(value.type) ||
107
+ typeof value.id !== 'string' || value.id.trim().length === 0 || value.id.includes('\0')) {
108
+ fail('invalid_request', `${label} is invalid`);
109
+ }
110
+ }
111
+ function assertAcl(value, label) {
112
+ if (!isPlainRecord(value) || !hasExactKeys(value, ACL_KEYS) ||
113
+ typeof value.ownerId !== 'string' || value.ownerId.trim().length === 0 || value.ownerId.includes('\0') ||
114
+ !Array.isArray(value.readerIds) || !Array.isArray(value.writerIds) ||
115
+ !['private', 'project', 'organization', 'public'].includes(String(value.visibility))) {
116
+ fail('invalid_request', `${label} is invalid`);
117
+ }
118
+ if (value.readerIds.some((id) => typeof id !== 'string' || id.trim().length === 0) ||
119
+ value.writerIds.some((id) => typeof id !== 'string' || id.trim().length === 0)) {
120
+ fail('invalid_request', `${label}.readerIds and writerIds must contain non-empty strings`);
121
+ }
122
+ const readerIds = value.readerIds;
123
+ const writerIds = value.writerIds;
124
+ if (new Set(readerIds).size !== readerIds.length || new Set(writerIds).size !== writerIds.length) {
125
+ fail('invalid_request', `${label}.readerIds and writerIds must not contain duplicates`);
126
+ }
127
+ }
128
+ function compareUnicodeCodePoints(left, right) {
129
+ const leftPoints = Array.from(left, (character) => character.codePointAt(0));
130
+ const rightPoints = Array.from(right, (character) => character.codePointAt(0));
131
+ const length = Math.min(leftPoints.length, rightPoints.length);
132
+ for (let index = 0; index < length; index += 1) {
133
+ if (leftPoints[index] !== rightPoints[index])
134
+ return leftPoints[index] - rightPoints[index];
135
+ }
136
+ return leftPoints.length - rightPoints.length;
137
+ }
138
+ function canonicalValue(value, active = new Set()) {
139
+ if (value === null || typeof value === 'string' || typeof value === 'boolean')
140
+ return value;
141
+ if (typeof value === 'number') {
142
+ if (!Number.isFinite(value))
143
+ fail('invalid_request', 'snapshot values must be finite JSON numbers');
144
+ return Object.is(value, -0) ? 0 : value;
145
+ }
146
+ if (typeof value !== 'object')
147
+ fail('invalid_request', 'snapshot values must be JSON-compatible');
148
+ if (active.has(value))
149
+ fail('invalid_request', 'snapshot values must not be cyclic');
150
+ active.add(value);
151
+ try {
152
+ if (Array.isArray(value)) {
153
+ const result = [];
154
+ for (let index = 0; index < value.length; index += 1) {
155
+ if (!(index in value))
156
+ fail('invalid_request', 'snapshot arrays must not be sparse');
157
+ result.push(canonicalValue(value[index], active));
158
+ }
159
+ return result;
160
+ }
161
+ if (!isPlainRecord(value))
162
+ fail('invalid_request', 'snapshot objects must be plain records');
163
+ const result = Object.create(null);
164
+ for (const key of Object.keys(value).sort(compareUnicodeCodePoints)) {
165
+ result[key] = canonicalValue(value[key], active);
166
+ }
167
+ return result;
168
+ }
169
+ finally {
170
+ active.delete(value);
171
+ }
172
+ }
173
+ function canonicalJson(value) {
174
+ const json = JSON.stringify(canonicalValue(value));
175
+ if (json === undefined)
176
+ fail('invalid_request', 'snapshot is not JSON serializable');
177
+ return json;
178
+ }
179
+ function deepFreeze(value) {
180
+ if (value !== null && typeof value === 'object' && !Object.isFrozen(value)) {
181
+ Object.freeze(value);
182
+ for (const child of Object.values(value))
183
+ deepFreeze(child);
184
+ }
185
+ return value;
186
+ }
187
+ function contentDigest(value) {
188
+ return `sha256:${createHash('sha256').update(canonicalJson(value), 'utf8').digest('hex')}`;
189
+ }
190
+ function payloadFor(snapshot) {
191
+ return { snapshot_version: JUDGMENT_PROBLEM_SNAPSHOT_VERSION, snapshot };
192
+ }
193
+ function validateEvidence(value, label) {
194
+ if (!isPlainRecord(value) || !hasOnlyKeys(value, EVIDENCE_KEYS) ||
195
+ !Object.prototype.hasOwnProperty.call(value, 'mode') ||
196
+ !Object.prototype.hasOwnProperty.call(value, 'digest') ||
197
+ !Object.prototype.hasOwnProperty.call(value, 'access') ||
198
+ (value.mode !== 'embedded_content' && value.mode !== 'immutable_reference') ||
199
+ typeof value.digest !== 'string' || !SNAPSHOT_ID_PATTERN.test(value.digest)) {
200
+ fail('invalid_request', `${label} is invalid`);
201
+ }
202
+ assertAcl(value.access, `${label}.access`);
203
+ const hasContent = Object.prototype.hasOwnProperty.call(value, 'content') && value.content !== undefined;
204
+ const hasReference = typeof value.reference_id === 'string' && value.reference_id.length > 0 &&
205
+ typeof value.reference_revision === 'string' && value.reference_revision.length > 0;
206
+ if (value.mode === 'embedded_content') {
207
+ if (!hasContent || hasReference)
208
+ fail('invalid_request', `${label} embedded content binding is invalid`);
209
+ const content = canonicalValue(value.content);
210
+ if (contentDigest(content) !== value.digest)
211
+ fail('integrity_mismatch', `${label}.content does not match digest`);
212
+ return deepFreeze({
213
+ mode: 'embedded_content',
214
+ digest: value.digest,
215
+ content,
216
+ access: normalizeAcl(value.access)
217
+ });
218
+ }
219
+ if (hasContent || !hasReference)
220
+ fail('invalid_request', `${label} immutable reference binding is invalid`);
221
+ requireRevision(value.reference_revision, `${label}.reference_revision`);
222
+ return deepFreeze({
223
+ mode: 'immutable_reference',
224
+ digest: value.digest,
225
+ reference_id: value.reference_id,
226
+ reference_revision: value.reference_revision,
227
+ access: normalizeAcl(value.access)
228
+ });
229
+ }
230
+ function normalizeAcl(value) {
231
+ return {
232
+ ownerId: value.ownerId,
233
+ visibility: value.visibility,
234
+ readerIds: [...value.readerIds],
235
+ writerIds: [...value.writerIds]
236
+ };
237
+ }
238
+ function validateReference(value, index) {
239
+ const label = `references[${index}]`;
240
+ if (!isPlainRecord(value) || !hasOnlyKeys(value, REFERENCE_KEYS) ||
241
+ !Object.prototype.hasOwnProperty.call(value, 'kind') ||
242
+ !Object.prototype.hasOwnProperty.call(value, 'id') ||
243
+ !Object.prototype.hasOwnProperty.call(value, 'revision') ||
244
+ !Object.prototype.hasOwnProperty.call(value, 'digest') ||
245
+ !Object.prototype.hasOwnProperty.call(value, 'scope') ||
246
+ !Object.prototype.hasOwnProperty.call(value, 'valid_from') ||
247
+ !REFERENCE_KINDS.includes(value.kind)) {
248
+ fail('invalid_request', `${label} is invalid`);
249
+ }
250
+ const id = requireString(value.id, `${label}.id`);
251
+ const revision = requireRevision(value.revision, `${label}.revision`);
252
+ const digest = requireSnapshotId(value.digest, `${label}.digest`);
253
+ assertScope(value.scope, `${label}.scope`);
254
+ const validFrom = requireDate(value.valid_from, `${label}.valid_from`);
255
+ const validTo = value.valid_to === undefined || value.valid_to === null
256
+ ? undefined
257
+ : requireDate(value.valid_to, `${label}.valid_to`);
258
+ if (validTo !== undefined && Date.parse(validTo) < Date.parse(validFrom)) {
259
+ fail('invalid_request', `${label}.valid_to precedes valid_from`);
260
+ }
261
+ const measurement = value.measurement === undefined
262
+ ? undefined
263
+ : validateMeasurementDescriptor(value.measurement, `${label}.measurement`);
264
+ const evidence = value.evidence === undefined ? undefined : validateEvidence(value.evidence, `${label}.evidence`);
265
+ return deepFreeze({
266
+ kind: value.kind,
267
+ id,
268
+ revision,
269
+ digest,
270
+ scope: { type: value.scope.type, id: value.scope.id },
271
+ valid_from: validFrom,
272
+ ...(validTo === undefined ? {} : { valid_to: validTo }),
273
+ ...(measurement === undefined ? {} : { measurement }),
274
+ ...(evidence === undefined ? {} : { evidence })
275
+ });
276
+ }
277
+ function validateMeasurementDescriptor(value, label) {
278
+ const aggregations = [
279
+ 'none',
280
+ 'sum',
281
+ 'average',
282
+ 'count',
283
+ 'min',
284
+ 'max',
285
+ 'last',
286
+ 'custom'
287
+ ];
288
+ const keys = ['aggregation', 'granularity', 'period', 'scope', 'unit', 'variableRef'];
289
+ if (!isPlainRecord(value) || !hasOnlyKeys(value, keys)
290
+ || !Object.prototype.hasOwnProperty.call(value, 'variableRef')
291
+ || !Object.prototype.hasOwnProperty.call(value, 'aggregation')
292
+ || !Object.prototype.hasOwnProperty.call(value, 'granularity')
293
+ || !Object.prototype.hasOwnProperty.call(value, 'scope')
294
+ || !Object.prototype.hasOwnProperty.call(value, 'period')) {
295
+ fail('invalid_request', `${label} is invalid`);
296
+ }
297
+ if (!isPlainRecord(value.variableRef)
298
+ || !hasExactKeys(value.variableRef, ['id', 'revision', 'type'])
299
+ || value.variableRef.type !== 'variable') {
300
+ fail('invalid_request', `${label}.variableRef must reference a Variable revision`);
301
+ }
302
+ const variableId = requireString(value.variableRef.id, `${label}.variableRef.id`);
303
+ const variableRevision = requireRevision(value.variableRef.revision, `${label}.variableRef.revision`);
304
+ if (typeof value.aggregation !== 'string' || !aggregations.includes(value.aggregation)) {
305
+ fail('invalid_request', `${label}.aggregation is invalid`);
306
+ }
307
+ const granularity = requireString(value.granularity, `${label}.granularity`);
308
+ if (value.unit !== undefined && typeof value.unit !== 'string') {
309
+ fail('invalid_request', `${label}.unit must be a string when provided`);
310
+ }
311
+ if (!isPlainRecord(value.scope)
312
+ || !Array.isArray(value.scope.subjectIds)
313
+ || value.scope.subjectIds.some((subjectId) => typeof subjectId !== 'string' || subjectId.trim().length === 0)
314
+ || typeof value.scope.validFrom !== 'string'
315
+ || Number.isNaN(Date.parse(value.scope.validFrom))
316
+ || (value.scope.validUntil !== undefined && (typeof value.scope.validUntil !== 'string' || Number.isNaN(Date.parse(value.scope.validUntil))))) {
317
+ fail('invalid_request', `${label}.scope is invalid`);
318
+ }
319
+ if (!isPlainRecord(value.period)
320
+ || typeof value.period.from !== 'string'
321
+ || typeof value.period.until !== 'string'
322
+ || Number.isNaN(Date.parse(value.period.from))
323
+ || Number.isNaN(Date.parse(value.period.until))
324
+ || Date.parse(value.period.until) < Date.parse(value.period.from)) {
325
+ fail('invalid_request', `${label}.period is invalid`);
326
+ }
327
+ return deepFreeze({
328
+ variableRef: { id: variableId, type: 'variable', revision: variableRevision },
329
+ ...(value.unit === undefined ? {} : { unit: value.unit }),
330
+ aggregation: value.aggregation,
331
+ granularity,
332
+ scope: {
333
+ subjectIds: [...value.scope.subjectIds],
334
+ validFrom: value.scope.validFrom,
335
+ ...(value.scope.validUntil === undefined ? {} : { validUntil: value.scope.validUntil })
336
+ },
337
+ period: { from: value.period.from, until: value.period.until }
338
+ });
339
+ }
340
+ function validateSnapshot(value) {
341
+ if (!isPlainRecord(value) || !hasExactKeys(value, SNAPSHOT_KEYS) ||
342
+ value.snapshot_version !== JUDGMENT_PROBLEM_SNAPSHOT_VERSION ||
343
+ value.execution_permission !== 'none') {
344
+ fail('invalid_request', 'snapshot shape or version is invalid');
345
+ }
346
+ const problemId = requireString(value.problem_id, 'snapshot.problem_id');
347
+ const revision = requireRevision(value.revision, 'snapshot.revision');
348
+ const question = requireString(value.question, 'snapshot.question');
349
+ assertScope(value.owner_scope, 'snapshot.owner_scope');
350
+ assertAcl(value.read_policy, 'snapshot.read_policy');
351
+ if (!Array.isArray(value.references) || value.references.length === 0) {
352
+ fail('missing_reference', 'snapshot.references must contain required references');
353
+ }
354
+ const references = value.references.map((reference, index) => validateReference(reference, index));
355
+ const createdAt = requireDate(value.created_at, 'snapshot.created_at');
356
+ return deepFreeze({
357
+ snapshot_version: JUDGMENT_PROBLEM_SNAPSHOT_VERSION,
358
+ problem_id: problemId,
359
+ revision,
360
+ question,
361
+ owner_scope: { type: value.owner_scope.type, id: value.owner_scope.id },
362
+ references,
363
+ read_policy: normalizeAcl(value.read_policy),
364
+ execution_permission: 'none',
365
+ created_at: createdAt
366
+ });
367
+ }
368
+ function requiredKinds(references) {
369
+ return new Set(references.map((reference) => reference.kind));
370
+ }
371
+ function validateRequiredReferences(snapshot) {
372
+ const kinds = requiredKinds(snapshot.references);
373
+ const objectiveCount = snapshot.references.filter((reference) => reference.kind === 'objective').length;
374
+ if (objectiveCount !== 1) {
375
+ fail('invalid_request', `snapshot must contain exactly one objective reference; found ${objectiveCount}`);
376
+ }
377
+ const required = [
378
+ 'objective',
379
+ 'criterion',
380
+ 'observation',
381
+ 'model',
382
+ 'constraint',
383
+ 'authority',
384
+ 'resource',
385
+ 'deadline'
386
+ ];
387
+ const missing = required.filter((kind) => !kinds.has(kind));
388
+ if (missing.length > 0)
389
+ fail('missing_reference', `snapshot is missing required references: ${missing.join(', ')}`);
390
+ }
391
+ function requireRoot(value) {
392
+ if (typeof value !== 'string' || value.trim().length === 0 || value.includes('\0')) {
393
+ fail('invalid_request', 'artifact root must be a non-empty filesystem path');
394
+ }
395
+ return path.resolve(value);
396
+ }
397
+ function locatorFor(root, problemId, revision) {
398
+ const keyDigest = contentDigest({ problem_id: problemId, revision });
399
+ const hex = keyDigest.slice('sha256:'.length);
400
+ return {
401
+ key_digest: keyDigest,
402
+ file: path.join(root, LOCATOR_DIRECTORY, REVISION_DIRECTORY, `${hex}.json`)
403
+ };
404
+ }
405
+ function providerResultError(reference, result) {
406
+ if (result.status === 'missing') {
407
+ return new JudgmentProblemSnapshotError('missing_reference', result.message ?? `Reference ${reference.kind}/${reference.id}@${reference.revision} is missing`, { reference });
408
+ }
409
+ if (result.status === 'not_applicable') {
410
+ return new JudgmentProblemSnapshotError('not_applicable', result.message ?? `Reference ${reference.kind}/${reference.id}@${reference.revision} is not applicable`, { reference });
411
+ }
412
+ if (result.status === 'unauthorized') {
413
+ return new JudgmentProblemSnapshotError('unauthorized', result.message ?? `Reference ${reference.kind}/${reference.id}@${reference.revision} is not readable`, { reference });
414
+ }
415
+ return new JudgmentProblemSnapshotError('unresolved_constraint', result.message ?? `Reference ${reference.kind}/${reference.id}@${reference.revision} is unresolved`, { reference });
416
+ }
417
+ function evidenceReference(owner, evidence) {
418
+ if (evidence.mode !== 'immutable_reference' || evidence.reference_id === undefined || evidence.reference_revision === undefined) {
419
+ fail('invalid_request', 'immutable evidence reference is incomplete', owner);
420
+ }
421
+ return {
422
+ kind: 'evidence',
423
+ id: evidence.reference_id,
424
+ revision: evidence.reference_revision,
425
+ digest: evidence.digest,
426
+ scope: owner.scope,
427
+ valid_from: owner.valid_from,
428
+ ...(owner.valid_to === undefined ? {} : { valid_to: owner.valid_to })
429
+ };
430
+ }
431
+ async function resolveOneReference(reference, phase, context, provider, snapshot, errorReference = reference) {
432
+ let result;
433
+ try {
434
+ result = await provider.resolve({ reference, phase, context, snapshot });
435
+ }
436
+ catch (error) {
437
+ if (error instanceof JudgmentProblemSnapshotError)
438
+ throw error;
439
+ const code = error && typeof error === 'object' && 'code' in error
440
+ ? error.code
441
+ : undefined;
442
+ if (code === 'authorization_denied' || code === 'unauthorized') {
443
+ fail('unauthorized', `Reference provider denied ${reference.kind}/${reference.id}@${reference.revision}: ${error instanceof Error ? error.message : String(error)}`, errorReference);
444
+ }
445
+ fail('unresolved_constraint', `Reference provider failed for ${reference.kind}/${reference.id}@${reference.revision}: ${error instanceof Error ? error.message : String(error)}`, errorReference);
446
+ }
447
+ if (!result || !['resolved', 'missing', 'not_applicable', 'unauthorized', 'unresolved'].includes(result.status)) {
448
+ fail('invalid_request', `Reference provider returned an invalid result for ${reference.kind}/${reference.id}@${reference.revision}`, errorReference);
449
+ }
450
+ if (result.status !== 'resolved')
451
+ throw providerResultError(errorReference, result);
452
+ if (result.digest === undefined) {
453
+ fail('integrity_mismatch', `Reference provider did not return the exact revision digest for ${reference.kind}/${reference.id}@${reference.revision}`, errorReference);
454
+ }
455
+ if (result.digest !== reference.digest) {
456
+ fail('integrity_mismatch', `Reference provider digest does not match ${reference.kind}/${reference.id}@${reference.revision}`, errorReference);
457
+ }
458
+ }
459
+ async function resolveReferences(snapshot, phase, context, provider) {
460
+ for (const reference of snapshot.references) {
461
+ await resolveOneReference(reference, phase, context, provider, snapshot);
462
+ if (reference.evidence?.mode === 'immutable_reference') {
463
+ await resolveOneReference(evidenceReference(reference, reference.evidence), phase, context, provider, snapshot, reference);
464
+ }
465
+ }
466
+ }
467
+ async function authorize(action, snapshot, context, provider) {
468
+ try {
469
+ const result = await provider.authorize({
470
+ action,
471
+ context,
472
+ acl: snapshot.read_policy,
473
+ snapshot
474
+ });
475
+ if (result === false)
476
+ throw new Error('provider denied access');
477
+ }
478
+ catch (error) {
479
+ if (error instanceof JudgmentProblemSnapshotError)
480
+ throw error;
481
+ fail('unauthorized', `${action} access was denied: ${error instanceof Error ? error.message : String(error)}`);
482
+ }
483
+ }
484
+ export const defaultJudgmentProblemSnapshotAccessProvider = {
485
+ authorize({ action, context, acl }) {
486
+ const principal = context.principal;
487
+ if (action === 'write') {
488
+ return acl.ownerId === principal || acl.writerIds.includes(principal);
489
+ }
490
+ return acl.visibility === 'public' || acl.ownerId === principal ||
491
+ acl.readerIds.includes(principal) || acl.writerIds.includes(principal);
492
+ }
493
+ };
494
+ async function assertRevisionDirectory(root, create) {
495
+ let rootDetails;
496
+ try {
497
+ rootDetails = await lstat(root);
498
+ }
499
+ catch {
500
+ fail('invalid_request', 'artifact root must be an existing regular directory');
501
+ }
502
+ if (!rootDetails.isDirectory() || rootDetails.isSymbolicLink()) {
503
+ fail('invalid_request', 'artifact root must be an existing regular directory');
504
+ }
505
+ const base = path.join(root, LOCATOR_DIRECTORY);
506
+ const revisions = path.join(base, REVISION_DIRECTORY);
507
+ if (!create) {
508
+ const details = await lstat(revisions).catch(() => undefined);
509
+ if (details === undefined)
510
+ fail('not_found', 'judgment problem snapshot was not found');
511
+ if (!details.isDirectory() || details.isSymbolicLink())
512
+ fail('integrity_mismatch', 'snapshot revision directory is invalid');
513
+ return revisions;
514
+ }
515
+ for (const directory of [base, revisions]) {
516
+ let details = await lstat(directory).catch(() => undefined);
517
+ if (details === undefined) {
518
+ try {
519
+ await mkdir(directory, { mode: 0o700 });
520
+ }
521
+ catch (error) {
522
+ if (error.code !== 'EEXIST')
523
+ fail('storage_io_error', 'snapshot directory could not be prepared');
524
+ }
525
+ details = await lstat(directory).catch(() => undefined);
526
+ }
527
+ if (!details || !details.isDirectory() || details.isSymbolicLink()) {
528
+ fail('integrity_mismatch', 'snapshot directory must be a regular directory');
529
+ }
530
+ }
531
+ return revisions;
532
+ }
533
+ async function readRegular(file) {
534
+ const details = await lstat(file).catch((error) => {
535
+ if (error.code === 'ENOENT')
536
+ fail('not_found', 'judgment problem snapshot was not found');
537
+ fail('storage_io_error', 'snapshot metadata could not be read');
538
+ });
539
+ if (!details.isFile() || details.isSymbolicLink())
540
+ fail('integrity_mismatch', 'snapshot must be a regular file');
541
+ try {
542
+ return await readFile(file, 'utf8');
543
+ }
544
+ catch {
545
+ fail('storage_io_error', 'snapshot bytes could not be read');
546
+ }
547
+ }
548
+ function envelopeBytes(envelope) {
549
+ return `${canonicalJson(envelope)}\n`;
550
+ }
551
+ function parseEnvelope(bytes) {
552
+ if (!bytes.endsWith('\n'))
553
+ fail('integrity_mismatch', 'stored snapshot bytes are not canonical');
554
+ let parsed;
555
+ try {
556
+ parsed = JSON.parse(bytes.slice(0, -1));
557
+ }
558
+ catch {
559
+ fail('integrity_mismatch', 'stored snapshot bytes are not complete JSON');
560
+ }
561
+ if (!isPlainRecord(parsed) || !hasExactKeys(parsed, ENVELOPE_KEYS) ||
562
+ parsed.snapshot_version !== JUDGMENT_PROBLEM_SNAPSHOT_VERSION ||
563
+ typeof parsed.snapshot_id !== 'string') {
564
+ fail('integrity_mismatch', 'stored snapshot envelope is invalid');
565
+ }
566
+ const snapshot = validateSnapshot(parsed.snapshot);
567
+ validateRequiredReferences(snapshot);
568
+ const snapshotId = requireSnapshotId(parsed.snapshot_id, 'stored snapshot_id');
569
+ const expectedId = computeJudgmentProblemSnapshotId(snapshot);
570
+ if (snapshotId !== expectedId)
571
+ fail('integrity_mismatch', 'stored snapshot identity does not match its content');
572
+ const canonicalBytes = envelopeBytes({
573
+ snapshot_id: expectedId,
574
+ snapshot_version: JUDGMENT_PROBLEM_SNAPSHOT_VERSION,
575
+ snapshot
576
+ });
577
+ if (bytes !== canonicalBytes)
578
+ fail('integrity_mismatch', 'stored snapshot bytes are not canonical');
579
+ return {
580
+ envelope: deepFreeze({
581
+ snapshot_id: expectedId,
582
+ snapshot_version: JUDGMENT_PROBLEM_SNAPSHOT_VERSION,
583
+ snapshot
584
+ }),
585
+ bytes
586
+ };
587
+ }
588
+ async function findById(root, snapshotId) {
589
+ const directory = await assertRevisionDirectory(root, false);
590
+ const direct = path.join(directory, `${snapshotId.slice('sha256:'.length)}.json`);
591
+ const directBytes = await readRegular(direct).catch((error) => {
592
+ if (error instanceof JudgmentProblemSnapshotError && error.code === 'not_found')
593
+ return undefined;
594
+ throw error;
595
+ });
596
+ if (directBytes !== undefined)
597
+ return parseEnvelope(directBytes);
598
+ const entries = await readdir(directory, { withFileTypes: true }).catch(() => {
599
+ fail('storage_io_error', 'snapshot revision directory could not be listed');
600
+ });
601
+ for (const entry of entries) {
602
+ if (!entry.isFile() || entry.isSymbolicLink() || !entry.name.endsWith('.json'))
603
+ continue;
604
+ const bytes = await readRegular(path.join(directory, entry.name));
605
+ const parsed = parseEnvelope(bytes);
606
+ if (parsed.envelope.snapshot_id === snapshotId)
607
+ return parsed;
608
+ }
609
+ fail('not_found', 'judgment problem snapshot was not found');
610
+ }
611
+ function receipt(snapshot, snapshotId, status) {
612
+ return deepFreeze({
613
+ snapshot_id: snapshotId,
614
+ snapshot_version: JUDGMENT_PROBLEM_SNAPSHOT_VERSION,
615
+ problem_id: snapshot.problem_id,
616
+ revision: snapshot.revision,
617
+ status
618
+ });
619
+ }
620
+ /**
621
+ * Derive the content address without writing. This validates only the
622
+ * snapshot shape; canonical reference resolution belongs to providers.
623
+ */
624
+ export function computeJudgmentProblemSnapshotId(value) {
625
+ return contentDigest(payloadFor(validateSnapshot(value)));
626
+ }
627
+ export async function saveJudgmentProblemSnapshot(request) {
628
+ if (!isPlainRecord(request))
629
+ fail('invalid_request', 'save request must be a plain object');
630
+ const root = requireRoot(request.root);
631
+ const snapshot = validateSnapshot(request.snapshot);
632
+ validateRequiredReferences(snapshot);
633
+ if (!request.referenceProvider || typeof request.referenceProvider.resolve !== 'function') {
634
+ fail('invalid_request', 'referenceProvider.resolve is required');
635
+ }
636
+ const context = validateAccessContext(request.access);
637
+ const accessProvider = request.accessProvider ?? defaultJudgmentProblemSnapshotAccessProvider;
638
+ await authorize('write', snapshot, context, accessProvider);
639
+ for (const reference of snapshot.references) {
640
+ if (reference.evidence === undefined)
641
+ continue;
642
+ try {
643
+ const allowed = await accessProvider.authorize({
644
+ action: 'read',
645
+ context,
646
+ acl: reference.evidence.access,
647
+ snapshot,
648
+ reference
649
+ });
650
+ if (allowed === false) {
651
+ fail('unauthorized', `evidence read access was denied for ${reference.kind}/${reference.id}@${reference.revision}`, reference);
652
+ }
653
+ }
654
+ catch (error) {
655
+ if (error instanceof JudgmentProblemSnapshotError)
656
+ throw error;
657
+ fail('unauthorized', `evidence read access was denied for ${reference.kind}/${reference.id}@${reference.revision}`, reference);
658
+ }
659
+ }
660
+ await resolveReferences(snapshot, 'save', context, request.referenceProvider);
661
+ const snapshotId = computeJudgmentProblemSnapshotId(snapshot);
662
+ const envelope = {
663
+ snapshot_id: snapshotId,
664
+ snapshot_version: JUDGMENT_PROBLEM_SNAPSHOT_VERSION,
665
+ snapshot
666
+ };
667
+ const bytes = envelopeBytes(envelope);
668
+ const directory = await assertRevisionDirectory(root, true);
669
+ const file = locatorFor(root, snapshot.problem_id, snapshot.revision).file;
670
+ const temporary = path.join(directory, `.tmp-${process.pid}-${randomUUID()}`);
671
+ let handle;
672
+ try {
673
+ handle = await open(temporary, 'wx', 0o600);
674
+ await handle.writeFile(bytes, 'utf8');
675
+ await handle.sync();
676
+ await handle.close();
677
+ handle = undefined;
678
+ try {
679
+ await link(temporary, file);
680
+ return receipt(snapshot, snapshotId, 'created');
681
+ }
682
+ catch (error) {
683
+ if (error.code !== 'EEXIST')
684
+ throw error;
685
+ const existingBytes = await readRegular(file);
686
+ const existing = parseEnvelope(existingBytes).envelope;
687
+ if (existing.snapshot.problem_id !== snapshot.problem_id || existing.snapshot.revision !== snapshot.revision) {
688
+ fail('integrity_mismatch', 'snapshot revision locator points to a different problem revision');
689
+ }
690
+ if (existing.snapshot_id !== snapshotId || existingBytes !== bytes) {
691
+ fail('conflict', `Problem ${snapshot.problem_id}@${snapshot.revision} already has a different snapshot`);
692
+ }
693
+ return receipt(snapshot, snapshotId, 'existing');
694
+ }
695
+ }
696
+ catch (error) {
697
+ if (error instanceof JudgmentProblemSnapshotError)
698
+ throw error;
699
+ throw new JudgmentProblemSnapshotError('storage_io_error', 'judgment problem snapshot could not be saved');
700
+ }
701
+ finally {
702
+ if (handle !== undefined)
703
+ await handle.close().catch(() => undefined);
704
+ await unlink(temporary).catch(() => undefined);
705
+ }
706
+ }
707
+ export async function loadJudgmentProblemSnapshot(request) {
708
+ if (!isPlainRecord(request))
709
+ fail('invalid_request', 'load request must be a plain object');
710
+ const root = requireRoot(request.root);
711
+ const context = validateAccessContext(request.access);
712
+ const referenceResolution = request.reference_resolution ?? 'current';
713
+ if (referenceResolution !== 'current' && referenceResolution !== 'historical') {
714
+ fail('invalid_request', 'reference_resolution must be current or historical');
715
+ }
716
+ if (request.snapshot_id === undefined && (request.problem_id === undefined || request.revision === undefined)) {
717
+ fail('invalid_request', 'load requires snapshot_id or problem_id and revision');
718
+ }
719
+ const suppliedId = request.snapshot_id === undefined ? undefined : requireSnapshotId(request.snapshot_id, 'snapshot_id');
720
+ const suppliedProblemId = request.problem_id === undefined ? undefined : requireString(request.problem_id, 'problem_id');
721
+ const suppliedRevision = request.revision === undefined ? undefined : requireRevision(request.revision, 'revision');
722
+ const parsed = suppliedProblemId !== undefined && suppliedRevision !== undefined
723
+ ? parseEnvelope(await readRegular(locatorFor(root, suppliedProblemId, suppliedRevision).file))
724
+ : await findById(root, suppliedId);
725
+ const { envelope } = parsed;
726
+ if (suppliedId !== undefined && envelope.snapshot_id !== suppliedId)
727
+ fail('integrity_mismatch', 'requested snapshot_id does not match stored content');
728
+ if (suppliedProblemId !== undefined && envelope.snapshot.problem_id !== suppliedProblemId)
729
+ fail('integrity_mismatch', 'requested problem_id does not match stored content');
730
+ if (suppliedRevision !== undefined && envelope.snapshot.revision !== suppliedRevision)
731
+ fail('integrity_mismatch', 'requested revision does not match stored content');
732
+ const accessProvider = request.accessProvider ?? defaultJudgmentProblemSnapshotAccessProvider;
733
+ await authorize('read', envelope.snapshot, context, accessProvider);
734
+ for (const reference of envelope.snapshot.references) {
735
+ if (reference.evidence !== undefined) {
736
+ try {
737
+ const allowed = await accessProvider.authorize({
738
+ action: 'read',
739
+ context,
740
+ acl: reference.evidence.access,
741
+ snapshot: envelope.snapshot,
742
+ reference
743
+ });
744
+ if (allowed === false)
745
+ fail('unauthorized', `evidence access was denied for ${reference.kind}/${reference.id}@${reference.revision}`, reference);
746
+ }
747
+ catch (error) {
748
+ if (error instanceof JudgmentProblemSnapshotError)
749
+ throw error;
750
+ fail('unauthorized', `evidence access was denied for ${reference.kind}/${reference.id}@${reference.revision}`, reference);
751
+ }
752
+ }
753
+ }
754
+ if (!request.referenceProvider || typeof request.referenceProvider.resolve !== 'function') {
755
+ fail('invalid_request', `referenceProvider.resolve is required for ${referenceResolution} reference resolution`);
756
+ }
757
+ await resolveReferences(envelope.snapshot, referenceResolution === 'current' ? 'read' : 'historical_read', context, request.referenceProvider);
758
+ return envelope.snapshot;
759
+ }
760
+ export async function validateJudgmentProblemSnapshot(request) {
761
+ if (!isPlainRecord(request))
762
+ fail('invalid_request', 'validation request must be a plain object');
763
+ const snapshot = validateSnapshot(request.snapshot);
764
+ validateRequiredReferences(snapshot);
765
+ const context = validateAccessContext(request.access);
766
+ const referenceResolution = request.reference_resolution ?? 'current';
767
+ if (referenceResolution !== 'current' && referenceResolution !== 'historical') {
768
+ fail('invalid_request', 'reference_resolution must be current or historical');
769
+ }
770
+ if (!request.referenceProvider || typeof request.referenceProvider.resolve !== 'function') {
771
+ fail('invalid_request', `referenceProvider.resolve is required for ${referenceResolution} reference resolution`);
772
+ }
773
+ const accessProvider = request.accessProvider ?? defaultJudgmentProblemSnapshotAccessProvider;
774
+ await authorize('read', snapshot, context, accessProvider);
775
+ for (const reference of snapshot.references) {
776
+ if (reference.evidence === undefined)
777
+ continue;
778
+ try {
779
+ const allowed = await accessProvider.authorize({
780
+ action: 'read',
781
+ context,
782
+ acl: reference.evidence.access,
783
+ snapshot,
784
+ reference
785
+ });
786
+ if (allowed === false)
787
+ fail('unauthorized', `evidence access was denied for ${reference.kind}/${reference.id}@${reference.revision}`, reference);
788
+ }
789
+ catch (error) {
790
+ if (error instanceof JudgmentProblemSnapshotError)
791
+ throw error;
792
+ fail('unauthorized', `evidence access was denied for ${reference.kind}/${reference.id}@${reference.revision}`, reference);
793
+ }
794
+ }
795
+ await resolveReferences(snapshot, referenceResolution === 'current' ? 'read' : 'historical_read', context, request.referenceProvider);
796
+ }
797
+ function validateAccessContext(value) {
798
+ if (!isPlainRecord(value) || typeof value.principal !== 'string' || value.principal.trim().length === 0 || value.principal.includes('\0')) {
799
+ fail('unauthorized', 'a trusted principal is required');
800
+ }
801
+ return { principal: value.principal };
802
+ }
803
+ /**
804
+ * Adapt the generic foundation store to the snapshot resolver. The store
805
+ * read and exact digest check always run before an optional caller validator.
806
+ * `save`/`read` additionally validate the definition for judgment use and
807
+ * reference scope. `historical_read` deliberately skips those past-use and
808
+ * applicability checks, but the trusted store must still enforce its current
809
+ * read ACL; an old revision cannot be read through a revoked current ACL.
810
+ * The optional validator can add organization-specific policy, but cannot
811
+ * bypass the canonical store or digest check.
812
+ */
813
+ export function createJudgmentProblemFoundationReferenceProvider(options) {
814
+ const objectiveReader = {
815
+ async read(reference, context) {
816
+ return await options.store.read(reference, { principal: context.principal });
817
+ }
818
+ };
819
+ return {
820
+ async resolve(input) {
821
+ if (input.reference.kind === 'observation') {
822
+ return resolveObservationReference(input, options, objectiveReader);
823
+ }
824
+ if (!['objective', 'variable', 'model', 'constraint'].includes(input.reference.kind)) {
825
+ if (options.resolveOther)
826
+ return options.resolveOther(input);
827
+ return { status: 'unresolved', message: `No provider is registered for ${input.reference.kind}` };
828
+ }
829
+ const resolved = await options.store.read({
830
+ id: input.reference.id,
831
+ type: input.reference.kind,
832
+ revision: input.reference.revision
833
+ }, { principal: input.context.principal });
834
+ if (!resolved)
835
+ return { status: 'missing', message: 'Foundation revision was not found' };
836
+ if (resolved.digest !== input.reference.digest)
837
+ return { status: 'unresolved', message: 'Foundation digest changed' };
838
+ if (!isPlainRecord(resolved.definition) || resolved.definition.type !== input.reference.kind) {
839
+ return { status: 'unresolved', message: 'Foundation revision did not return a matching definition' };
840
+ }
841
+ const definition = resolved.definition;
842
+ if (input.phase !== 'historical_read') {
843
+ const validation = validateFoundationDefinition(definition, { use: 'judgment' });
844
+ if (!validation.valid) {
845
+ const notApplicable = validation.issues.some((issue) => (issue.code === 'UNAUTHORIZED_USE' || issue.code === 'UNVERIFIED_MODEL'));
846
+ return {
847
+ status: notApplicable ? 'not_applicable' : 'unresolved',
848
+ message: validation.issues.map((issue) => issue.message).join('; ')
849
+ };
850
+ }
851
+ const applicability = validateFoundationReferenceApplicability(input.reference, definition);
852
+ if (!applicability.valid) {
853
+ return { status: 'not_applicable', message: applicability.message };
854
+ }
855
+ }
856
+ if (definition.type === 'objective') {
857
+ if (input.phase === 'historical_read') {
858
+ const dependencyResult = await validateObjectiveDependencies(definition, input, options);
859
+ if (dependencyResult)
860
+ return dependencyResult;
861
+ }
862
+ else {
863
+ const readiness = await checkObjectiveReadiness(objectiveReader, input.reference.id, { principal: input.context.principal }, input.reference.revision);
864
+ if (!readiness.ready)
865
+ return objectiveReadinessResult(readiness.issues);
866
+ const dependencyResult = await validateObjectiveDependencies(definition, input, options);
867
+ if (dependencyResult)
868
+ return dependencyResult;
869
+ }
870
+ }
871
+ if (definition.type === 'model') {
872
+ const dependencyResult = await validateModelDependencies(definition, input, options);
873
+ if (dependencyResult)
874
+ return dependencyResult;
875
+ }
876
+ if (options.validate) {
877
+ return options.validate({
878
+ ...input,
879
+ record: { definition, digest: resolved.digest }
880
+ });
881
+ }
882
+ return { status: 'resolved', digest: resolved.digest };
883
+ }
884
+ };
885
+ }
886
+ function objectiveReadinessResult(issues) {
887
+ const notApplicable = issues.some((issue) => (issue.code === 'UNAUTHORIZED_USE' || issue.code === 'CRITERION_PERIOD_MISMATCH'));
888
+ return {
889
+ status: notApplicable ? 'not_applicable' : 'unresolved',
890
+ message: issues.map((issue) => issue.message).join('; ')
891
+ };
892
+ }
893
+ async function validateObjectiveDependencies(definition, input, options) {
894
+ for (const criterion of definition.criteria) {
895
+ const dependency = criterion?.variableRef;
896
+ if (!isVariableRevision(dependency)) {
897
+ return { status: 'unresolved', message: 'Objective criterion does not reference a valid Variable revision' };
898
+ }
899
+ const resolved = await options.store.read(dependency, { principal: input.context.principal });
900
+ if (!resolved)
901
+ return { status: 'missing', message: `Objective Variable ${dependency.id}@${dependency.revision} was not found` };
902
+ if (!matchingFoundationRecord(resolved, dependency, 'variable')) {
903
+ return { status: 'unresolved', message: `Objective Variable ${dependency.id}@${dependency.revision} is not the requested revision` };
904
+ }
905
+ const pinned = findSnapshotReference(input.snapshot, dependency);
906
+ if (pinned && pinned.digest !== resolved.digest) {
907
+ return { status: 'unresolved', message: `Objective Variable ${dependency.id}@${dependency.revision} digest changed` };
908
+ }
909
+ }
910
+ return undefined;
911
+ }
912
+ async function validateModelDependencies(definition, input, options) {
913
+ const dependencies = [...definition.inputVariableRefs, ...definition.outputVariableRefs];
914
+ const seen = new Set();
915
+ for (const dependency of dependencies) {
916
+ if (!isVariableRevision(dependency)) {
917
+ return { status: 'unresolved', message: 'Model input/output must reference a valid Variable revision' };
918
+ }
919
+ const key = `${dependency.id}@${dependency.revision}`;
920
+ if (seen.has(key))
921
+ continue;
922
+ seen.add(key);
923
+ const resolved = await options.store.read(dependency, { principal: input.context.principal });
924
+ if (!resolved)
925
+ return { status: 'missing', message: `Model Variable ${dependency.id}@${dependency.revision} was not found` };
926
+ if (!matchingFoundationRecord(resolved, dependency, 'variable')) {
927
+ return { status: 'unresolved', message: `Model Variable ${dependency.id}@${dependency.revision} is not the requested revision` };
928
+ }
929
+ const pinned = findSnapshotReference(input.snapshot, dependency);
930
+ if (pinned && pinned.digest !== resolved.digest) {
931
+ return { status: 'unresolved', message: `Model Variable ${dependency.id}@${dependency.revision} digest changed` };
932
+ }
933
+ if (input.phase === 'historical_read')
934
+ continue;
935
+ const definitionResult = validateFoundationDefinition(resolved.definition, { use: 'judgment' });
936
+ if (!definitionResult.valid) {
937
+ const notApplicable = definitionResult.issues.some((issue) => issue.code === 'UNAUTHORIZED_USE');
938
+ return {
939
+ status: notApplicable ? 'not_applicable' : 'unresolved',
940
+ message: definitionResult.issues.map((issue) => issue.message).join('; ')
941
+ };
942
+ }
943
+ const applicability = validateFoundationReferenceApplicability(input.reference, resolved.definition);
944
+ if (!applicability.valid)
945
+ return { status: 'not_applicable', message: applicability.message };
946
+ }
947
+ return undefined;
948
+ }
949
+ function isVariableRevision(value) {
950
+ return isPlainRecord(value)
951
+ && value.type === 'variable'
952
+ && typeof value.id === 'string'
953
+ && value.id.length > 0
954
+ && typeof value.revision === 'string'
955
+ && /^[1-9]\d*$/u.test(value.revision);
956
+ }
957
+ function matchingFoundationRecord(record, reference, type) {
958
+ return typeof record.digest === 'string'
959
+ && isPlainRecord(record.definition)
960
+ && record.definition.type === type
961
+ && record.definition.id === reference.id
962
+ && record.definition.revision === reference.revision;
963
+ }
964
+ function findSnapshotReference(snapshot, reference) {
965
+ return snapshot?.references.find((candidate) => (candidate.kind === reference.type
966
+ && candidate.id === reference.id
967
+ && candidate.revision === reference.revision));
968
+ }
969
+ async function resolveObservationReference(input, options, objectiveReader) {
970
+ if (!options.resolveOther)
971
+ return { status: 'unresolved', message: 'No canonical Observation provider is registered' };
972
+ const resolved = await options.resolveOther(input);
973
+ if (resolved.status !== 'resolved')
974
+ return resolved;
975
+ const descriptor = input.reference.measurement;
976
+ const canonical = resolved.canonical?.measurement;
977
+ if (input.phase === 'historical_read') {
978
+ if (descriptor !== undefined && canonical !== undefined && !sameMeasurementDescriptor(descriptor, canonical)) {
979
+ return { status: 'unresolved', message: 'Historical Observation measurement metadata changed' };
980
+ }
981
+ return resolved;
982
+ }
983
+ if (descriptor === undefined || canonical === undefined) {
984
+ return { status: 'unresolved', message: 'Current Observation requires canonical measurement metadata and a pinned descriptor' };
985
+ }
986
+ if (!sameMeasurementDescriptor(descriptor, canonical)) {
987
+ return { status: 'unresolved', message: 'Observation measurement descriptor does not match canonical metadata' };
988
+ }
989
+ const snapshot = input.snapshot;
990
+ if (!snapshot)
991
+ return { status: 'unresolved', message: 'Current Observation validation requires the complete snapshot bundle' };
992
+ const objectiveReference = snapshot.references.find((candidate) => candidate.kind === 'objective');
993
+ if (!objectiveReference)
994
+ return { status: 'unresolved', message: 'Current Observation requires an Objective reference' };
995
+ const objectiveRecord = await options.store.read({ id: objectiveReference.id, type: 'objective', revision: objectiveReference.revision }, { principal: input.context.principal });
996
+ if (!objectiveRecord)
997
+ return { status: 'missing', message: 'Objective for Observation measurement was not found' };
998
+ const objectiveRevision = {
999
+ id: objectiveReference.id,
1000
+ type: 'objective',
1001
+ revision: objectiveReference.revision
1002
+ };
1003
+ if (!matchingFoundationRecord(objectiveRecord, objectiveRevision, 'objective')) {
1004
+ return { status: 'unresolved', message: 'Objective for Observation measurement is not the requested revision' };
1005
+ }
1006
+ if (objectiveRecord.digest !== objectiveReference.digest) {
1007
+ return { status: 'unresolved', message: 'Objective for Observation measurement digest changed' };
1008
+ }
1009
+ const objectiveDefinition = objectiveRecord.definition;
1010
+ const objectiveApplicability = validateFoundationReferenceApplicability(objectiveReference, objectiveDefinition);
1011
+ if (!objectiveApplicability.valid)
1012
+ return { status: 'not_applicable', message: objectiveApplicability.message };
1013
+ const readiness = await checkObjectiveReadiness(objectiveReader, objectiveReference.id, { principal: input.context.principal }, objectiveReference.revision);
1014
+ if (!readiness.ready)
1015
+ return objectiveReadinessResult(readiness.issues);
1016
+ const variableRecord = await options.store.read(descriptor.variableRef, { principal: input.context.principal });
1017
+ if (!variableRecord)
1018
+ return { status: 'missing', message: 'Observation measurement Variable was not found' };
1019
+ if (!matchingFoundationRecord(variableRecord, descriptor.variableRef, 'variable')) {
1020
+ return { status: 'unresolved', message: 'Observation measurement Variable is not the requested revision' };
1021
+ }
1022
+ const pinnedVariable = findSnapshotReference(snapshot, descriptor.variableRef);
1023
+ if (pinnedVariable && pinnedVariable.digest !== variableRecord.digest) {
1024
+ return { status: 'unresolved', message: 'Observation measurement Variable digest changed' };
1025
+ }
1026
+ const compatibility = validateEvaluationCompatibility(variableRecord.definition, descriptor, { period: objectiveDefinition.evaluationPeriod });
1027
+ if (!compatibility.valid) {
1028
+ const notApplicable = compatibility.issues.some((issue) => issue.code === 'UNAUTHORIZED_USE');
1029
+ return {
1030
+ status: notApplicable ? 'not_applicable' : 'unresolved',
1031
+ message: compatibility.issues.map((issue) => issue.message).join('; ')
1032
+ };
1033
+ }
1034
+ if (!scopeContainsReference(descriptor.scope, input.reference)) {
1035
+ return { status: 'not_applicable', message: 'Observation measurement scope does not cover the Observation reference' };
1036
+ }
1037
+ return resolved;
1038
+ }
1039
+ function sameMeasurementDescriptor(left, right) {
1040
+ return canonicalJson(left) === canonicalJson(right);
1041
+ }
1042
+ function validateFoundationReferenceApplicability(reference, definition) {
1043
+ const scopes = definition.type === 'model'
1044
+ ? [definition.scope, definition.applicability]
1045
+ : [definition.scope];
1046
+ for (const scope of scopes) {
1047
+ if (!scopeContainsReference(scope, reference)) {
1048
+ return {
1049
+ valid: false,
1050
+ message: `Foundation ${definition.type}/${definition.id}@${definition.revision} is outside the reference scope or validity period`
1051
+ };
1052
+ }
1053
+ }
1054
+ return { valid: true };
1055
+ }
1056
+ function scopeContainsReference(scope, reference) {
1057
+ if (!scope.subjectIds.includes(reference.scope.id))
1058
+ return false;
1059
+ const sourceFrom = Date.parse(scope.validFrom);
1060
+ const sourceUntil = scope.validUntil === undefined ? Number.POSITIVE_INFINITY : Date.parse(scope.validUntil);
1061
+ const referenceFrom = Date.parse(reference.valid_from);
1062
+ const referenceUntil = reference.valid_to == null ? Number.POSITIVE_INFINITY : Date.parse(reference.valid_to);
1063
+ // Open-ended source/reference periods are represented by +Infinity. Only
1064
+ // supplied timestamps must be finite; rejecting Infinity here would make
1065
+ // every valid open-ended Foundation scope look inapplicable.
1066
+ if (!Number.isFinite(sourceFrom) || !Number.isFinite(referenceFrom))
1067
+ return false;
1068
+ if (scope.validUntil !== undefined && !Number.isFinite(sourceUntil))
1069
+ return false;
1070
+ if (reference.valid_to != null && !Number.isFinite(referenceUntil))
1071
+ return false;
1072
+ return sourceFrom <= referenceFrom && sourceUntil >= referenceUntil;
1073
+ }