@unson/brainbase-mcp 0.5.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 (92) hide show
  1. package/README.md +8 -0
  2. package/contracts/judgment-dag/digest.json +2 -2
  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 +809 -0
  9. package/dist/company-os-evaluation.d.ts +193 -0
  10. package/dist/company-os-evaluation.js +1150 -0
  11. package/dist/company-os-impact-review.d.ts +248 -0
  12. package/dist/company-os-impact-review.js +663 -0
  13. package/dist/company-os-learning-adoption.d.ts +288 -0
  14. package/dist/company-os-learning-adoption.js +944 -0
  15. package/dist/company-os-objectives.d.ts +52 -0
  16. package/dist/company-os-objectives.js +197 -0
  17. package/dist/company-os-receipt-adapter.d.ts +109 -0
  18. package/dist/company-os-receipt-adapter.js +636 -0
  19. package/dist/constraint-exception-store.d.ts +23 -0
  20. package/dist/constraint-exception-store.js +108 -0
  21. package/dist/constraint-resolution.d.ts +175 -0
  22. package/dist/constraint-resolution.js +922 -0
  23. package/dist/decision-adapter.d.ts +145 -0
  24. package/dist/decision-adapter.js +586 -0
  25. package/dist/durable-wait-http.d.ts +51 -0
  26. package/dist/durable-wait-http.js +497 -0
  27. package/dist/durable-waits.d.ts +234 -0
  28. package/dist/durable-waits.js +944 -0
  29. package/dist/execution-authority-http.d.ts +28 -0
  30. package/dist/execution-authority-http.js +359 -0
  31. package/dist/execution-authority.d.ts +201 -0
  32. package/dist/execution-authority.js +634 -0
  33. package/dist/foundation-catalog.d.ts +26 -0
  34. package/dist/foundation-catalog.js +206 -0
  35. package/dist/foundation-constraint-store.d.ts +50 -0
  36. package/dist/foundation-constraint-store.js +215 -0
  37. package/dist/foundation-http.d.ts +90 -0
  38. package/dist/foundation-http.js +439 -0
  39. package/dist/foundation-store.d.ts +132 -0
  40. package/dist/foundation-store.js +654 -0
  41. package/dist/judgment-dag-composition.d.ts +224 -0
  42. package/dist/judgment-dag-composition.js +731 -0
  43. package/dist/judgment-dag.d.ts +2 -0
  44. package/dist/judgment-dag.js +1 -0
  45. package/dist/judgment-problem-snapshot.d.ts +185 -0
  46. package/dist/judgment-problem-snapshot.js +777 -0
  47. package/dist/judgment-view-http.d.ts +22 -0
  48. package/dist/judgment-view-http.js +128 -0
  49. package/dist/judgment-view.d.ts +222 -0
  50. package/dist/judgment-view.js +627 -0
  51. package/dist/knowledge-adapter.d.ts +112 -0
  52. package/dist/knowledge-adapter.js +691 -0
  53. package/dist/ontology-foundation.d.ts +209 -0
  54. package/dist/ontology-foundation.js +1000 -0
  55. package/dist/ontology.d.ts +13 -0
  56. package/dist/ontology.js +14 -0
  57. package/dist/organization-connection.d.ts +164 -0
  58. package/dist/organization-connection.js +428 -0
  59. package/dist/personal-knowledge.d.ts +120 -0
  60. package/dist/personal-knowledge.js +750 -0
  61. package/dist/problem-candidates.d.ts +149 -0
  62. package/dist/problem-candidates.js +898 -0
  63. package/dist/problem-selection.d.ts +238 -0
  64. package/dist/problem-selection.js +995 -0
  65. package/dist/resource-reservation-http.d.ts +31 -0
  66. package/dist/resource-reservation-http.js +371 -0
  67. package/dist/resource-reservations.d.ts +221 -0
  68. package/dist/resource-reservations.js +577 -0
  69. package/dist/server.d.ts +97 -0
  70. package/dist/server.js +180 -1
  71. package/dist/ssot.d.ts +6 -1
  72. package/dist/ssot.js +63 -5
  73. package/dist/streamable-http.d.ts +42 -0
  74. package/dist/streamable-http.js +288 -0
  75. package/dist/types.d.ts +20 -0
  76. package/dist/world-model.d.ts +181 -0
  77. package/dist/world-model.js +873 -0
  78. package/package.json +145 -3
  79. package/ui/icons/mana/LICENSE.tabler.txt +21 -0
  80. package/ui/icons/mana/affiliate.svg +20 -0
  81. package/ui/icons/mana/brand-github.svg +15 -0
  82. package/ui/icons/mana/brand-google-drive.svg +17 -0
  83. package/ui/icons/mana/brand-slack.svg +18 -0
  84. package/ui/icons/mana/cpu.svg +24 -0
  85. package/ui/judgment-view.css +238 -0
  86. package/ui/judgment-view.js +508 -0
  87. package/ui/objective-editor.css +180 -0
  88. package/ui/objective-editor.js +1139 -0
  89. package/ui/outcome-knowledge.css +365 -0
  90. package/ui/outcome-knowledge.js +1570 -0
  91. package/ui/outcome-mana.css +255 -0
  92. package/ui/outcome-mana.js +2325 -0
