@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,944 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { assertFoundationCatalog, cloneFoundationCatalog, cloneFoundationDefinition, digestFoundationDefinition, findFoundationRecord, findLatestFoundationRecord, foundationCatalogOrEmpty, foundationKey, nextFoundationRevision, } from './foundation-catalog.js';
3
+ import { validateFoundationDefinition } from './ontology-foundation.js';
4
+ import { loadPersonalOs, mutatePersonalOsWithSidecar, readPersonalOsSidecar } from './ssot.js';
5
+ /**
6
+ * Learning adoption is an append-only ledger over the existing canonical
7
+ * aggregate. It does not promote a candidate to truth and it does not
8
+ * mutate a running judgment. Foundation target revisions are appended to
9
+ * Graph v2; the ledger stores only the references and decision evidence.
10
+ */
11
+ export const COMPANY_OS_LEARNING_ADOPTION_CONTRACT_VERSION = '0.1.0';
12
+ export const COMPANY_OS_LEARNING_ADOPTION_RECORD_CATALOG_VERSION = 1;
13
+ /**
14
+ * Stable identity for a canonical adoption record when it crosses a
15
+ * knowledge boundary. This is deliberately separate from the catalog
16
+ * version and from the adopted foundation target revision.
17
+ */
18
+ export const COMPANY_OS_LEARNING_ADOPTION_LOCATOR_SCHEMA = 'company-os-learning-adoption-record.v1';
19
+ export const COMPANY_OS_LEARNING_ADOPTION_SIDECAR = 'evidence/company-os-learning-adoption.json';
20
+ /**
21
+ * Project a canonical adoption record into the locator contract used by
22
+ * knowledge adapters. The full record digest is the content identity; the
23
+ * catalog version and adopted target revision have different meanings.
24
+ */
25
+ export function createLearningAdoptionLocator(record) {
26
+ const id = requireId(record?.id, 'adoption locator id');
27
+ if (!isDigest(record?.digest)) {
28
+ throw new CompanyOsLearningAdoptionError('invalid_input', 'adoption locator requires a sha256 content digest');
29
+ }
30
+ return { id, schema: COMPANY_OS_LEARNING_ADOPTION_LOCATOR_SCHEMA, contentDigest: record.digest };
31
+ }
32
+ export class CompanyOsLearningAdoptionError extends Error {
33
+ code;
34
+ constructor(code, message) {
35
+ super(message);
36
+ this.name = 'CompanyOsLearningAdoptionError';
37
+ this.code = code;
38
+ }
39
+ }
40
+ export function createCompanyOsLearningEvaluationPort(store) {
41
+ if (!store || typeof store.read !== 'function') {
42
+ throw new TypeError('An evaluation store with read is required');
43
+ }
44
+ return {
45
+ read: (id, access) => store.read(id, access)
46
+ };
47
+ }
48
+ export function createLocalLearningAdoptionAuthorizationPort() {
49
+ return {
50
+ authorize(request) {
51
+ const definition = request.target.definition;
52
+ if (!isRecord(definition) || !isFoundationAcl(definition.acl)) {
53
+ return {
54
+ status: 'denied',
55
+ authorizationId: `denied-${request.candidate.id}`,
56
+ authorizedBy: request.access.principal,
57
+ reason: 'A local authorization decision requires a canonical Foundation ACL',
58
+ decidedAt: new Date().toISOString()
59
+ };
60
+ }
61
+ const acl = definition.acl;
62
+ const allowed = acl.ownerId === request.access.principal || acl.writerIds.includes(request.access.principal);
63
+ return {
64
+ status: allowed ? 'authorized' : 'denied',
65
+ authorizationId: `${request.candidate.id}-${request.validation.id}-${request.target.reference.revision}`,
66
+ authorizedBy: request.access.principal,
67
+ ...(allowed ? {} : { reason: 'Current target ACL does not permit adoption' }),
68
+ decidedAt: new Date().toISOString()
69
+ };
70
+ }
71
+ };
72
+ }
73
+ export function createCompanyOsLearningAdoptionStore(options) {
74
+ if (!options || !isNonEmptyString(options.dataDir)) {
75
+ throw new CompanyOsLearningAdoptionError('invalid_input', 'dataDir is required');
76
+ }
77
+ if (!options.evaluation || typeof options.evaluation.read !== 'function') {
78
+ throw new CompanyOsLearningAdoptionError('invalid_input', 'evaluation.read is required');
79
+ }
80
+ if (!options.target || typeof options.target.readCurrent !== 'function' || typeof options.target.read !== 'function'
81
+ || typeof options.target.prepareRevision !== 'function' || typeof options.target.commitRevision !== 'function') {
82
+ throw new CompanyOsLearningAdoptionError('invalid_input', 'a complete target port is required');
83
+ }
84
+ if (options.runReceipt !== undefined && typeof options.runReceipt.read !== 'function') {
85
+ throw new CompanyOsLearningAdoptionError('invalid_input', 'runReceipt.read is required when a run receipt port is provided');
86
+ }
87
+ return new GraphCompanyOsLearningAdoptionStore(options);
88
+ }
89
+ /**
90
+ * Expose only the exact adoption locator after the store has performed its
91
+ * current ACL, candidate, validation, and canonical target checks.
92
+ */
93
+ export function createLearningAdoptionLocatorReadPort(store) {
94
+ if (!store || typeof store.readAdoptionByLocator !== 'function') {
95
+ throw new TypeError('A learning adoption store with readAdoptionByLocator is required');
96
+ }
97
+ return {
98
+ async read({ locator, access }) {
99
+ const record = await store.readAdoptionByLocator(locator, access);
100
+ return record ? createLearningAdoptionLocator(record) : null;
101
+ }
102
+ };
103
+ }
104
+ /**
105
+ * Foundation-backed target provider. It is the concrete OSS path for
106
+ * Objective and world-model revisions. Judgment/execution method registries
107
+ * belong to a consuming provider and are intentionally unsupported here.
108
+ */
109
+ export function createFoundationLearningTargetPort(options) {
110
+ if (!options?.foundationStore)
111
+ throw new TypeError('foundationStore is required');
112
+ const store = options.foundationStore;
113
+ return {
114
+ async readCurrent(reference, access) {
115
+ const type = foundationTypeForTarget(reference);
116
+ const record = await store.readLatest(type, reference.id, access);
117
+ if (!record || record.definition.revision !== reference.revision || record.digest !== reference.digest)
118
+ return null;
119
+ return foundationTargetVersion(reference, record.definition, record.digest);
120
+ },
121
+ async read(reference, access) {
122
+ const type = foundationTypeForTarget(reference);
123
+ const record = await store.read({ id: reference.id, type, revision: reference.revision }, access);
124
+ if (!record || record.digest !== reference.digest)
125
+ return null;
126
+ return foundationTargetVersion(reference, record.definition, record.digest);
127
+ },
128
+ prepareRevision(input) {
129
+ const current = input.current;
130
+ const type = foundationTypeForTarget(current.target);
131
+ if (!isRecord(input.candidate.proposedChange) || !isRecord(input.candidate.proposedChange.definition)) {
132
+ throw new CompanyOsLearningAdoptionError('invalid_input', 'Foundation adoption requires proposedChange.definition');
133
+ }
134
+ const proposedDefinition = cloneJson(input.candidate.proposedChange.definition);
135
+ const proposed = { ...proposedDefinition, revision: nextFoundationRevision(current.reference.revision) };
136
+ if (proposed.id !== current.reference.id || proposed.type !== type) {
137
+ throw new CompanyOsLearningAdoptionError('revision_conflict', 'Proposed foundation definition changes the logical target');
138
+ }
139
+ const result = validateFoundationDefinition(proposed, { use: 'draft' });
140
+ if (!result.valid) {
141
+ throw new CompanyOsLearningAdoptionError('invalid_input', result.issues.map((issue) => issue.message).join('; '));
142
+ }
143
+ if (!sameJson(proposed.acl, current.acl) || !sameJson(proposed.scope, current.scope)) {
144
+ throw new CompanyOsLearningAdoptionError('authorization_denied', 'Adoption cannot change target ACL or scope');
145
+ }
146
+ if (proposed.type === 'model' && isRecord(current.definition)
147
+ && current.definition.validationState !== undefined
148
+ && proposed.validationState !== current.definition.validationState) {
149
+ throw new CompanyOsLearningAdoptionError('invalid_validation', 'Adoption cannot upgrade or rewrite a Model validation state');
150
+ }
151
+ const digest = digestFoundationDefinition(proposed);
152
+ return {
153
+ target: { ...current.target },
154
+ reference: {
155
+ kind: current.target.kind,
156
+ id: proposed.id,
157
+ revision: proposed.revision,
158
+ digest,
159
+ foundationType: proposed.type
160
+ },
161
+ definition: cloneFoundationDefinition(proposed),
162
+ acl: cloneJson(proposed.acl),
163
+ scope: cloneJson(proposed.scope)
164
+ };
165
+ },
166
+ commitRevision(input) {
167
+ const type = foundationTypeForTarget(input.expectedCurrent.target);
168
+ if (input.currentAggregate.graph.version !== 2) {
169
+ throw new CompanyOsLearningAdoptionError('unsupported_target', 'Foundation adoption requires canonical Graph v2');
170
+ }
171
+ if (!isFoundationDefinition(input.prepared.definition) || input.prepared.reference.foundationType !== type) {
172
+ throw new CompanyOsLearningAdoptionError('integrity_mismatch', 'Prepared foundation revision is invalid');
173
+ }
174
+ const catalog = foundationCatalogOrEmpty(input.currentAggregate.graph.foundation);
175
+ const latest = findLatestFoundationRecord(catalog, type, input.expectedCurrent.reference.id);
176
+ if (!latest || latest.definition.revision !== input.expectedCurrent.reference.revision
177
+ || latest.digest !== input.expectedCurrent.reference.digest) {
178
+ throw new CompanyOsLearningAdoptionError('revision_conflict', 'The foundation target changed before adoption commit');
179
+ }
180
+ const preparedFoundationReference = {
181
+ id: input.prepared.reference.id,
182
+ type,
183
+ revision: input.prepared.reference.revision
184
+ };
185
+ const existing = findFoundationRecord(catalog, preparedFoundationReference);
186
+ if (existing && existing.digest !== input.prepared.reference.digest) {
187
+ throw new CompanyOsLearningAdoptionError('integrity_mismatch', 'Prepared foundation revision collides with another digest');
188
+ }
189
+ const nextCatalog = cloneFoundationCatalog(catalog);
190
+ if (!existing) {
191
+ nextCatalog.records.push({
192
+ definition: cloneFoundationDefinition(input.prepared.definition),
193
+ digest: input.prepared.reference.digest
194
+ });
195
+ nextCatalog.latest[foundationKey(preparedFoundationReference)] = {
196
+ id: input.prepared.reference.id,
197
+ type,
198
+ revision: input.prepared.reference.revision
199
+ };
200
+ }
201
+ assertFoundationCatalog(nextCatalog);
202
+ return {
203
+ nextAggregate: {
204
+ ...input.currentAggregate,
205
+ graph: { ...input.currentAggregate.graph, foundation: nextCatalog }
206
+ },
207
+ adopted: {
208
+ ...input.prepared,
209
+ definition: cloneJson(input.prepared.definition),
210
+ acl: cloneJson(input.prepared.acl),
211
+ scope: cloneJson(input.prepared.scope)
212
+ }
213
+ };
214
+ }
215
+ };
216
+ }
217
+ class GraphCompanyOsLearningAdoptionStore {
218
+ options;
219
+ now;
220
+ authorization;
221
+ constructor(options) {
222
+ this.options = options;
223
+ this.now = options.now ?? (() => new Date().toISOString());
224
+ this.authorization = options.authorization ?? createLocalLearningAdoptionAuthorizationPort();
225
+ }
226
+ async createCandidate(request) {
227
+ const access = assertAccess(request.access);
228
+ const input = normalizeCandidateInput(request);
229
+ const evaluation = await this.options.evaluation.read(input.sourceEvaluationRef.id, access);
230
+ if (!evaluation)
231
+ throw new CompanyOsLearningAdoptionError('not_found', `Evaluation ${input.sourceEvaluationRef.id} was not found`);
232
+ const evaluationDigest = digestEvaluationRecord(evaluation);
233
+ if (evaluationDigest !== input.sourceEvaluationRef.digest) {
234
+ throw new CompanyOsLearningAdoptionError('integrity_mismatch', 'Candidate source evaluation digest does not match the canonical record');
235
+ }
236
+ const candidate = {
237
+ version: COMPANY_OS_LEARNING_ADOPTION_RECORD_CATALOG_VERSION,
238
+ ...input,
239
+ proposedChange: cloneJson(input.proposedChange),
240
+ grounds: [...input.grounds],
241
+ counterexamples: [...input.counterexamples],
242
+ uncertainty: [...input.uncertainty],
243
+ applicability: cloneJson(input.applicability),
244
+ digest: ''
245
+ };
246
+ const completeCandidate = { ...candidate, digest: digestLearningRecord({ ...candidate, digest: '' }) };
247
+ let committed;
248
+ await this.mutateLedger(async (ledger) => {
249
+ const existing = ledger.candidates.find((item) => item.id === completeCandidate.id);
250
+ if (existing) {
251
+ if (existing.digest !== completeCandidate.digest)
252
+ throw new CompanyOsLearningAdoptionError('revision_conflict', `Candidate ${completeCandidate.id} already exists with different content`);
253
+ committed = cloneJson(existing);
254
+ return ledger;
255
+ }
256
+ committed = cloneJson(completeCandidate);
257
+ return { ...ledger, candidates: [...ledger.candidates, completeCandidate] };
258
+ });
259
+ if (!committed)
260
+ throw new CompanyOsLearningAdoptionError('readback_mismatch', 'Candidate commit did not produce a record');
261
+ const readback = await this.readCandidate(committed.id, access);
262
+ if (!readback || readback.digest !== committed.digest)
263
+ throw new CompanyOsLearningAdoptionError('readback_mismatch', `Candidate ${committed.id} was not readable after commit`);
264
+ return readback;
265
+ }
266
+ async readCandidate(id, access) {
267
+ const context = assertAccess(access);
268
+ const candidateId = requireId(id, 'candidate id');
269
+ const ledger = await this.loadLedger();
270
+ const candidate = ledger.candidates.find((item) => item.id === candidateId);
271
+ if (!candidate)
272
+ return null;
273
+ await this.assertCandidateAccess(candidate, context);
274
+ return cloneJson(candidate);
275
+ }
276
+ async createValidation(request) {
277
+ const access = assertAccess(request.access);
278
+ const input = normalizeValidationInput(request);
279
+ const candidate = await this.readCandidateByReference(input.candidateRef, access);
280
+ if (!candidate)
281
+ throw new CompanyOsLearningAdoptionError('not_found', `Candidate ${input.candidateRef.id} was not found`);
282
+ assertValidationLogic(input);
283
+ const validation = {
284
+ version: COMPANY_OS_LEARNING_ADOPTION_RECORD_CATALOG_VERSION,
285
+ ...input,
286
+ sourceEvaluationRef: cloneJson(candidate.sourceEvaluationRef),
287
+ findings: cloneJson(input.findings),
288
+ digest: ''
289
+ };
290
+ const completeValidation = {
291
+ ...validation,
292
+ digest: digestLearningRecord({ ...validation, digest: '' })
293
+ };
294
+ let committed;
295
+ await this.mutateLedger(async (ledger) => {
296
+ const existing = ledger.validations.find((item) => item.id === completeValidation.id);
297
+ if (existing) {
298
+ if (existing.digest !== completeValidation.digest)
299
+ throw new CompanyOsLearningAdoptionError('revision_conflict', `Validation ${completeValidation.id} already exists with different content`);
300
+ committed = cloneJson(existing);
301
+ return ledger;
302
+ }
303
+ committed = cloneJson(completeValidation);
304
+ return { ...ledger, validations: [...ledger.validations, completeValidation] };
305
+ });
306
+ if (!committed)
307
+ throw new CompanyOsLearningAdoptionError('readback_mismatch', 'Validation commit did not produce a record');
308
+ const readback = await this.readValidation(committed.id, access);
309
+ if (!readback || readback.digest !== committed.digest)
310
+ throw new CompanyOsLearningAdoptionError('readback_mismatch', `Validation ${committed.id} was not readable after commit`);
311
+ return readback;
312
+ }
313
+ async readValidation(id, access) {
314
+ const context = assertAccess(access);
315
+ const validationId = requireId(id, 'validation id');
316
+ const ledger = await this.loadLedger();
317
+ const validation = ledger.validations.find((item) => item.id === validationId);
318
+ if (!validation)
319
+ return null;
320
+ const candidate = await this.readCandidateByReference(validation.candidateRef, context);
321
+ if (!candidate)
322
+ throw new CompanyOsLearningAdoptionError('integrity_mismatch', `Validation ${validation.id} points to an unreadable candidate`);
323
+ if (!sameRefDigest(candidate.sourceEvaluationRef, validation.sourceEvaluationRef)) {
324
+ throw new CompanyOsLearningAdoptionError('integrity_mismatch', `Validation ${validation.id} source evaluation changed`);
325
+ }
326
+ assertValidationLogic(validation);
327
+ return cloneJson(validation);
328
+ }
329
+ async adopt(request) {
330
+ const access = assertAccess(request.access);
331
+ const id = requireId(request.id, 'adoption id');
332
+ const idempotencyKey = requireId(request.idempotencyKey, 'idempotencyKey');
333
+ const candidateRef = normalizeRecordReference(request.candidateRef, 'candidateRef');
334
+ const validationRef = normalizeRecordReference(request.validationRef, 'validationRef');
335
+ const requestDigest = digestJson({ id, idempotencyKey, candidateRef, validationRef, principal: access.principal });
336
+ const existing = await this.findAdoptionByIdempotencyKey(idempotencyKey, access);
337
+ if (existing) {
338
+ if (existing.requestDigest !== requestDigest)
339
+ throw new CompanyOsLearningAdoptionError('revision_conflict', `Adoption key ${idempotencyKey} was used with different content`);
340
+ return existing;
341
+ }
342
+ const candidate = await this.readCandidateByReference(candidateRef, access);
343
+ if (!candidate)
344
+ throw new CompanyOsLearningAdoptionError('not_found', `Candidate ${candidateRef.id} was not found`);
345
+ const validation = await this.readValidationByReference(validationRef, access);
346
+ if (!validation)
347
+ throw new CompanyOsLearningAdoptionError('not_found', `Validation ${validationRef.id} was not found`);
348
+ if (!sameRefDigest(validation.candidateRef, candidateRef)) {
349
+ throw new CompanyOsLearningAdoptionError('integrity_mismatch', 'Validation does not belong to the selected candidate');
350
+ }
351
+ if (!sameRefDigest(candidate.sourceEvaluationRef, validation.sourceEvaluationRef)) {
352
+ throw new CompanyOsLearningAdoptionError('integrity_mismatch', 'Candidate and validation do not refer to the same evaluation');
353
+ }
354
+ const target = await this.options.target.readCurrent(candidate.target, access);
355
+ if (!target)
356
+ throw new CompanyOsLearningAdoptionError('revision_conflict', 'The target is not the current exact revision');
357
+ assertScopeAccess(target.scope, access);
358
+ const decision = await this.authorization.authorize({ candidate, validation, target, access });
359
+ assertAuthorizationDecision(decision);
360
+ if (decision.status !== 'authorized')
361
+ throw new CompanyOsLearningAdoptionError('authorization_denied', decision.reason ?? 'Adoption was denied');
362
+ const prepared = await this.options.target.prepareRevision({ candidate, validation, current: target });
363
+ assertPreparedRevision(prepared, target);
364
+ let committed;
365
+ await this.mutateLedger(async (ledger, currentAggregate) => {
366
+ const sameKey = ledger.adoptions.find((item) => item.idempotencyKey === idempotencyKey);
367
+ if (sameKey) {
368
+ if (sameKey.requestDigest !== requestDigest)
369
+ throw new CompanyOsLearningAdoptionError('revision_conflict', `Adoption key ${idempotencyKey} was used with different content`);
370
+ committed = cloneJson(sameKey);
371
+ return { ledger, nextAggregate: currentAggregate };
372
+ }
373
+ const storedCandidate = ledger.candidates.find((item) => item.id === candidate.id);
374
+ const storedValidation = ledger.validations.find((item) => item.id === validation.id);
375
+ if (!storedCandidate || storedCandidate.digest !== candidate.digest || !storedValidation || storedValidation.digest !== validation.digest) {
376
+ throw new CompanyOsLearningAdoptionError('integrity_mismatch', 'Candidate or validation changed before adoption commit');
377
+ }
378
+ const result = this.options.target.commitRevision({ currentAggregate, expectedCurrent: target, prepared });
379
+ if (!sameRefIdentity(result.adopted.reference, candidate.target)) {
380
+ throw new CompanyOsLearningAdoptionError('integrity_mismatch', 'Adopted target changed the candidate logical identity');
381
+ }
382
+ const adoption = {
383
+ version: COMPANY_OS_LEARNING_ADOPTION_RECORD_CATALOG_VERSION,
384
+ id,
385
+ idempotencyKey,
386
+ requestDigest,
387
+ candidateRef: { ...candidateRef },
388
+ validationRef: { ...validationRef },
389
+ previousTarget: cloneJson(target.reference),
390
+ adoptedTarget: cloneJson(result.adopted.reference),
391
+ authorization: cloneJson(decision),
392
+ ...(isModelDefinition(result.adopted.definition) ? { targetValidationState: result.adopted.definition.validationState } : {}),
393
+ adoptedAt: this.now(),
394
+ digest: ''
395
+ };
396
+ const complete = { ...adoption, digest: digestLearningRecord(adoption) };
397
+ committed = complete;
398
+ return {
399
+ ledger: { ...ledger, adoptions: [...ledger.adoptions, complete] },
400
+ nextAggregate: result.nextAggregate
401
+ };
402
+ });
403
+ if (!committed)
404
+ throw new CompanyOsLearningAdoptionError('readback_mismatch', 'Adoption commit did not produce a record');
405
+ const readback = await this.readAdoption(committed.id, access);
406
+ if (!readback || readback.digest !== committed.digest)
407
+ throw new CompanyOsLearningAdoptionError('readback_mismatch', `Adoption ${committed.id} was not readable after commit`);
408
+ return readback;
409
+ }
410
+ async readAdoption(id, access) {
411
+ const context = assertAccess(access);
412
+ const adoptionId = requireId(id, 'adoption id');
413
+ const ledger = await this.loadLedger();
414
+ const adoption = ledger.adoptions.find((item) => item.id === adoptionId);
415
+ if (!adoption)
416
+ return null;
417
+ if (digestLearningRecord({ ...adoption, digest: '' }) !== adoption.digest) {
418
+ throw new CompanyOsLearningAdoptionError('integrity_mismatch', `Adoption ${id} digest does not match`);
419
+ }
420
+ const candidate = await this.readCandidateByReference(adoption.candidateRef, context);
421
+ if (!candidate)
422
+ throw new CompanyOsLearningAdoptionError('integrity_mismatch', `Adoption ${id} points to an unreadable candidate`);
423
+ const validation = await this.readValidationByReference(adoption.validationRef, context);
424
+ if (!validation)
425
+ throw new CompanyOsLearningAdoptionError('integrity_mismatch', `Adoption ${id} points to an unreadable validation`);
426
+ if (!sameRefDigest(validation.candidateRef, adoption.candidateRef)) {
427
+ throw new CompanyOsLearningAdoptionError('integrity_mismatch', `Adoption ${id} links a validation for a different candidate`);
428
+ }
429
+ if (!sameRefExact(adoption.previousTarget, candidate.target)
430
+ || !sameRefIdentity(adoption.previousTarget, candidate.target)
431
+ || !sameRefIdentity(adoption.adoptedTarget, candidate.target)) {
432
+ throw new CompanyOsLearningAdoptionError('integrity_mismatch', `Adoption ${id} changed the candidate target identity`);
433
+ }
434
+ const target = await this.options.target.read(adoption.adoptedTarget, context);
435
+ if (!target || !isTargetReferenceShape(target.reference) || !sameRefExact(target.reference, adoption.adoptedTarget)
436
+ || !sameRefIdentity(target.reference, candidate.target)) {
437
+ throw new CompanyOsLearningAdoptionError('integrity_mismatch', `Adopted target for ${id} is not readable at its exact revision`);
438
+ }
439
+ void candidate;
440
+ void validation;
441
+ return cloneJson(adoption);
442
+ }
443
+ async readAdoptionLocator(id, access) {
444
+ const adoption = await this.readAdoption(id, access);
445
+ return adoption ? createLearningAdoptionLocator(adoption) : null;
446
+ }
447
+ async readAdoptionByLocator(locator, access) {
448
+ const normalized = normalizeLearningAdoptionLocator(locator);
449
+ const adoption = await this.readAdoption(normalized.id, access);
450
+ if (!adoption)
451
+ return null;
452
+ const current = createLearningAdoptionLocator(adoption);
453
+ if (!sameJson(current, normalized)) {
454
+ throw new CompanyOsLearningAdoptionError('integrity_mismatch', `Adoption ${normalized.id} does not match the requested canonical locator`);
455
+ }
456
+ return adoption;
457
+ }
458
+ async recordRunUse(request) {
459
+ const access = assertAccess(request.access);
460
+ if (request.runPhase !== 'planned' && request.runPhase !== 'actual') {
461
+ throw new CompanyOsLearningAdoptionError('invalid_input', 'runPhase must be planned or actual');
462
+ }
463
+ const id = requireId(request.id, 'run-use id');
464
+ const runId = requireId(request.runId, 'run id');
465
+ const adoptionId = requireId(request.adoptionId, 'adoption id');
466
+ const adoption = await this.readAdoption(adoptionId, access);
467
+ if (!adoption)
468
+ throw new CompanyOsLearningAdoptionError('not_found', `Adoption ${adoptionId} was not found`);
469
+ const receiptRef = request.runPhase === 'actual'
470
+ ? normalizeRunReceiptReference(request.receiptRef)
471
+ : request.receiptRef === undefined
472
+ ? undefined
473
+ : (() => { throw new CompanyOsLearningAdoptionError('invalid_input', 'planned run-use cannot carry a receipt reference'); })();
474
+ if (request.runPhase === 'actual') {
475
+ await this.readAndAssertRunReceipt(receiptRef, runId, adoption, access);
476
+ }
477
+ const requestDigest = digestJson({ id, runId, adoptionId, runPhase: request.runPhase, receiptRef, principal: access.principal });
478
+ let committed;
479
+ await this.mutateLedger(async (ledger) => {
480
+ const existing = ledger.runUses.find((item) => item.id === id);
481
+ if (existing) {
482
+ if (existing.digest !== digestLearningRecord({ ...existing, digest: '' }))
483
+ throw new CompanyOsLearningAdoptionError('corrupt_record', `Run use ${id} is corrupt`);
484
+ if (existing.runId !== runId || existing.adoptionId !== adoptionId || existing.requestDigest !== requestDigest) {
485
+ throw new CompanyOsLearningAdoptionError('revision_conflict', `Run-use ${id} already exists with different content`);
486
+ }
487
+ committed = cloneJson(existing);
488
+ return ledger;
489
+ }
490
+ const record = {
491
+ version: COMPANY_OS_LEARNING_ADOPTION_RECORD_CATALOG_VERSION,
492
+ id,
493
+ runId,
494
+ adoptionId,
495
+ requestDigest,
496
+ adoptedTarget: cloneJson(adoption.adoptedTarget),
497
+ runPhase: request.runPhase,
498
+ ...(receiptRef ? { receiptRef: cloneJson(receiptRef) } : {}),
499
+ usedAt: this.now(),
500
+ digest: ''
501
+ };
502
+ const complete = { ...record, digest: digestLearningRecord(record) };
503
+ committed = complete;
504
+ return { ...ledger, runUses: [...ledger.runUses, complete] };
505
+ });
506
+ if (!committed)
507
+ throw new CompanyOsLearningAdoptionError('readback_mismatch', 'Run-use commit did not produce a record');
508
+ const readback = await this.readRunUse(committed.id, access);
509
+ if (!readback || readback.digest !== committed.digest)
510
+ throw new CompanyOsLearningAdoptionError('readback_mismatch', `Run use ${id} was not readable after commit`);
511
+ return readback;
512
+ }
513
+ async readRunUse(id, access) {
514
+ const context = assertAccess(access);
515
+ const runUseId = requireId(id, 'run-use id');
516
+ const ledger = await this.loadLedger();
517
+ const record = ledger.runUses.find((item) => item.id === runUseId);
518
+ if (!record)
519
+ return null;
520
+ if (digestLearningRecord({ ...record, digest: '' }) !== record.digest)
521
+ throw new CompanyOsLearningAdoptionError('integrity_mismatch', `Run use ${id} digest does not match`);
522
+ const adoption = await this.readAdoption(record.adoptionId, context);
523
+ if (!adoption || !sameRefExact(adoption.adoptedTarget, record.adoptedTarget))
524
+ throw new CompanyOsLearningAdoptionError('integrity_mismatch', `Run use ${id} points to a different adoption`);
525
+ if (record.runPhase === 'actual') {
526
+ if (!record.receiptRef)
527
+ throw new CompanyOsLearningAdoptionError('corrupt_record', `Actual run use ${id} has no receipt reference`);
528
+ await this.readAndAssertRunReceipt(record.receiptRef, record.runId, adoption, context);
529
+ }
530
+ else if (record.receiptRef !== undefined) {
531
+ throw new CompanyOsLearningAdoptionError('corrupt_record', `Planned run use ${id} has an unexpected receipt reference`);
532
+ }
533
+ return cloneJson(record);
534
+ }
535
+ async readCandidateByReference(reference, access) {
536
+ const ledger = await this.loadLedger();
537
+ const candidate = ledger.candidates.find((item) => item.id === reference.id);
538
+ if (!candidate)
539
+ return null;
540
+ if (candidate.digest !== reference.digest)
541
+ throw new CompanyOsLearningAdoptionError('integrity_mismatch', `Candidate ${reference.id} digest does not match`);
542
+ await this.assertCandidateAccess(candidate, access);
543
+ return cloneJson(candidate);
544
+ }
545
+ async readValidationByReference(reference, access) {
546
+ const ledger = await this.loadLedger();
547
+ const validation = ledger.validations.find((item) => item.id === reference.id);
548
+ if (!validation)
549
+ return null;
550
+ if (validation.digest !== reference.digest)
551
+ throw new CompanyOsLearningAdoptionError('integrity_mismatch', `Validation ${reference.id} digest does not match`);
552
+ const candidate = await this.readCandidateByReference(validation.candidateRef, access);
553
+ if (!candidate)
554
+ return null;
555
+ if (!sameRefDigest(validation.sourceEvaluationRef, candidate.sourceEvaluationRef)) {
556
+ throw new CompanyOsLearningAdoptionError('integrity_mismatch', `Validation ${validation.id} source evaluation changed`);
557
+ }
558
+ assertValidationLogic(validation);
559
+ return cloneJson(validation);
560
+ }
561
+ async assertCandidateAccess(candidate, access) {
562
+ const evaluation = await this.options.evaluation.read(candidate.sourceEvaluationRef.id, access);
563
+ if (!evaluation)
564
+ throw new CompanyOsLearningAdoptionError('not_found', `Evaluation ${candidate.sourceEvaluationRef.id} was not found`);
565
+ if (digestEvaluationRecord(evaluation) !== candidate.sourceEvaluationRef.digest) {
566
+ throw new CompanyOsLearningAdoptionError('integrity_mismatch', `Candidate ${candidate.id} source evaluation changed`);
567
+ }
568
+ const target = await this.options.target.read(candidate.target, access);
569
+ if (!target || !isTargetReferenceShape(target.reference) || !sameRefExact(target.reference, candidate.target)
570
+ || !sameRefIdentity(target.reference, candidate.target)) {
571
+ throw new CompanyOsLearningAdoptionError('integrity_mismatch', `Candidate ${candidate.id} target is not readable at its exact revision`);
572
+ }
573
+ assertScopeAccess(target.scope, access);
574
+ assertScopeAccess(candidate.applicability, access);
575
+ }
576
+ async readAndAssertRunReceipt(reference, runId, adoption, access) {
577
+ if (!this.options.runReceipt) {
578
+ throw new CompanyOsLearningAdoptionError('unsupported_target', 'Actual run-use requires a host-owned run receipt port');
579
+ }
580
+ const receipt = await this.options.runReceipt.read(reference, access);
581
+ if (!receipt)
582
+ throw new CompanyOsLearningAdoptionError('not_found', `Run receipt ${reference.id} was not found`);
583
+ if (!isRecord(receipt) || !isTargetReferenceShape(receipt.adoptedTarget)
584
+ || receipt.id !== reference.id || receipt.digest !== reference.digest
585
+ || digestLearningRecord({ ...receipt, digest: '' }) !== receipt.digest
586
+ || receipt.runId !== runId || receipt.runPhase !== 'actual'
587
+ || !sameRefExact(receipt.adoptedTarget, adoption.adoptedTarget)) {
588
+ throw new CompanyOsLearningAdoptionError('readback_mismatch', `Run receipt ${reference.id} does not prove exact adopted target use`);
589
+ }
590
+ return receipt;
591
+ }
592
+ async findAdoptionByIdempotencyKey(key, access) {
593
+ const ledger = await this.loadLedger();
594
+ const adoption = ledger.adoptions.find((item) => item.idempotencyKey === key);
595
+ if (!adoption)
596
+ return null;
597
+ return this.readAdoption(adoption.id, access);
598
+ }
599
+ async loadLedger() {
600
+ try {
601
+ await loadPersonalOs(this.options.dataDir);
602
+ return parseLedger(await readPersonalOsSidecar(this.options.dataDir, COMPANY_OS_LEARNING_ADOPTION_SIDECAR));
603
+ }
604
+ catch (error) {
605
+ if (error instanceof CompanyOsLearningAdoptionError)
606
+ throw error;
607
+ throw new CompanyOsLearningAdoptionError('corrupt_record', formatError(error));
608
+ }
609
+ }
610
+ async mutateLedger(mutator) {
611
+ try {
612
+ await mutatePersonalOsWithSidecar(this.options.dataDir, COMPANY_OS_LEARNING_ADOPTION_SIDECAR, async (current, sidecarContent) => {
613
+ if (current.graph.version !== 2)
614
+ throw new CompanyOsLearningAdoptionError('unsupported_target', 'Learning adoption requires canonical Graph v2');
615
+ const ledger = parseLedger(sidecarContent);
616
+ const result = await mutator(ledger, current);
617
+ const nextLedger = isLedgerMutation(result) ? result.ledger : result;
618
+ assertLedger(nextLedger);
619
+ return {
620
+ next: isLedgerMutation(result) ? (result.nextAggregate ?? current) : current,
621
+ sidecarContent: `${JSON.stringify(nextLedger, null, 2)}\n`,
622
+ result: undefined
623
+ };
624
+ });
625
+ }
626
+ catch (error) {
627
+ if (error instanceof CompanyOsLearningAdoptionError)
628
+ throw error;
629
+ throw new CompanyOsLearningAdoptionError('corrupt_record', formatError(error));
630
+ }
631
+ }
632
+ }
633
+ export function digestEvaluationRecord(record) {
634
+ return digestJson(record);
635
+ }
636
+ function normalizeCandidateInput(request) {
637
+ requireId(request.id, 'candidate id');
638
+ const sourceEvaluationRef = normalizeEvaluationReference(request.sourceEvaluationRef);
639
+ const target = normalizeTargetReference(request.target);
640
+ const grounds = normalizeTextArray(request.grounds, 'grounds', true);
641
+ const counterexamples = normalizeTextArray(request.counterexamples, 'counterexamples', false);
642
+ const uncertainty = normalizeTextArray(request.uncertainty, 'uncertainty', true);
643
+ const applicability = normalizeScope(request.applicability);
644
+ requireTimestamp(request.createdAt, 'createdAt');
645
+ return {
646
+ id: request.id,
647
+ sourceEvaluationRef,
648
+ target,
649
+ proposedChange: cloneJson(request.proposedChange),
650
+ grounds,
651
+ counterexamples,
652
+ uncertainty,
653
+ applicability,
654
+ createdAt: request.createdAt
655
+ };
656
+ }
657
+ function normalizeValidationInput(request) {
658
+ requireId(request.id, 'validation id');
659
+ const candidateRef = normalizeRecordReference(request.candidateRef, 'candidateRef');
660
+ if (!Array.isArray(request.findings))
661
+ throw new CompanyOsLearningAdoptionError('invalid_input', 'findings must be an array');
662
+ const findings = request.findings.map((finding, index) => normalizeFinding(finding, index));
663
+ if (!['supported', 'partially_supported', 'indeterminate', 'rejected'].includes(request.conclusion)) {
664
+ throw new CompanyOsLearningAdoptionError('invalid_input', 'Unknown validation conclusion');
665
+ }
666
+ if (!['unchanged', 'revise', 'refuted', 'indeterminate'].includes(request.modelDisposition)) {
667
+ throw new CompanyOsLearningAdoptionError('invalid_input', 'Unknown model disposition');
668
+ }
669
+ if (!isNonEmptyString(request.basis))
670
+ throw new CompanyOsLearningAdoptionError('invalid_input', 'basis is required');
671
+ requireTimestamp(request.validatedAt, 'validatedAt');
672
+ return { id: request.id, candidateRef, findings, conclusion: request.conclusion, modelDisposition: request.modelDisposition, basis: request.basis, validatedAt: request.validatedAt };
673
+ }
674
+ function normalizeFinding(finding, index) {
675
+ if (!isRecord(finding) || !['measurement_error', 'execution_difference', 'external_change', 'other'].includes(finding.kind)) {
676
+ throw new CompanyOsLearningAdoptionError('invalid_input', `findings[${index}] has an unknown kind`);
677
+ }
678
+ if (!isNonEmptyString(finding.description))
679
+ throw new CompanyOsLearningAdoptionError('invalid_input', `findings[${index}].description is required`);
680
+ return { kind: finding.kind, description: finding.description, evidenceRefs: normalizeTextArray(finding.evidenceRefs, `findings[${index}].evidenceRefs`, false) };
681
+ }
682
+ function assertValidationLogic(validation) {
683
+ if (validation.modelDisposition === 'refuted' && !validation.findings.some((finding) => finding.kind !== 'other')) {
684
+ throw new CompanyOsLearningAdoptionError('invalid_validation', 'A prediction gap alone cannot refute a model; causal findings are required');
685
+ }
686
+ }
687
+ function normalizeEvaluationReference(reference) {
688
+ if (!isRecord(reference) || !isNonEmptyString(reference.id) || !isDigest(reference.digest)) {
689
+ throw new CompanyOsLearningAdoptionError('invalid_input', 'sourceEvaluationRef requires id and sha256 digest');
690
+ }
691
+ return { id: reference.id, digest: reference.digest };
692
+ }
693
+ function normalizeTargetReference(reference) {
694
+ if (!isRecord(reference) || !['world_model', 'judgment_method', 'execution_method', 'objective'].includes(reference.kind)
695
+ || !isNonEmptyString(reference.id) || !isRevision(reference.revision) || !isDigest(reference.digest)) {
696
+ throw new CompanyOsLearningAdoptionError('invalid_input', 'target requires kind, id, positive revision, and sha256 digest');
697
+ }
698
+ if (reference.foundationType !== undefined && !['objective', 'variable', 'model', 'constraint'].includes(reference.foundationType)) {
699
+ throw new CompanyOsLearningAdoptionError('invalid_input', 'target foundationType is invalid');
700
+ }
701
+ if ((reference.kind === 'objective' || reference.kind === 'world_model') && reference.foundationType === undefined) {
702
+ throw new CompanyOsLearningAdoptionError('invalid_input', 'Objective/world_model targets require foundationType');
703
+ }
704
+ if ((reference.kind === 'judgment_method' || reference.kind === 'execution_method') && reference.foundationType !== undefined) {
705
+ throw new CompanyOsLearningAdoptionError('invalid_input', 'Method targets cannot carry foundationType');
706
+ }
707
+ return cloneJson(reference);
708
+ }
709
+ function normalizeRecordReference(reference, label) {
710
+ if (!isRecord(reference) || !isNonEmptyString(reference.id) || !isDigest(reference.digest)) {
711
+ throw new CompanyOsLearningAdoptionError('invalid_input', `${label} requires id and sha256 digest`);
712
+ }
713
+ return { id: reference.id, digest: reference.digest };
714
+ }
715
+ function normalizeLearningAdoptionLocator(value) {
716
+ if (!isRecord(value)
717
+ || value.schema !== COMPANY_OS_LEARNING_ADOPTION_LOCATOR_SCHEMA
718
+ || !isNonEmptyString(value.id)
719
+ || !isDigest(value.contentDigest)) {
720
+ throw new CompanyOsLearningAdoptionError('invalid_input', 'adoption locator requires id, the registered schema, and a sha256 contentDigest');
721
+ }
722
+ return {
723
+ id: value.id,
724
+ schema: COMPANY_OS_LEARNING_ADOPTION_LOCATOR_SCHEMA,
725
+ contentDigest: value.contentDigest
726
+ };
727
+ }
728
+ function normalizeRunReceiptReference(reference) {
729
+ if (!isRecord(reference) || !isNonEmptyString(reference.id) || !isDigest(reference.digest)) {
730
+ throw new CompanyOsLearningAdoptionError('invalid_input', 'actual run-use requires a receiptRef with id and sha256 digest');
731
+ }
732
+ return { id: reference.id, digest: reference.digest };
733
+ }
734
+ function assertPreparedRevision(prepared, current) {
735
+ if (!prepared || !sameRefIdentity(prepared.target, current.target) || !isRevision(prepared.reference.revision)
736
+ || !isDigest(prepared.reference.digest) || prepared.reference.revision === current.reference.revision) {
737
+ throw new CompanyOsLearningAdoptionError('integrity_mismatch', 'Target provider returned an invalid prepared revision');
738
+ }
739
+ if (!sameJson(prepared.reference.kind, current.reference.kind) || prepared.reference.id !== current.reference.id) {
740
+ throw new CompanyOsLearningAdoptionError('revision_conflict', 'Prepared revision changes target identity');
741
+ }
742
+ }
743
+ function assertAuthorizationDecision(decision) {
744
+ if (!decision || !['authorized', 'denied'].includes(decision.status)
745
+ || !isNonEmptyString(decision.authorizationId) || !isNonEmptyString(decision.authorizedBy) || !isNonEmptyString(decision.decidedAt)) {
746
+ throw new CompanyOsLearningAdoptionError('authorization_denied', 'Authorization provider returned an invalid decision');
747
+ }
748
+ }
749
+ function foundationTypeForTarget(reference) {
750
+ if (reference.kind === 'judgment_method' || reference.kind === 'execution_method' || reference.foundationType === undefined) {
751
+ throw new CompanyOsLearningAdoptionError('unsupported_target', `No OSS Foundation target adapter exists for ${reference.kind}`);
752
+ }
753
+ if (reference.kind === 'objective' && reference.foundationType !== 'objective') {
754
+ throw new CompanyOsLearningAdoptionError('invalid_input', 'Objective target must use Foundation type objective');
755
+ }
756
+ if (reference.kind === 'world_model' && !['model', 'variable'].includes(reference.foundationType)) {
757
+ throw new CompanyOsLearningAdoptionError('invalid_input', 'World-model target must use Foundation type model or variable');
758
+ }
759
+ return reference.foundationType;
760
+ }
761
+ function foundationTargetVersion(target, definition, digest) {
762
+ return {
763
+ target: cloneJson(target),
764
+ reference: { ...target, id: definition.id, revision: definition.revision, digest, foundationType: definition.type },
765
+ definition: cloneFoundationDefinition(definition),
766
+ acl: cloneJson(definition.acl),
767
+ scope: cloneJson(definition.scope)
768
+ };
769
+ }
770
+ function isFoundationDefinition(value) {
771
+ return isRecord(value) && ['objective', 'variable', 'model', 'constraint'].includes(value.type)
772
+ && isNonEmptyString(value.id) && isRevision(value.revision);
773
+ }
774
+ function isModelDefinition(value) {
775
+ return isFoundationDefinition(value) && value.type === 'model' && typeof value.validationState === 'string';
776
+ }
777
+ function assertAccess(value) {
778
+ if (!value || !isNonEmptyString(value.principal))
779
+ throw new CompanyOsLearningAdoptionError('authorization_denied', 'A trusted principal is required');
780
+ if (value.scope !== undefined)
781
+ normalizeScope(value.scope);
782
+ return value;
783
+ }
784
+ function assertScopeAccess(scope, access) {
785
+ if (access.scope === undefined)
786
+ return;
787
+ if (!scope.subjectIds.some((id) => access.scope?.subjectIds.includes(id))) {
788
+ throw new CompanyOsLearningAdoptionError('scope_violation', `Principal ${access.principal} cannot access the target scope`);
789
+ }
790
+ }
791
+ function normalizeScope(scope) {
792
+ if (!isRecord(scope) || !Array.isArray(scope.subjectIds) || scope.subjectIds.length === 0 || !scope.subjectIds.every(isNonEmptyString)
793
+ || !isNonEmptyString(scope.validFrom) || (scope.validUntil !== undefined && !isNonEmptyString(scope.validUntil))) {
794
+ throw new CompanyOsLearningAdoptionError('invalid_input', 'applicability must contain subjectIds and validFrom');
795
+ }
796
+ return cloneJson(scope);
797
+ }
798
+ function normalizeTextArray(value, label, requireOne) {
799
+ if (!Array.isArray(value) || (requireOne && value.length === 0) || !value.every(isNonEmptyString)) {
800
+ throw new CompanyOsLearningAdoptionError('invalid_input', `${label} must contain ${requireOne ? 'at least one ' : ''}non-empty string`);
801
+ }
802
+ return [...value];
803
+ }
804
+ function requireTimestamp(value, label) {
805
+ if (!isNonEmptyString(value) || Number.isNaN(Date.parse(value)))
806
+ throw new CompanyOsLearningAdoptionError('invalid_input', `${label} must be an RFC3339 timestamp`);
807
+ }
808
+ function requireId(value, label) {
809
+ if (!isNonEmptyString(value))
810
+ throw new CompanyOsLearningAdoptionError('invalid_input', `${label} is required`);
811
+ return value;
812
+ }
813
+ function isRevision(value) {
814
+ return typeof value === 'string' && /^[1-9]\d*$/.test(value);
815
+ }
816
+ function isDigest(value) {
817
+ return typeof value === 'string' && /^sha256:[0-9a-f]{64}$/.test(value);
818
+ }
819
+ function isFoundationAcl(value) {
820
+ return isRecord(value) && isNonEmptyString(value.ownerId) && ['private', 'project', 'organization', 'public'].includes(value.visibility)
821
+ && Array.isArray(value.readerIds) && value.readerIds.every(isNonEmptyString)
822
+ && Array.isArray(value.writerIds) && value.writerIds.every(isNonEmptyString);
823
+ }
824
+ function sameRefIdentity(left, right) {
825
+ return left.kind === right.kind && left.id === right.id && left.foundationType === right.foundationType;
826
+ }
827
+ function sameRefExact(left, right) {
828
+ return sameRefIdentity(left, right) && left.revision === right.revision && left.digest === right.digest;
829
+ }
830
+ function isTargetReferenceShape(value) {
831
+ return isRecord(value)
832
+ && ['world_model', 'judgment_method', 'execution_method', 'objective'].includes(value.kind)
833
+ && isNonEmptyString(value.id)
834
+ && isRevision(value.revision)
835
+ && isDigest(value.digest)
836
+ && (value.foundationType === undefined || ['objective', 'variable', 'model', 'constraint'].includes(value.foundationType));
837
+ }
838
+ function sameRefDigest(left, right) {
839
+ return left.id === right.id && left.digest === right.digest;
840
+ }
841
+ function digestLearningRecord(value) {
842
+ return digestJson(value);
843
+ }
844
+ function digestJson(value) {
845
+ const serialized = JSON.stringify(canonicalize(value));
846
+ if (serialized === undefined)
847
+ throw new CompanyOsLearningAdoptionError('invalid_input', 'Value is not JSON serializable');
848
+ return `sha256:${createHash('sha256').update(serialized, 'utf8').digest('hex')}`;
849
+ }
850
+ function canonicalize(value) {
851
+ if (Array.isArray(value))
852
+ return value.map(canonicalize);
853
+ if (value && typeof value === 'object') {
854
+ return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right, 'en')).map(([key, entry]) => [key, canonicalize(entry)]));
855
+ }
856
+ return value;
857
+ }
858
+ function cloneJson(value) {
859
+ return JSON.parse(JSON.stringify(value));
860
+ }
861
+ function sameJson(left, right) {
862
+ return JSON.stringify(canonicalize(left)) === JSON.stringify(canonicalize(right));
863
+ }
864
+ function isNonEmptyString(value) {
865
+ return typeof value === 'string' && value.trim().length > 0;
866
+ }
867
+ function isRecord(value) {
868
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
869
+ }
870
+ function formatError(error) {
871
+ return error instanceof Error ? error.message : String(error);
872
+ }
873
+ function isLedgerMutation(value) {
874
+ return isRecord(value) && 'ledger' in value;
875
+ }
876
+ function parseLedger(content) {
877
+ if (content === undefined || content.trim() === '')
878
+ return emptyLedger();
879
+ let parsed;
880
+ try {
881
+ parsed = JSON.parse(content);
882
+ }
883
+ catch (error) {
884
+ throw new CompanyOsLearningAdoptionError('corrupt_record', `Learning adoption sidecar is not valid JSON: ${formatError(error)}`);
885
+ }
886
+ try {
887
+ assertLedger(parsed);
888
+ return cloneJson(parsed);
889
+ }
890
+ catch (error) {
891
+ if (error instanceof CompanyOsLearningAdoptionError)
892
+ throw error;
893
+ throw new CompanyOsLearningAdoptionError('corrupt_record', formatError(error));
894
+ }
895
+ }
896
+ function emptyLedger() {
897
+ return { version: COMPANY_OS_LEARNING_ADOPTION_RECORD_CATALOG_VERSION, candidates: [], validations: [], adoptions: [], runUses: [] };
898
+ }
899
+ function assertLedger(value) {
900
+ if (!isRecord(value) || value.version !== COMPANY_OS_LEARNING_ADOPTION_RECORD_CATALOG_VERSION
901
+ || !Array.isArray(value.candidates) || !Array.isArray(value.validations) || !Array.isArray(value.adoptions) || !Array.isArray(value.runUses)) {
902
+ throw new CompanyOsLearningAdoptionError('corrupt_record', 'Learning adoption sidecar has an invalid catalog shape');
903
+ }
904
+ uniqueIds(value.candidates, 'candidate');
905
+ uniqueIds(value.validations, 'validation');
906
+ uniqueIds(value.adoptions, 'adoption');
907
+ uniqueIds(value.runUses, 'run-use');
908
+ for (const candidate of value.candidates) {
909
+ if (!isRecord(candidate) || candidate.version !== 1 || !isNonEmptyString(candidate.id) || !isDigest(candidate.digest)
910
+ || digestLearningRecord({ ...candidate, digest: '' }) !== candidate.digest)
911
+ throw new CompanyOsLearningAdoptionError('corrupt_record', 'Learning candidate digest or shape is invalid');
912
+ }
913
+ for (const validation of value.validations) {
914
+ if (!isRecord(validation) || validation.version !== 1 || !isNonEmptyString(validation.id) || !isDigest(validation.digest)
915
+ || digestLearningRecord({ ...validation, digest: '' }) !== validation.digest)
916
+ throw new CompanyOsLearningAdoptionError('corrupt_record', 'Learning validation digest or shape is invalid');
917
+ assertValidationLogic(validation);
918
+ }
919
+ for (const adoption of value.adoptions) {
920
+ if (!isRecord(adoption) || adoption.version !== 1 || !isNonEmptyString(adoption.id) || !isDigest(adoption.digest)
921
+ || digestLearningRecord({ ...adoption, digest: '' }) !== adoption.digest)
922
+ throw new CompanyOsLearningAdoptionError('corrupt_record', 'Learning adoption digest or shape is invalid');
923
+ }
924
+ for (const runUse of value.runUses) {
925
+ if (!isRecord(runUse) || runUse.version !== 1 || !isNonEmptyString(runUse.id) || !isDigest(runUse.digest)
926
+ || !['planned', 'actual'].includes(runUse.runPhase)
927
+ || !isTargetReferenceShape(runUse.adoptedTarget)
928
+ || (runUse.runPhase === 'actual' && !isRunReceiptReferenceShape(runUse.receiptRef))
929
+ || (runUse.runPhase === 'planned' && runUse.receiptRef !== undefined)
930
+ || digestLearningRecord({ ...runUse, digest: '' }) !== runUse.digest)
931
+ throw new CompanyOsLearningAdoptionError('corrupt_record', 'Learning run-use digest or shape is invalid');
932
+ }
933
+ }
934
+ function isRunReceiptReferenceShape(value) {
935
+ return isRecord(value) && isNonEmptyString(value.id) && isDigest(value.digest);
936
+ }
937
+ function uniqueIds(values, label) {
938
+ const ids = new Set();
939
+ for (const value of values) {
940
+ if (!isRecord(value) || !isNonEmptyString(value.id) || ids.has(value.id))
941
+ throw new CompanyOsLearningAdoptionError('corrupt_record', `Duplicate or invalid ${label} id`);
942
+ ids.add(value.id);
943
+ }
944
+ }