@@ -0,0 +1,636 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { initializePersonalOs, mutatePersonalOsWithSidecar, readPersonalOsSidecar } from './ssot.js';
3
+ export const COMPANY_OS_RECEIPT_ADAPTER_CONTRACT_VERSION = '0.1.1';
4
+ export const COMPANY_OS_RECEIPT_LINK_CATALOG_VERSION = 2;
5
+ export const COMPANY_OS_RECEIPT_LINK_SIDECAR = 'evidence/company-os-receipt-links.json';
6
+ export class CompanyOsReceiptAdapterError extends Error {
7
+ code;
8
+ constructor(code, message) {
9
+ super(message);
10
+ this.name = 'CompanyOsReceiptAdapterError';
11
+ this.code = code;
12
+ }
13
+ }
14
+ export function createCompanyOsReceiptAdapter(options) {
15
+ assertOptions(options);
16
+ return new GraphCompanyOsReceiptAdapter(options);
17
+ }
18
+ /**
19
+ * Reuses the existing read-only OutcomeCasePort without importing any
20
+ * organization runtime. OutcomeCase closure and evaluation remain owned by
21
+ * that provider; this adapter only exposes a source projection.
22
+ */
23
+ export function createOutcomeCaseReceiptSourcePort(port) {
24
+ if (!port || typeof port.read !== 'function') {
25
+ throw new CompanyOsReceiptAdapterError('invalid_input', 'OutcomeCase port with read is required');
26
+ }
27
+ return {
28
+ async read(reference, access) {
29
+ const normalizedAccess = normalizeAccess(access);
30
+ assertSourceReference(reference, 'OutcomeCase source reference');
31
+ if (reference.kind !== 'outcome_case') {
32
+ throw new CompanyOsReceiptAdapterError('invalid_input', 'OutcomeCase source port received a different source kind');
33
+ }
34
+ const outcomeReference = {
35
+ id: reference.id,
36
+ ...(reference.revision === undefined ? {} : { revision: reference.revision }),
37
+ ...(reference.hash === null ? {} : { digest: reference.hash })
38
+ };
39
+ let resolved;
40
+ try {
41
+ resolved = await port.read(outcomeReference, {
42
+ principal: normalizedAccess.principal,
43
+ ...(normalizedAccess.scope === undefined ? {} : { scope: normalizedAccess.scope })
44
+ });
45
+ }
46
+ catch (error) {
47
+ throw mapSourceError(error, `OutcomeCase ${reference.id}`);
48
+ }
49
+ if (!resolved || !resolved.reference
50
+ || !isNonEmptyString(resolved.reference.id)
51
+ || !isNonEmptyString(resolved.reference.revision)
52
+ || !isNonEmptyString(resolved.reference.digest)) {
53
+ throw new CompanyOsReceiptAdapterError('source_unavailable', `OutcomeCase ${reference.id} returned invalid canonical metadata`);
54
+ }
55
+ assertOutcomeCaseSourceProjection(resolved.source, `OutcomeCase ${reference.id}`);
56
+ return {
57
+ reference: {
58
+ kind: 'outcome_case',
59
+ id: resolved.reference.id,
60
+ revision: resolved.reference.revision,
61
+ hash: resolved.reference.digest,
62
+ state: resolved.source.state,
63
+ owner_refs: resolved.source.owner_refs.map(cloneOutcomeCaseOwnerReference),
64
+ ...(resolved.source.conditions === undefined
65
+ ? {}
66
+ : { conditions: cloneOutcomeCaseConditions(resolved.source.conditions) })
67
+ }
68
+ };
69
+ }
70
+ };
71
+ }
72
+ function assertOutcomeCaseSourceProjection(value, label) {
73
+ if (!isRecord(value) || !isNonEmptyString(value.state) || !Array.isArray(value.owner_refs)) {
74
+ throw new CompanyOsReceiptAdapterError('source_unavailable', `${label} returned invalid canonical source metadata`);
75
+ }
76
+ value.owner_refs.forEach((owner, index) => assertOutcomeCaseOwnerReference(owner, `${label}.source.owner_refs[${index}]`));
77
+ if (value.conditions !== undefined) {
78
+ assertOutcomeCaseConditions(value.conditions, `${label}.source.conditions`);
79
+ }
80
+ }
81
+ function assertOutcomeCaseOwnerReference(value, label) {
82
+ if (!isRecord(value) || (value.status !== 'typed' && value.status !== 'unknown')) {
83
+ throw new CompanyOsReceiptAdapterError('source_unavailable', `${label} has an invalid status`);
84
+ }
85
+ if (value.status === 'unknown') {
86
+ if (!['not_recorded', 'legacy_untyped', 'source_unavailable'].includes(String(value.reason))) {
87
+ throw new CompanyOsReceiptAdapterError('source_unavailable', `${label} has an invalid unknown reason`);
88
+ }
89
+ return;
90
+ }
91
+ if (!isRecord(value.ref)
92
+ || !isNonEmptyString(value.ref.id)
93
+ || !isNonEmptyString(value.ref.type)
94
+ || (value.ref.revision !== undefined && !isNonEmptyString(value.ref.revision))
95
+ || (value.ref.digest !== undefined && !isNonEmptyString(value.ref.digest))) {
96
+ throw new CompanyOsReceiptAdapterError('source_unavailable', `${label}.ref has an invalid shape`);
97
+ }
98
+ }
99
+ function assertOutcomeCaseConditions(value, label) {
100
+ if (!isRecord(value)) {
101
+ throw new CompanyOsReceiptAdapterError('source_unavailable', `${label} must be an object`);
102
+ }
103
+ assertOutcomeCaseConditionValue(value, label);
104
+ }
105
+ function assertOutcomeCaseConditionValue(value, label) {
106
+ if (value === null || typeof value === 'string' || typeof value === 'boolean')
107
+ return;
108
+ if (typeof value === 'number') {
109
+ if (Number.isFinite(value))
110
+ return;
111
+ throw new CompanyOsReceiptAdapterError('source_unavailable', `${label} contains a non-finite number`);
112
+ }
113
+ if (Array.isArray(value)) {
114
+ value.forEach((item, index) => assertOutcomeCaseConditionValue(item, `${label}[${index}]`));
115
+ return;
116
+ }
117
+ if (isRecord(value)) {
118
+ for (const [key, item] of Object.entries(value)) {
119
+ if (!isNonEmptyString(key)) {
120
+ throw new CompanyOsReceiptAdapterError('source_unavailable', `${label} contains an empty key`);
121
+ }
122
+ assertOutcomeCaseConditionValue(item, `${label}.${key}`);
123
+ }
124
+ return;
125
+ }
126
+ throw new CompanyOsReceiptAdapterError('source_unavailable', `${label} contains a non-JSON value`);
127
+ }
128
+ function cloneOutcomeCaseOwnerReference(owner) {
129
+ return owner.status === 'typed'
130
+ ? {
131
+ status: 'typed',
132
+ ref: {
133
+ id: owner.ref.id,
134
+ type: owner.ref.type,
135
+ ...(owner.ref.revision === undefined ? {} : { revision: owner.ref.revision }),
136
+ ...(owner.ref.digest === undefined ? {} : { digest: owner.ref.digest })
137
+ }
138
+ }
139
+ : { status: 'unknown', reason: owner.reason };
140
+ }
141
+ function cloneOutcomeCaseConditions(conditions) {
142
+ return cloneOutcomeCaseConditionValue(conditions);
143
+ }
144
+ function cloneOutcomeCaseConditionValue(value) {
145
+ if (Array.isArray(value))
146
+ return value.map(cloneOutcomeCaseConditionValue);
147
+ if (isRecord(value)) {
148
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, cloneOutcomeCaseConditionValue(child)]));
149
+ }
150
+ return value;
151
+ }
152
+ class GraphCompanyOsReceiptAdapter {
153
+ options;
154
+ constructor(options) {
155
+ this.options = options;
156
+ }
157
+ async link(request) {
158
+ const normalized = normalizeLinkRequest(request);
159
+ await initializePersonalOs(this.options.dataDir);
160
+ // External reads happen before the SSOT lock. The transaction callback
161
+ // below must remain pure so a provider cannot deadlock on the same lock.
162
+ const currentSource = await this.readCurrentSource(normalized.source, normalized.access, 'link');
163
+ // The request is only a lookup locator. The source owner's canonical
164
+ // projection is the trusted snapshot that must be persisted.
165
+ const record = makeLinkRecord(normalized, currentSource.reference);
166
+ const stored = await mutatePersonalOsWithSidecar(this.options.dataDir, COMPANY_OS_RECEIPT_LINK_SIDECAR, (current, sidecarContent) => {
167
+ const catalog = parseCatalog(sidecarContent);
168
+ const existing = catalog.records.find((candidate) => candidate.id === record.id);
169
+ if (existing !== undefined) {
170
+ if (!sameLinkPayload(existing, record)) {
171
+ throw new CompanyOsReceiptAdapterError('revision_conflict', `Receipt link ${record.id} already exists with different content`);
172
+ }
173
+ return { next: current, sidecarContent: serializeCatalog(catalog), result: existing };
174
+ }
175
+ const nextCatalog = {
176
+ version: COMPANY_OS_RECEIPT_LINK_CATALOG_VERSION,
177
+ records: [...catalog.records, record]
178
+ };
179
+ return { next: current, sidecarContent: serializeCatalog(nextCatalog), result: record };
180
+ });
181
+ return makeProjection(stored, currentSource);
182
+ }
183
+ async read(id, access) {
184
+ if (!isNonEmptyString(id)) {
185
+ throw new CompanyOsReceiptAdapterError('invalid_input', 'Receipt link id is required');
186
+ }
187
+ const normalizedAccess = normalizeAccess(access);
188
+ await initializePersonalOs(this.options.dataDir);
189
+ const catalog = parseCatalog(await readPersonalOsSidecar(this.options.dataDir, COMPANY_OS_RECEIPT_LINK_SIDECAR));
190
+ const record = catalog.records.find((candidate) => candidate.id === id);
191
+ if (record === undefined)
192
+ return null;
193
+ const currentSource = await this.readCurrentSource(record.source, normalizedAccess);
194
+ return makeProjection(record, currentSource);
195
+ }
196
+ async list(access) {
197
+ const normalizedAccess = normalizeAccess(access);
198
+ await initializePersonalOs(this.options.dataDir);
199
+ const catalog = parseCatalog(await readPersonalOsSidecar(this.options.dataDir, COMPANY_OS_RECEIPT_LINK_SIDECAR));
200
+ const projections = [];
201
+ for (const record of catalog.records) {
202
+ const currentSource = await this.readCurrentSource(record.source, normalizedAccess);
203
+ projections.push(makeProjection(record, currentSource));
204
+ }
205
+ return projections;
206
+ }
207
+ async readCurrentSource(reference, access, mode = 'stored') {
208
+ const port = this.options.sourcePorts[reference.kind];
209
+ if (!port || typeof port.read !== 'function') {
210
+ throw new CompanyOsReceiptAdapterError('source_unavailable', `No source provider is configured for ${reference.kind}`);
211
+ }
212
+ let projection;
213
+ try {
214
+ projection = await port.read(cloneSourceReference(reference), access);
215
+ }
216
+ catch (error) {
217
+ throw mapSourceError(error, `${reference.kind}/${reference.id}`);
218
+ }
219
+ if (projection === null) {
220
+ throw new CompanyOsReceiptAdapterError('source_not_found', `Source ${reference.kind}/${reference.id} was not found`);
221
+ }
222
+ try {
223
+ assertSourceProjection(projection);
224
+ }
225
+ catch (error) {
226
+ if (error instanceof CompanyOsReceiptAdapterError)
227
+ throw error;
228
+ throw new CompanyOsReceiptAdapterError('source_unavailable', formatError(error));
229
+ }
230
+ if (mode === 'link') {
231
+ assertSourceLocator(reference, projection.reference);
232
+ }
233
+ else {
234
+ assertSourceIdentity(reference, projection.reference);
235
+ }
236
+ return {
237
+ reference: cloneSourceReference(projection.reference),
238
+ state_changed: projection.reference.state !== reference.state
239
+ };
240
+ }
241
+ }
242
+ function assertOptions(options) {
243
+ if (!options || !isNonEmptyString(options.dataDir)) {
244
+ throw new CompanyOsReceiptAdapterError('invalid_input', 'dataDir is required');
245
+ }
246
+ if (!options.sourcePorts || typeof options.sourcePorts !== 'object') {
247
+ throw new CompanyOsReceiptAdapterError('invalid_input', 'sourcePorts are required');
248
+ }
249
+ }
250
+ function normalizeLinkRequest(request) {
251
+ if (!request || typeof request !== 'object') {
252
+ throw new CompanyOsReceiptAdapterError('invalid_input', 'Receipt link request is required');
253
+ }
254
+ if (!isNonEmptyString(request.id)) {
255
+ throw new CompanyOsReceiptAdapterError('invalid_input', 'Receipt link id is required');
256
+ }
257
+ if (!isStrictRfc3339(request.recorded_at)) {
258
+ throw new CompanyOsReceiptAdapterError('invalid_input', 'Receipt recorded_at must be RFC3339');
259
+ }
260
+ assertSourceReference(request.source, 'Receipt source');
261
+ if (!Array.isArray(request.judgment_refs)) {
262
+ throw new CompanyOsReceiptAdapterError('invalid_input', 'judgment_refs must be an array');
263
+ }
264
+ const judgmentRefs = request.judgment_refs.map((reference, index) => {
265
+ assertJudgmentReference(reference, `judgment_refs[${index}]`);
266
+ return cloneJudgmentReference(reference);
267
+ });
268
+ const keys = new Set(judgmentRefs.map(judgmentReferenceKey));
269
+ if (keys.size !== judgmentRefs.length) {
270
+ throw new CompanyOsReceiptAdapterError('invalid_input', 'judgment_refs must not contain duplicates');
271
+ }
272
+ return {
273
+ id: request.id,
274
+ source: cloneSourceReference(request.source),
275
+ judgment_refs: judgmentRefs,
276
+ recorded_at: request.recorded_at,
277
+ access: normalizeAccess(request.access)
278
+ };
279
+ }
280
+ function makeLinkRecord(request, canonicalSource) {
281
+ return {
282
+ version: COMPANY_OS_RECEIPT_LINK_CATALOG_VERSION,
283
+ id: request.id,
284
+ source: cloneSourceReference(canonicalSource),
285
+ source_digest: sourceSnapshotDigest(canonicalSource),
286
+ judgment_refs: request.judgment_refs.map(cloneJudgmentReference),
287
+ recorded_at: request.recorded_at,
288
+ objective_status: 'unrecorded',
289
+ judgment_quality_status: 'unrecorded'
290
+ };
291
+ }
292
+ function makeProjection(record, sourceRead) {
293
+ return {
294
+ link: cloneLinkRecord(record),
295
+ source_read: {
296
+ reference: cloneSourceReference(sourceRead.reference),
297
+ // Always compare against the persisted snapshot. In particular, a
298
+ // link request may contain caller-supplied state that the provider
299
+ // correctly replaces with its canonical current state.
300
+ state_changed: sourceRead.reference.state !== record.source.state
301
+ }
302
+ };
303
+ }
304
+ function parseCatalog(content) {
305
+ if (content === undefined)
306
+ return { version: COMPANY_OS_RECEIPT_LINK_CATALOG_VERSION, records: [] };
307
+ let parsed;
308
+ try {
309
+ parsed = JSON.parse(content);
310
+ }
311
+ catch (error) {
312
+ throw new CompanyOsReceiptAdapterError('corrupt_record', `Receipt link sidecar is not valid JSON: ${formatError(error)}`);
313
+ }
314
+ if (!isRecord(parsed) || parsed.version !== COMPANY_OS_RECEIPT_LINK_CATALOG_VERSION || !Array.isArray(parsed.records)) {
315
+ throw new CompanyOsReceiptAdapterError('corrupt_record', 'Receipt link sidecar has an unsupported catalog shape');
316
+ }
317
+ const records = parsed.records.map((record, index) => parseStoredRecord(record, `records[${index}]`));
318
+ const ids = new Set(records.map((record) => record.id));
319
+ if (ids.size !== records.length) {
320
+ throw new CompanyOsReceiptAdapterError('corrupt_record', 'Receipt link sidecar contains duplicate IDs');
321
+ }
322
+ return { version: COMPANY_OS_RECEIPT_LINK_CATALOG_VERSION, records };
323
+ }
324
+ function parseStoredRecord(value, label) {
325
+ if (!isRecord(value)) {
326
+ throw new CompanyOsReceiptAdapterError('corrupt_record', `${label} must be an object`);
327
+ }
328
+ if (value.version !== COMPANY_OS_RECEIPT_LINK_CATALOG_VERSION
329
+ || value.objective_status !== 'unrecorded'
330
+ || value.judgment_quality_status !== 'unrecorded') {
331
+ throw new CompanyOsReceiptAdapterError('corrupt_record', `${label} has invalid status or version`);
332
+ }
333
+ if (!isNonEmptyString(value.id) || !isStrictRfc3339(value.recorded_at)) {
334
+ throw new CompanyOsReceiptAdapterError('corrupt_record', `${label} has invalid id or recorded_at`);
335
+ }
336
+ assertSourceReference(value.source, `${label}.source`, 'corrupt_record');
337
+ if (!isSourceDigest(value.source_digest)
338
+ || value.source_digest !== sourceSnapshotDigest(value.source)) {
339
+ throw new CompanyOsReceiptAdapterError('corrupt_record', `${label}.source_digest does not match the source snapshot`);
340
+ }
341
+ if (!Array.isArray(value.judgment_refs)) {
342
+ throw new CompanyOsReceiptAdapterError('corrupt_record', `${label}.judgment_refs must be an array`);
343
+ }
344
+ const judgmentRefs = value.judgment_refs.map((reference, index) => {
345
+ assertJudgmentReference(reference, `${label}.judgment_refs[${index}]`, 'corrupt_record');
346
+ return cloneJudgmentReference(reference);
347
+ });
348
+ const keys = new Set(judgmentRefs.map(judgmentReferenceKey));
349
+ if (keys.size !== judgmentRefs.length) {
350
+ throw new CompanyOsReceiptAdapterError('corrupt_record', `${label}.judgment_refs contains duplicates`);
351
+ }
352
+ return {
353
+ version: COMPANY_OS_RECEIPT_LINK_CATALOG_VERSION,
354
+ id: value.id,
355
+ source: cloneSourceReference(value.source),
356
+ source_digest: value.source_digest,
357
+ judgment_refs: judgmentRefs,
358
+ recorded_at: value.recorded_at,
359
+ objective_status: 'unrecorded',
360
+ judgment_quality_status: 'unrecorded'
361
+ };
362
+ }
363
+ function serializeCatalog(catalog) {
364
+ return `${JSON.stringify(catalog, null, 2)}\n`;
365
+ }
366
+ function assertSourceReference(value, label, code = 'invalid_input') {
367
+ if (!isRecord(value)
368
+ || !['run_receipt', 'outcome_case', 'meeting_context_receipt', 'decision_event'].includes(String(value.kind))
369
+ || !isNonEmptyString(value.id)
370
+ || (value.revision !== undefined && !isNonEmptyString(value.revision))
371
+ || (value.hash !== null && !isNonEmptyString(value.hash))
372
+ || !isNonEmptyString(value.state)
373
+ || !Array.isArray(value.owner_refs)) {
374
+ throw new CompanyOsReceiptAdapterError(code, `${label} has an invalid shape`);
375
+ }
376
+ value.owner_refs.forEach((owner, index) => assertOwnerReference(owner, `${label}.owner_refs[${index}]`, code));
377
+ if (value.conditions !== undefined) {
378
+ assertSourceConditions(value.conditions, `${label}.conditions`, code);
379
+ }
380
+ }
381
+ function assertOwnerReference(value, label, code) {
382
+ if (!isRecord(value) || (value.status !== 'typed' && value.status !== 'unknown')) {
383
+ throw new CompanyOsReceiptAdapterError(code, `${label} has an invalid status`);
384
+ }
385
+ if (value.status === 'unknown') {
386
+ if (!['not_recorded', 'legacy_untyped', 'source_unavailable'].includes(String(value.reason))) {
387
+ throw new CompanyOsReceiptAdapterError(code, `${label} has an invalid unknown reason`);
388
+ }
389
+ return;
390
+ }
391
+ if (!isRecord(value.ref)
392
+ || !isNonEmptyString(value.ref.id)
393
+ || !isNonEmptyString(value.ref.type)
394
+ || (value.ref.revision !== undefined && !isNonEmptyString(value.ref.revision))
395
+ || (value.ref.digest !== undefined && !isNonEmptyString(value.ref.digest))) {
396
+ throw new CompanyOsReceiptAdapterError(code, `${label}.ref has an invalid shape`);
397
+ }
398
+ }
399
+ function assertSourceConditions(value, label, code) {
400
+ if (!isRecord(value)) {
401
+ throw new CompanyOsReceiptAdapterError(code, `${label} must be an object`);
402
+ }
403
+ assertSourceConditionValue(value, label, code);
404
+ }
405
+ function assertSourceConditionValue(value, label, code) {
406
+ if (value === null || typeof value === 'string' || typeof value === 'boolean')
407
+ return;
408
+ if (typeof value === 'number') {
409
+ if (Number.isFinite(value))
410
+ return;
411
+ throw new CompanyOsReceiptAdapterError(code, `${label} contains a non-finite number`);
412
+ }
413
+ if (Array.isArray(value)) {
414
+ value.forEach((item, index) => assertSourceConditionValue(item, `${label}[${index}]`, code));
415
+ return;
416
+ }
417
+ if (isRecord(value)) {
418
+ for (const [key, item] of Object.entries(value)) {
419
+ if (!isNonEmptyString(key)) {
420
+ throw new CompanyOsReceiptAdapterError(code, `${label} contains an empty key`);
421
+ }
422
+ assertSourceConditionValue(item, `${label}.${key}`, code);
423
+ }
424
+ return;
425
+ }
426
+ throw new CompanyOsReceiptAdapterError(code, `${label} contains a non-JSON value`);
427
+ }
428
+ function assertSourceProjection(value) {
429
+ if (!isRecord(value)) {
430
+ throw new CompanyOsReceiptAdapterError('source_unavailable', 'Source provider returned an invalid projection');
431
+ }
432
+ assertSourceReference(value.reference, 'Source projection.reference', 'source_unavailable');
433
+ }
434
+ function assertSourceLocator(expected, actual) {
435
+ if (expected.kind !== actual.kind || expected.id !== actual.id) {
436
+ throw new CompanyOsReceiptAdapterError('integrity_mismatch', 'Source provider returned a different kind or ID');
437
+ }
438
+ if (expected.revision !== undefined && expected.revision !== actual.revision) {
439
+ throw new CompanyOsReceiptAdapterError('integrity_mismatch', 'Source provider returned a different revision');
440
+ }
441
+ if (expected.hash !== null && expected.hash !== actual.hash) {
442
+ throw new CompanyOsReceiptAdapterError('integrity_mismatch', 'Source provider returned a different hash');
443
+ }
444
+ if (expected.conditions !== undefined && !sameJson(expected.conditions, actual.conditions)) {
445
+ throw new CompanyOsReceiptAdapterError('integrity_mismatch', 'Source provider returned different conditions');
446
+ }
447
+ }
448
+ /**
449
+ * Verifies the stable identity of a saved source snapshot against its current
450
+ * owner projection. State is deliberately excluded: a source may transition
451
+ * after linking and is reported through `state_changed` instead.
452
+ */
453
+ function assertSourceIdentity(expected, actual) {
454
+ assertSourceLocator(expected, actual);
455
+ if (expected.revision !== actual.revision) {
456
+ throw new CompanyOsReceiptAdapterError('integrity_mismatch', 'Source provider returned a different revision');
457
+ }
458
+ if (expected.hash !== actual.hash) {
459
+ throw new CompanyOsReceiptAdapterError('integrity_mismatch', 'Source provider returned a different hash');
460
+ }
461
+ if (!sameJson(expected.owner_refs, actual.owner_refs)) {
462
+ throw new CompanyOsReceiptAdapterError('integrity_mismatch', 'Source provider returned different owner references');
463
+ }
464
+ if (!sameJson(expected.conditions, actual.conditions)) {
465
+ throw new CompanyOsReceiptAdapterError('integrity_mismatch', 'Source provider returned different conditions');
466
+ }
467
+ }
468
+ function assertJudgmentReference(value, label, code = 'invalid_input') {
469
+ if (!isRecord(value)
470
+ || !['problem', 'objective', 'model', 'constraint', 'dag', 'decision', 'evaluation', 'evidence'].includes(String(value.kind))
471
+ || !isNonEmptyString(value.id)
472
+ || (value.revision !== undefined && !isNonEmptyString(value.revision))
473
+ || (value.digest !== undefined && !isNonEmptyString(value.digest))) {
474
+ throw new CompanyOsReceiptAdapterError(code, `${label} has an invalid shape`);
475
+ }
476
+ }
477
+ function normalizeAccess(value) {
478
+ if (!value || typeof value !== 'object' || !isNonEmptyString(value.principal)) {
479
+ throw new CompanyOsReceiptAdapterError('invalid_input', 'Receipt access principal is required');
480
+ }
481
+ return {
482
+ principal: value.principal,
483
+ ...(value.scope === undefined ? {} : { scope: value.scope })
484
+ };
485
+ }
486
+ function mapSourceError(error, label) {
487
+ if (error instanceof CompanyOsReceiptAdapterError)
488
+ return error;
489
+ if (error && typeof error === 'object' && 'code' in error) {
490
+ const code = error.code;
491
+ if (code === 'authorization_denied' || code === 'scope_violation') {
492
+ return new CompanyOsReceiptAdapterError('authorization_denied', `${label} read was denied`);
493
+ }
494
+ if (code === 'not_found')
495
+ return new CompanyOsReceiptAdapterError('source_not_found', `${label} was not found`);
496
+ if (code === 'integrity_mismatch')
497
+ return new CompanyOsReceiptAdapterError('integrity_mismatch', `${label} identity did not match`);
498
+ }
499
+ return new CompanyOsReceiptAdapterError('source_unavailable', `${label} read was unavailable: ${formatError(error)}`);
500
+ }
501
+ function cloneSourceReference(reference) {
502
+ return {
503
+ kind: reference.kind,
504
+ id: reference.id,
505
+ ...(reference.revision === undefined ? {} : { revision: reference.revision }),
506
+ hash: reference.hash,
507
+ state: reference.state,
508
+ owner_refs: reference.owner_refs.map((owner) => owner.status === 'typed'
509
+ ? {
510
+ status: 'typed',
511
+ ref: {
512
+ id: owner.ref.id,
513
+ type: owner.ref.type,
514
+ ...(owner.ref.revision === undefined ? {} : { revision: owner.ref.revision }),
515
+ ...(owner.ref.digest === undefined ? {} : { digest: owner.ref.digest })
516
+ }
517
+ }
518
+ : { status: 'unknown', reason: owner.reason }),
519
+ ...(reference.conditions === undefined ? {} : { conditions: cloneSourceConditions(reference.conditions) })
520
+ };
521
+ }
522
+ function cloneSourceConditions(conditions) {
523
+ return cloneSourceConditionValue(conditions);
524
+ }
525
+ function cloneSourceConditionValue(value) {
526
+ if (Array.isArray(value))
527
+ return value.map(cloneSourceConditionValue);
528
+ if (isRecord(value)) {
529
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, cloneSourceConditionValue(child)]));
530
+ }
531
+ return value;
532
+ }
533
+ function cloneJudgmentReference(reference) {
534
+ return {
535
+ kind: reference.kind,
536
+ id: reference.id,
537
+ ...(reference.revision === undefined ? {} : { revision: reference.revision }),
538
+ ...(reference.digest === undefined ? {} : { digest: reference.digest })
539
+ };
540
+ }
541
+ function cloneLinkRecord(record) {
542
+ return {
543
+ version: COMPANY_OS_RECEIPT_LINK_CATALOG_VERSION,
544
+ id: record.id,
545
+ source: cloneSourceReference(record.source),
546
+ source_digest: record.source_digest,
547
+ judgment_refs: record.judgment_refs.map(cloneJudgmentReference),
548
+ recorded_at: record.recorded_at,
549
+ objective_status: 'unrecorded',
550
+ judgment_quality_status: 'unrecorded'
551
+ };
552
+ }
553
+ function judgmentReferenceKey(reference) {
554
+ return `${reference.kind}:${reference.id}:${reference.revision ?? ''}:${reference.digest ?? ''}`;
555
+ }
556
+ function sameLinkPayload(left, right) {
557
+ return left.id === right.id
558
+ && left.recorded_at === right.recorded_at
559
+ && sameSourceIdentity(left.source, right.source)
560
+ && sameJson(left.judgment_refs, right.judgment_refs);
561
+ }
562
+ function sameSourceIdentity(left, right) {
563
+ return left.kind === right.kind
564
+ && left.id === right.id
565
+ && left.revision === right.revision
566
+ && left.hash === right.hash
567
+ && sameJson(left.owner_refs, right.owner_refs)
568
+ && sameJson(left.conditions, right.conditions);
569
+ }
570
+ function sameJson(left, right) {
571
+ return stableJson(left) === stableJson(right);
572
+ }
573
+ function sourceSnapshotDigest(source) {
574
+ return `sha256:${createHash('sha256').update(stableJson(source), 'utf8').digest('hex')}`;
575
+ }
576
+ function isSourceDigest(value) {
577
+ return typeof value === 'string' && /^sha256:[a-f0-9]{64}$/u.test(value);
578
+ }
579
+ function stableJson(value) {
580
+ return JSON.stringify(canonicalizeJson(value));
581
+ }
582
+ function canonicalizeJson(value) {
583
+ if (Array.isArray(value))
584
+ return value.map(canonicalizeJson);
585
+ if (isRecord(value)) {
586
+ return Object.fromEntries(Object.entries(value)
587
+ .sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)
588
+ .map(([key, child]) => [key, canonicalizeJson(child)]));
589
+ }
590
+ return value;
591
+ }
592
+ function isStrictRfc3339(value) {
593
+ if (typeof value !== 'string')
594
+ return false;
595
+ 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);
596
+ if (!match)
597
+ return false;
598
+ const year = Number(match[1]);
599
+ const month = Number(match[2]);
600
+ const day = Number(match[3]);
601
+ const hour = Number(match[4]);
602
+ const minute = Number(match[5]);
603
+ const second = Number(match[6]);
604
+ if (month < 1 || month > 12 || day < 1 || day > daysInMonth(year, month)
605
+ || hour > 23 || minute > 59 || second > 59)
606
+ return false;
607
+ if (match[8] !== 'Z') {
608
+ const offsetHour = Number(match[8].slice(1, 3));
609
+ const offsetMinute = Number(match[8].slice(4, 6));
610
+ if (offsetHour > 23 || offsetMinute > 59)
611
+ return false;
612
+ }
613
+ return true;
614
+ }
615
+ function daysInMonth(year, month) {
616
+ if (month === 2)
617
+ return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) ? 29 : 28;
618
+ return [4, 6, 9, 11].includes(month) ? 30 : 31;
619
+ }
620
+ function isNonEmptyString(value) {
621
+ return typeof value === 'string' && value.trim().length > 0 && !value.includes('\0');
622
+ }
623
+ function isRecord(value) {
624
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
625
+ return false;
626
+ try {
627
+ const prototype = Object.getPrototypeOf(value);
628
+ return prototype === Object.prototype || prototype === null;
629
+ }
630
+ catch {
631
+ return false;
632
+ }
633
+ }
634
+ function formatError(error) {
635
+ return error instanceof Error ? error.message : String(error);
636
+ }
@@ -0,0 +1,23 @@
1
+ import { type ConstraintAuthorizationContext, type ConstraintExceptionQuery, type ConstraintExceptionRecord } from './constraint-resolution.js';
2
+ import type { ConstraintExceptionStore } from './foundation-constraint-store.js';
3
+ /** The shared SSOT evidence area used for rich exception metadata. */
4
+ export declare const CONSTRAINT_EXCEPTION_EVIDENCE_PATH: "evidence/constraint-exceptions.jsonl";
5
+ export interface AtomicConstraintExceptionStoreOptions {
6
+ readonly dataDir: string;
7
+ readonly evidencePath?: string;
8
+ }
9
+ /**
10
+ * Durable exception evidence stored through the common SSOT transaction
11
+ * writer. Constraint definitions remain in GraphFoundationRevisionStore;
12
+ * this evidence adapter only supplies the approval metadata missing from the
13
+ * shared ontology reference. Authorization is intentionally performed by
14
+ * ConstraintService's injected provider before this storage port is called.
15
+ */
16
+ export declare class AtomicConstraintExceptionStore implements ConstraintExceptionStore {
17
+ private readonly dataDir;
18
+ private readonly evidencePath;
19
+ constructor(options: AtomicConstraintExceptionStoreOptions);
20
+ append(exception: ConstraintExceptionRecord, _context: ConstraintAuthorizationContext): Promise<ConstraintExceptionRecord>;
21
+ list(query: ConstraintExceptionQuery, _context: ConstraintAuthorizationContext): Promise<readonly ConstraintExceptionRecord[]>;
22
+ }
23
+ export declare function createAtomicConstraintExceptionStore(options: AtomicConstraintExceptionStoreOptions): ConstraintExceptionStore;