@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.
- package/README.md +8 -0
- package/contracts/judgment-dag/digest.json +2 -2
- package/dist/canonical-task-contract.d.ts +9 -0
- package/dist/canonical-task-contract.js +39 -0
- package/dist/canonical-task-principal.d.ts +16 -0
- package/dist/canonical-task-principal.js +36 -0
- package/dist/canonical-task-service.d.ts +251 -0
- package/dist/canonical-task-service.js +809 -0
- package/dist/company-os-evaluation.d.ts +193 -0
- package/dist/company-os-evaluation.js +1150 -0
- package/dist/company-os-impact-review.d.ts +248 -0
- package/dist/company-os-impact-review.js +663 -0
- package/dist/company-os-learning-adoption.d.ts +288 -0
- package/dist/company-os-learning-adoption.js +944 -0
- package/dist/company-os-objectives.d.ts +52 -0
- package/dist/company-os-objectives.js +197 -0
- package/dist/company-os-receipt-adapter.d.ts +109 -0
- package/dist/company-os-receipt-adapter.js +636 -0
- package/dist/constraint-exception-store.d.ts +23 -0
- package/dist/constraint-exception-store.js +108 -0
- package/dist/constraint-resolution.d.ts +175 -0
- package/dist/constraint-resolution.js +922 -0
- package/dist/decision-adapter.d.ts +145 -0
- package/dist/decision-adapter.js +586 -0
- package/dist/durable-wait-http.d.ts +51 -0
- package/dist/durable-wait-http.js +497 -0
- package/dist/durable-waits.d.ts +234 -0
- package/dist/durable-waits.js +944 -0
- package/dist/execution-authority-http.d.ts +28 -0
- package/dist/execution-authority-http.js +359 -0
- package/dist/execution-authority.d.ts +201 -0
- package/dist/execution-authority.js +634 -0
- package/dist/foundation-catalog.d.ts +26 -0
- package/dist/foundation-catalog.js +206 -0
- package/dist/foundation-constraint-store.d.ts +50 -0
- package/dist/foundation-constraint-store.js +215 -0
- package/dist/foundation-http.d.ts +90 -0
- package/dist/foundation-http.js +439 -0
- package/dist/foundation-store.d.ts +132 -0
- package/dist/foundation-store.js +654 -0
- package/dist/judgment-dag-composition.d.ts +224 -0
- package/dist/judgment-dag-composition.js +731 -0
- package/dist/judgment-dag.d.ts +2 -0
- package/dist/judgment-dag.js +1 -0
- package/dist/judgment-problem-snapshot.d.ts +185 -0
- package/dist/judgment-problem-snapshot.js +777 -0
- package/dist/judgment-view-http.d.ts +22 -0
- package/dist/judgment-view-http.js +128 -0
- package/dist/judgment-view.d.ts +222 -0
- package/dist/judgment-view.js +627 -0
- package/dist/knowledge-adapter.d.ts +112 -0
- package/dist/knowledge-adapter.js +691 -0
- package/dist/ontology-foundation.d.ts +209 -0
- package/dist/ontology-foundation.js +1000 -0
- package/dist/ontology.d.ts +13 -0
- package/dist/ontology.js +14 -0
- package/dist/organization-connection.d.ts +164 -0
- package/dist/organization-connection.js +428 -0
- package/dist/personal-knowledge.d.ts +120 -0
- package/dist/personal-knowledge.js +750 -0
- package/dist/problem-candidates.d.ts +149 -0
- package/dist/problem-candidates.js +898 -0
- package/dist/problem-selection.d.ts +238 -0
- package/dist/problem-selection.js +995 -0
- package/dist/resource-reservation-http.d.ts +31 -0
- package/dist/resource-reservation-http.js +371 -0
- package/dist/resource-reservations.d.ts +221 -0
- package/dist/resource-reservations.js +577 -0
- package/dist/server.d.ts +97 -0
- package/dist/server.js +180 -1
- package/dist/ssot.d.ts +6 -1
- package/dist/ssot.js +63 -5
- package/dist/streamable-http.d.ts +42 -0
- package/dist/streamable-http.js +288 -0
- package/dist/types.d.ts +20 -0
- package/dist/world-model.d.ts +181 -0
- package/dist/world-model.js +873 -0
- package/package.json +145 -3
- package/ui/icons/mana/LICENSE.tabler.txt +21 -0
- package/ui/icons/mana/affiliate.svg +20 -0
- package/ui/icons/mana/brand-github.svg +15 -0
- package/ui/icons/mana/brand-google-drive.svg +17 -0
- package/ui/icons/mana/brand-slack.svg +18 -0
- package/ui/icons/mana/cpu.svg +24 -0
- package/ui/judgment-view.css +238 -0
- package/ui/judgment-view.js +508 -0
- package/ui/objective-editor.css +180 -0
- package/ui/objective-editor.js +1139 -0
- package/ui/outcome-knowledge.css +365 -0
- package/ui/outcome-knowledge.js +1570 -0
- package/ui/outcome-mana.css +255 -0
- package/ui/outcome-mana.js +2325 -0
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { DecisionAdapterError, validateDecisionAdapterConditions } from './decision-adapter.js';
|
|
3
|
+
import { initializePersonalOs, loadPersonalOs, mutatePersonalOsWithSidecar, readPersonalOsSidecar } from './ssot.js';
|
|
4
|
+
/** Common contract for attaching exact judgment conditions to old knowledge records. */
|
|
5
|
+
export const KNOWLEDGE_ADAPTER_VERSION = 'knowledge-condition-adapter.v2';
|
|
6
|
+
/** A read-only compatibility marker for sidecars written by the v1 adapter. */
|
|
7
|
+
export const LEGACY_KNOWLEDGE_ADAPTER_VERSION = 'knowledge-condition-adapter.v1';
|
|
8
|
+
export const KNOWLEDGE_ADAPTER_SIDECAR = 'evidence/knowledge-condition-adapter.json';
|
|
9
|
+
export class KnowledgeAdapterError extends Error {
|
|
10
|
+
code;
|
|
11
|
+
constructor(code, message) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = 'KnowledgeAdapterError';
|
|
14
|
+
this.code = code;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Personal canonical implementation. Knowledge records remain owned by the
|
|
19
|
+
* host provider; this store only keeps exact condition/provenance references in
|
|
20
|
+
* a transactional sidecar next to the canonical aggregate.
|
|
21
|
+
*/
|
|
22
|
+
export class GraphKnowledgeConditionAdapter {
|
|
23
|
+
dataDir;
|
|
24
|
+
recordPort;
|
|
25
|
+
adoptionPort;
|
|
26
|
+
sidecarPath;
|
|
27
|
+
constructor(options) {
|
|
28
|
+
if (!options || !options.dataDir)
|
|
29
|
+
throw new KnowledgeAdapterError('validation_error', 'dataDir is required');
|
|
30
|
+
if (!options.recordPort || typeof options.recordPort.read !== 'function') {
|
|
31
|
+
throw new KnowledgeAdapterError('validation_error', 'recordPort with read is required');
|
|
32
|
+
}
|
|
33
|
+
this.dataDir = options.dataDir;
|
|
34
|
+
this.recordPort = options.recordPort;
|
|
35
|
+
this.adoptionPort = options.adoptionPort;
|
|
36
|
+
this.sidecarPath = options.sidecarPath ?? KNOWLEDGE_ADAPTER_SIDECAR;
|
|
37
|
+
assertSidecarPath(this.sidecarPath);
|
|
38
|
+
}
|
|
39
|
+
async attach(source, conditions, context, options = {}) {
|
|
40
|
+
const requestedSource = normalizeSourceReference(source, 'source');
|
|
41
|
+
const normalizedConditions = normalizeConditions(conditions);
|
|
42
|
+
assertContext(context);
|
|
43
|
+
const idempotencyKey = normalizeText(options.idempotency_key ?? `knowledge:${requestedSource.kind}:${requestedSource.id}`, 'idempotency_key', 512);
|
|
44
|
+
await initializePersonalOs(this.dataDir);
|
|
45
|
+
const snapshot = await this.loadSnapshot();
|
|
46
|
+
const sourceRead = await this.readSource(requestedSource, context, 'attach');
|
|
47
|
+
if (sourceRead.source_status === 'denied' || sourceRead.acl_status !== 'allowed') {
|
|
48
|
+
throw new KnowledgeAdapterError('authorization_denied', `Knowledge source ${sourceKey(requestedSource)} is not readable`);
|
|
49
|
+
}
|
|
50
|
+
if (sourceRead.source_status === 'not_found' || sourceRead.source_status === 'retracted') {
|
|
51
|
+
throw new KnowledgeAdapterError('source_unavailable', `Knowledge source ${sourceKey(requestedSource)} cannot receive a binding`);
|
|
52
|
+
}
|
|
53
|
+
if (sourceRead.source_status !== 'present' && sourceRead.source_status !== 'quarantined') {
|
|
54
|
+
throw new KnowledgeAdapterError('source_unavailable', `Knowledge source ${sourceKey(requestedSource)} cannot receive a binding`);
|
|
55
|
+
}
|
|
56
|
+
const sourceStatusAtAttach = sourceRead.source_status;
|
|
57
|
+
const exactSource = sourceRead.source;
|
|
58
|
+
if (!exactSource || !hasExactSourceLocator(exactSource)) {
|
|
59
|
+
throw new KnowledgeAdapterError('source_unavailable', `Knowledge source ${sourceKey(requestedSource)} did not provide an exact revision and digest`);
|
|
60
|
+
}
|
|
61
|
+
assertExpectedSourceLocator(requestedSource, exactSource);
|
|
62
|
+
const adoption = await this.readAdoption(exactSource, context, 'attach');
|
|
63
|
+
if (adoption.status === 'denied') {
|
|
64
|
+
throw new KnowledgeAdapterError('authorization_denied', `Adoption record for ${sourceKey(exactSource)} is not readable`);
|
|
65
|
+
}
|
|
66
|
+
const attachedAt = new Date().toISOString();
|
|
67
|
+
const bindingId = bindingIdFor(exactSource);
|
|
68
|
+
try {
|
|
69
|
+
return await mutatePersonalOsWithSidecar(this.dataDir, this.sidecarPath, (current, sidecarContent) => {
|
|
70
|
+
assertSnapshotUnchanged(snapshot, current, sidecarContent);
|
|
71
|
+
const sidecar = parseSidecar(sidecarContent);
|
|
72
|
+
const key = sourceKey(exactSource);
|
|
73
|
+
const existing = sidecar.bindings[key];
|
|
74
|
+
if (existing) {
|
|
75
|
+
if (!sameBindingPayload(existing, {
|
|
76
|
+
idempotency_key: idempotencyKey,
|
|
77
|
+
source: exactSource,
|
|
78
|
+
conditions: normalizedConditions,
|
|
79
|
+
provenance: sourceRead.provenance,
|
|
80
|
+
...(adoption.adoption ? { adoption: adoption.adoption } : {})
|
|
81
|
+
})) {
|
|
82
|
+
throw new KnowledgeAdapterError('condition_conflict', `Knowledge source ${key} already has a different condition binding`);
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
next: current,
|
|
86
|
+
sidecarContent: serializeSidecar(sidecar),
|
|
87
|
+
result: makeAttachReceipt(existing, sourceRead, adoption, 'existing')
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const conflictingKey = Object.keys(sidecar.bindings).find((candidate) => (sidecar.bindings[candidate]?.idempotency_key === idempotencyKey));
|
|
91
|
+
if (conflictingKey !== undefined) {
|
|
92
|
+
throw new KnowledgeAdapterError('condition_conflict', `Idempotency key ${idempotencyKey} is already bound to ${conflictingKey}`);
|
|
93
|
+
}
|
|
94
|
+
const stored = {
|
|
95
|
+
binding_id: bindingId,
|
|
96
|
+
idempotency_key: idempotencyKey,
|
|
97
|
+
source: clone(exactSource),
|
|
98
|
+
source_status_at_attach: sourceStatusAtAttach,
|
|
99
|
+
conditions: clone(normalizedConditions),
|
|
100
|
+
provenance: clone(sourceRead.provenance),
|
|
101
|
+
...(adoption.adoption ? { adoption: clone(adoption.adoption) } : {}),
|
|
102
|
+
attached_at: attachedAt
|
|
103
|
+
};
|
|
104
|
+
const nextSidecar = {
|
|
105
|
+
...sidecar,
|
|
106
|
+
bindings: { ...sidecar.bindings, [key]: stored }
|
|
107
|
+
};
|
|
108
|
+
assertStagedBinding(nextSidecar, key, stored);
|
|
109
|
+
return {
|
|
110
|
+
next: current,
|
|
111
|
+
sidecarContent: serializeSidecar(nextSidecar),
|
|
112
|
+
result: makeAttachReceipt(stored, sourceRead, adoption, 'created')
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
if (error instanceof KnowledgeAdapterError)
|
|
118
|
+
throw error;
|
|
119
|
+
throw new KnowledgeAdapterError('store_corrupt', formatError(error));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async read(source, context) {
|
|
123
|
+
const requestedSource = normalizeSourceReference(source, 'source');
|
|
124
|
+
assertContext(context);
|
|
125
|
+
await initializePersonalOs(this.dataDir);
|
|
126
|
+
let sourceRead;
|
|
127
|
+
try {
|
|
128
|
+
sourceRead = await this.readSource(requestedSource, context, 'read');
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
if (error instanceof KnowledgeAdapterError)
|
|
132
|
+
throw error;
|
|
133
|
+
throw new KnowledgeAdapterError('source_unavailable', formatError(error));
|
|
134
|
+
}
|
|
135
|
+
if (sourceRead.source_status === 'denied' || sourceRead.acl_status === 'denied') {
|
|
136
|
+
return {
|
|
137
|
+
source: requestedSource,
|
|
138
|
+
source_status: sourceRead.source_status,
|
|
139
|
+
acl_status: sourceRead.acl_status,
|
|
140
|
+
condition_status: 'denied',
|
|
141
|
+
provenance: [],
|
|
142
|
+
adoption_status: 'denied'
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
if (sourceRead.acl_status === 'unknown') {
|
|
146
|
+
return {
|
|
147
|
+
source: requestedSource,
|
|
148
|
+
source_status: sourceRead.source_status,
|
|
149
|
+
acl_status: sourceRead.acl_status,
|
|
150
|
+
condition_status: 'unavailable',
|
|
151
|
+
provenance: [],
|
|
152
|
+
adoption_status: 'unavailable'
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
if (sourceRead.source_status === 'not_found' || sourceRead.source_status === 'retracted' || !sourceRead.source) {
|
|
156
|
+
return {
|
|
157
|
+
source: requestedSource,
|
|
158
|
+
source_status: sourceRead.source_status,
|
|
159
|
+
acl_status: sourceRead.acl_status,
|
|
160
|
+
condition_status: 'unavailable',
|
|
161
|
+
provenance: [],
|
|
162
|
+
adoption_status: 'unavailable'
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
const exactSource = sourceRead.source;
|
|
166
|
+
if (!hasExactSourceLocator(exactSource)) {
|
|
167
|
+
return {
|
|
168
|
+
source: requestedSource,
|
|
169
|
+
resolved_source: clone(exactSource),
|
|
170
|
+
source_status: sourceRead.source_status,
|
|
171
|
+
acl_status: sourceRead.acl_status,
|
|
172
|
+
condition_status: 'unavailable',
|
|
173
|
+
provenance: clone(sourceRead.provenance),
|
|
174
|
+
adoption_status: 'unavailable'
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
assertExpectedSourceLocator(requestedSource, exactSource);
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
return {
|
|
182
|
+
source: requestedSource,
|
|
183
|
+
resolved_source: clone(exactSource),
|
|
184
|
+
source_status: sourceRead.source_status,
|
|
185
|
+
acl_status: sourceRead.acl_status,
|
|
186
|
+
condition_status: 'integrity_mismatch',
|
|
187
|
+
provenance: clone(sourceRead.provenance),
|
|
188
|
+
adoption_status: 'unavailable'
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
const sidecar = await this.loadSidecar();
|
|
192
|
+
const stored = sidecar.bindings[sourceKey(exactSource)];
|
|
193
|
+
if (!stored) {
|
|
194
|
+
return {
|
|
195
|
+
source: requestedSource,
|
|
196
|
+
resolved_source: clone(exactSource),
|
|
197
|
+
source_status: sourceRead.source_status,
|
|
198
|
+
acl_status: sourceRead.acl_status,
|
|
199
|
+
condition_status: 'unrecorded',
|
|
200
|
+
provenance: clone(sourceRead.provenance),
|
|
201
|
+
adoption_status: this.adoptionPort ? 'unrecorded' : 'unavailable'
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
validateStoredBinding(stored, sourceKey(exactSource));
|
|
205
|
+
if (!sameJson(stored.source, exactSource) || !sameJson(stored.provenance, sourceRead.provenance)) {
|
|
206
|
+
return {
|
|
207
|
+
source: requestedSource,
|
|
208
|
+
resolved_source: clone(exactSource),
|
|
209
|
+
source_status: sourceRead.source_status,
|
|
210
|
+
acl_status: sourceRead.acl_status,
|
|
211
|
+
condition_status: 'integrity_mismatch',
|
|
212
|
+
provenance: clone(sourceRead.provenance),
|
|
213
|
+
adoption_status: 'unavailable'
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
if (stored.legacy_adoption) {
|
|
217
|
+
// The v1 shape has no semantic schema/contentDigest contract. Preserve
|
|
218
|
+
// it for an explicit migration, but never treat its revision as the
|
|
219
|
+
// current adoption locator or claim this binding is verified.
|
|
220
|
+
return {
|
|
221
|
+
source: requestedSource,
|
|
222
|
+
resolved_source: clone(exactSource),
|
|
223
|
+
source_status: sourceRead.source_status,
|
|
224
|
+
acl_status: sourceRead.acl_status,
|
|
225
|
+
condition_status: 'unavailable',
|
|
226
|
+
provenance: clone(stored.provenance),
|
|
227
|
+
adoption_status: 'unavailable'
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
// An adoption locator is part of the historical binding snapshot. A
|
|
231
|
+
// provider may record adoption after this binding was attached, but that
|
|
232
|
+
// later record belongs to a new binding/revision and must not be surfaced
|
|
233
|
+
// as if it had been saved here.
|
|
234
|
+
const storedAdoption = stored.adoption;
|
|
235
|
+
if (!storedAdoption) {
|
|
236
|
+
return {
|
|
237
|
+
source: requestedSource,
|
|
238
|
+
resolved_source: clone(exactSource),
|
|
239
|
+
source_status: sourceRead.source_status,
|
|
240
|
+
acl_status: sourceRead.acl_status,
|
|
241
|
+
condition_status: 'recorded',
|
|
242
|
+
conditions: clone(stored.conditions),
|
|
243
|
+
provenance: clone(stored.provenance),
|
|
244
|
+
adoption_status: this.adoptionPort ? 'unrecorded' : 'unavailable'
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
const adoption = await this.readAdoption(exactSource, context, 'read');
|
|
248
|
+
if (adoption.status === 'denied') {
|
|
249
|
+
return {
|
|
250
|
+
source: requestedSource,
|
|
251
|
+
resolved_source: clone(exactSource),
|
|
252
|
+
source_status: sourceRead.source_status,
|
|
253
|
+
acl_status: sourceRead.acl_status,
|
|
254
|
+
condition_status: 'denied',
|
|
255
|
+
provenance: clone(stored.provenance),
|
|
256
|
+
adoption_status: 'denied'
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
if (adoption.status === 'unavailable') {
|
|
260
|
+
return {
|
|
261
|
+
source: requestedSource,
|
|
262
|
+
resolved_source: clone(exactSource),
|
|
263
|
+
source_status: sourceRead.source_status,
|
|
264
|
+
acl_status: sourceRead.acl_status,
|
|
265
|
+
condition_status: 'unavailable',
|
|
266
|
+
provenance: clone(stored.provenance),
|
|
267
|
+
adoption_status: 'unavailable'
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
if (adoption.status === 'unrecorded') {
|
|
271
|
+
return {
|
|
272
|
+
source: requestedSource,
|
|
273
|
+
resolved_source: clone(exactSource),
|
|
274
|
+
source_status: sourceRead.source_status,
|
|
275
|
+
acl_status: sourceRead.acl_status,
|
|
276
|
+
condition_status: 'integrity_mismatch',
|
|
277
|
+
provenance: clone(stored.provenance),
|
|
278
|
+
adoption_status: 'unrecorded'
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
if (adoption.status === 'recorded'
|
|
282
|
+
&& (!adoption.adoption || !sameJson(storedAdoption, adoption.adoption))) {
|
|
283
|
+
return {
|
|
284
|
+
source: requestedSource,
|
|
285
|
+
resolved_source: clone(exactSource),
|
|
286
|
+
source_status: sourceRead.source_status,
|
|
287
|
+
acl_status: sourceRead.acl_status,
|
|
288
|
+
condition_status: 'integrity_mismatch',
|
|
289
|
+
provenance: clone(stored.provenance),
|
|
290
|
+
adoption_status: adoption.status
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
return {
|
|
294
|
+
source: requestedSource,
|
|
295
|
+
resolved_source: clone(exactSource),
|
|
296
|
+
source_status: sourceRead.source_status,
|
|
297
|
+
acl_status: sourceRead.acl_status,
|
|
298
|
+
condition_status: 'recorded',
|
|
299
|
+
conditions: clone(stored.conditions),
|
|
300
|
+
provenance: clone(stored.provenance),
|
|
301
|
+
adoption_status: adoption.status,
|
|
302
|
+
adoption: clone(storedAdoption)
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
async readSource(source, context, operation) {
|
|
306
|
+
try {
|
|
307
|
+
const result = await this.recordPort.read({ source: clone(source), context: clone(context) });
|
|
308
|
+
return normalizeSourceRead(result, source);
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
if (error instanceof KnowledgeAdapterError)
|
|
312
|
+
throw error;
|
|
313
|
+
throw new KnowledgeAdapterError('source_unavailable', formatError(error));
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
async readAdoption(source, context, operation) {
|
|
317
|
+
if (!this.adoptionPort)
|
|
318
|
+
return { status: 'unavailable' };
|
|
319
|
+
try {
|
|
320
|
+
const result = await this.adoptionPort.read({ source: clone(source), context: clone(context) });
|
|
321
|
+
if (!result || typeof result !== 'object')
|
|
322
|
+
return { status: 'unavailable' };
|
|
323
|
+
if (result.status !== 'recorded' && result.status !== 'unrecorded'
|
|
324
|
+
&& result.status !== 'denied' && result.status !== 'unavailable') {
|
|
325
|
+
return { status: 'unavailable' };
|
|
326
|
+
}
|
|
327
|
+
if (result.adoption === undefined) {
|
|
328
|
+
return result.status === 'recorded' ? { status: 'unavailable' } : { status: result.status };
|
|
329
|
+
}
|
|
330
|
+
if (result.status !== 'recorded')
|
|
331
|
+
return { status: 'unavailable' };
|
|
332
|
+
return { status: 'recorded', adoption: normalizeAdoptionReference(result.adoption, 'adoption') };
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
if (operation === 'read')
|
|
336
|
+
return { status: 'unavailable' };
|
|
337
|
+
if (error instanceof KnowledgeAdapterError)
|
|
338
|
+
throw error;
|
|
339
|
+
return { status: 'unavailable' };
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
async loadSnapshot() {
|
|
343
|
+
try {
|
|
344
|
+
return {
|
|
345
|
+
current: await loadPersonalOs(this.dataDir),
|
|
346
|
+
sidecarContent: await readPersonalOsSidecar(this.dataDir, this.sidecarPath)
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
throw new KnowledgeAdapterError('store_corrupt', formatError(error));
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
async loadSidecar() {
|
|
354
|
+
try {
|
|
355
|
+
return parseSidecar(await readPersonalOsSidecar(this.dataDir, this.sidecarPath));
|
|
356
|
+
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
if (error instanceof KnowledgeAdapterError)
|
|
359
|
+
throw error;
|
|
360
|
+
throw new KnowledgeAdapterError('store_corrupt', formatError(error));
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
export function createKnowledgeConditionAdapter(options) {
|
|
365
|
+
return new GraphKnowledgeConditionAdapter(options);
|
|
366
|
+
}
|
|
367
|
+
function normalizeSourceReference(value, field) {
|
|
368
|
+
if (!value || typeof value !== 'object')
|
|
369
|
+
throw new KnowledgeAdapterError('validation_error', `${field} is required`);
|
|
370
|
+
const input = value;
|
|
371
|
+
if (!KNOWLEDGE_RECORD_KINDS.includes(input.kind)) {
|
|
372
|
+
throw new KnowledgeAdapterError('validation_error', `${field}.kind is unsupported`);
|
|
373
|
+
}
|
|
374
|
+
const normalized = {
|
|
375
|
+
kind: input.kind,
|
|
376
|
+
id: normalizeText(input.id, `${field}.id`, 512),
|
|
377
|
+
...(input.revision === undefined ? {} : { revision: normalizeRevision(input.revision, `${field}.revision`) }),
|
|
378
|
+
...(input.digest === undefined ? {} : { digest: normalizeDigest(input.digest, `${field}.digest`) })
|
|
379
|
+
};
|
|
380
|
+
return normalized;
|
|
381
|
+
}
|
|
382
|
+
function normalizeEvidenceReference(value, field) {
|
|
383
|
+
if (!value || typeof value !== 'object')
|
|
384
|
+
throw new KnowledgeAdapterError('store_corrupt', `${field} is invalid`);
|
|
385
|
+
const input = value;
|
|
386
|
+
return {
|
|
387
|
+
kind: normalizeText(input.kind, `${field}.kind`, 256),
|
|
388
|
+
id: normalizeText(input.id, `${field}.id`, 2_048),
|
|
389
|
+
...(input.revision === undefined ? {} : { revision: normalizeRevision(input.revision, `${field}.revision`) }),
|
|
390
|
+
...(input.digest === undefined ? {} : { digest: normalizeDigest(input.digest, `${field}.digest`) })
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
function normalizeAdoptionReference(value, field) {
|
|
394
|
+
if (!value || typeof value !== 'object')
|
|
395
|
+
throw new KnowledgeAdapterError('source_unavailable', `${field} is invalid`);
|
|
396
|
+
const input = value;
|
|
397
|
+
return {
|
|
398
|
+
id: normalizeText(input.id, `${field}.id`, 512),
|
|
399
|
+
schema: normalizeSchema(input.schema, `${field}.schema`),
|
|
400
|
+
contentDigest: normalizeDigest(input.contentDigest, `${field}.contentDigest`)
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
function normalizeSchema(value, field) {
|
|
404
|
+
const schema = normalizeText(value, field, 128);
|
|
405
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/u.test(schema)) {
|
|
406
|
+
throw new KnowledgeAdapterError('validation_error', `${field} must be a semantic schema identifier`);
|
|
407
|
+
}
|
|
408
|
+
return schema;
|
|
409
|
+
}
|
|
410
|
+
function normalizeConditions(value) {
|
|
411
|
+
if (!value || typeof value !== 'object')
|
|
412
|
+
throw new KnowledgeAdapterError('validation_error', 'conditions are required');
|
|
413
|
+
const input = value;
|
|
414
|
+
const snapshot = input.problem_snapshot;
|
|
415
|
+
const method = input.method;
|
|
416
|
+
const refs = input.objective_refs;
|
|
417
|
+
const normalized = {
|
|
418
|
+
problem_snapshot: {
|
|
419
|
+
snapshot_id: normalizeSnapshotId(snapshot?.snapshot_id, 'conditions.problem_snapshot.snapshot_id'),
|
|
420
|
+
problem_id: String(snapshot?.problem_id ?? ''),
|
|
421
|
+
revision: String(snapshot?.revision ?? '')
|
|
422
|
+
},
|
|
423
|
+
method: {
|
|
424
|
+
id: String(method?.id ?? ''),
|
|
425
|
+
version: String(method?.version ?? '')
|
|
426
|
+
},
|
|
427
|
+
objective_refs: Array.isArray(refs) ? refs.map((value) => {
|
|
428
|
+
const ref = value;
|
|
429
|
+
return {
|
|
430
|
+
id: String(ref?.id ?? ''),
|
|
431
|
+
type: 'objective',
|
|
432
|
+
revision: String(ref?.revision ?? '')
|
|
433
|
+
};
|
|
434
|
+
}) : []
|
|
435
|
+
};
|
|
436
|
+
try {
|
|
437
|
+
validateDecisionAdapterConditions(normalized);
|
|
438
|
+
}
|
|
439
|
+
catch (error) {
|
|
440
|
+
if (error instanceof DecisionAdapterError) {
|
|
441
|
+
throw new KnowledgeAdapterError('validation_error', error.message);
|
|
442
|
+
}
|
|
443
|
+
throw error;
|
|
444
|
+
}
|
|
445
|
+
return normalized;
|
|
446
|
+
}
|
|
447
|
+
function normalizeSnapshotId(value, field) {
|
|
448
|
+
const snapshotId = normalizeText(value, field, 128);
|
|
449
|
+
if (!/^sha256:[0-9a-f]{64}$/u.test(snapshotId)) {
|
|
450
|
+
throw new KnowledgeAdapterError('validation_error', `${field} must be sha256:<64 lowercase hex>`);
|
|
451
|
+
}
|
|
452
|
+
return snapshotId;
|
|
453
|
+
}
|
|
454
|
+
function normalizeSourceRead(value, requested) {
|
|
455
|
+
if (!value || typeof value !== 'object')
|
|
456
|
+
throw new KnowledgeAdapterError('source_unavailable', 'Knowledge provider returned an invalid result');
|
|
457
|
+
const input = value;
|
|
458
|
+
const sourceStatus = input.source_status;
|
|
459
|
+
if (!KNOWLEDGE_SOURCE_STATUSES.includes(sourceStatus)) {
|
|
460
|
+
throw new KnowledgeAdapterError('source_unavailable', 'Knowledge provider returned an invalid source status');
|
|
461
|
+
}
|
|
462
|
+
const aclStatus = input.acl_status;
|
|
463
|
+
if (!KNOWLEDGE_ACL_STATUSES.includes(aclStatus)) {
|
|
464
|
+
throw new KnowledgeAdapterError('source_unavailable', 'Knowledge provider returned an invalid ACL status');
|
|
465
|
+
}
|
|
466
|
+
const source = input.source === undefined ? undefined : normalizeSourceReference(input.source, 'provider.source');
|
|
467
|
+
if (source && (source.kind !== requested.kind || source.id !== requested.id)) {
|
|
468
|
+
throw new KnowledgeAdapterError('integrity_mismatch', 'Knowledge provider returned a different source identity');
|
|
469
|
+
}
|
|
470
|
+
const rawProvenance = input.provenance;
|
|
471
|
+
if (rawProvenance !== undefined && !Array.isArray(rawProvenance)) {
|
|
472
|
+
throw new KnowledgeAdapterError('source_unavailable', 'Knowledge provider returned invalid provenance');
|
|
473
|
+
}
|
|
474
|
+
const provenance = (rawProvenance ?? []).map((entry, index) => normalizeEvidenceReference(entry, `provider.provenance[${index}]`));
|
|
475
|
+
return {
|
|
476
|
+
source_status: sourceStatus,
|
|
477
|
+
acl_status: aclStatus,
|
|
478
|
+
...(source ? { source } : {}),
|
|
479
|
+
provenance
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
function assertExpectedSourceLocator(requested, resolved) {
|
|
483
|
+
if (requested.revision !== undefined && requested.revision !== resolved.revision) {
|
|
484
|
+
throw new KnowledgeAdapterError('integrity_mismatch', 'Knowledge source revision changed');
|
|
485
|
+
}
|
|
486
|
+
if (requested.digest !== undefined && requested.digest !== resolved.digest) {
|
|
487
|
+
throw new KnowledgeAdapterError('integrity_mismatch', 'Knowledge source digest changed');
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
function hasExactSourceLocator(source) {
|
|
491
|
+
return source.revision !== undefined && source.digest !== undefined;
|
|
492
|
+
}
|
|
493
|
+
function sourceKey(source) {
|
|
494
|
+
return `${source.kind}\u0000${source.id}`;
|
|
495
|
+
}
|
|
496
|
+
function bindingIdFor(source) {
|
|
497
|
+
return `sha256:${createHash('sha256').update(sourceKey(source), 'utf8').digest('hex')}`;
|
|
498
|
+
}
|
|
499
|
+
function assertSnapshotUnchanged(snapshot, current, sidecarContent) {
|
|
500
|
+
if (!sameJson(snapshot.current, current) || snapshot.sidecarContent !== sidecarContent) {
|
|
501
|
+
throw new KnowledgeAdapterError('condition_conflict', 'Canonical aggregate or knowledge sidecar changed while the provider was read');
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
function assertStagedBinding(sidecar, key, expected) {
|
|
505
|
+
const staged = sidecar.bindings[key];
|
|
506
|
+
if (!staged || !sameBindingPayload(staged, expected)) {
|
|
507
|
+
throw new KnowledgeAdapterError('readback_mismatch', `Staged knowledge binding ${key} is incomplete`);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
function sameBindingPayload(left, right) {
|
|
511
|
+
return left.idempotency_key === right.idempotency_key
|
|
512
|
+
&& sameJson(left.source, right.source)
|
|
513
|
+
&& sameJson(left.conditions, right.conditions)
|
|
514
|
+
&& sameJson(left.provenance, right.provenance)
|
|
515
|
+
&& sameJson(left.adoption, right.adoption)
|
|
516
|
+
&& sameJson(left.legacy_adoption, right.legacy_adoption);
|
|
517
|
+
}
|
|
518
|
+
function makeAttachReceipt(binding, sourceRead, adoption, operation) {
|
|
519
|
+
const legacy = binding.legacy_adoption !== undefined;
|
|
520
|
+
return {
|
|
521
|
+
binding_id: binding.binding_id,
|
|
522
|
+
operation,
|
|
523
|
+
source: clone(binding.source),
|
|
524
|
+
resolved_source: clone(binding.source),
|
|
525
|
+
source_status: sourceRead.source_status,
|
|
526
|
+
acl_status: sourceRead.acl_status,
|
|
527
|
+
condition_status: legacy ? 'unavailable' : 'recorded',
|
|
528
|
+
conditions: clone(binding.conditions),
|
|
529
|
+
provenance: clone(binding.provenance),
|
|
530
|
+
adoption_status: legacy ? 'unavailable' : adoption.status,
|
|
531
|
+
...(!legacy && adoption.adoption ? { adoption: clone(adoption.adoption) } : {})
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
function parseSidecar(content) {
|
|
535
|
+
if (content === undefined || content.trim() === '')
|
|
536
|
+
return { version: KNOWLEDGE_ADAPTER_VERSION, bindings: {} };
|
|
537
|
+
let parsed;
|
|
538
|
+
try {
|
|
539
|
+
parsed = JSON.parse(content);
|
|
540
|
+
}
|
|
541
|
+
catch (error) {
|
|
542
|
+
throw new KnowledgeAdapterError('store_corrupt', `Knowledge adapter sidecar is not valid JSON: ${formatError(error)}`);
|
|
543
|
+
}
|
|
544
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
545
|
+
throw new KnowledgeAdapterError('store_corrupt', 'Knowledge adapter sidecar must be an object');
|
|
546
|
+
}
|
|
547
|
+
const value = parsed;
|
|
548
|
+
if ((value.version !== KNOWLEDGE_ADAPTER_VERSION && value.version !== LEGACY_KNOWLEDGE_ADAPTER_VERSION)
|
|
549
|
+
|| !value.bindings || typeof value.bindings !== 'object' || Array.isArray(value.bindings)) {
|
|
550
|
+
throw new KnowledgeAdapterError('store_corrupt', 'Knowledge adapter sidecar has an unsupported schema');
|
|
551
|
+
}
|
|
552
|
+
const bindings = {};
|
|
553
|
+
for (const [key, raw] of Object.entries(value.bindings)) {
|
|
554
|
+
if (value.version === LEGACY_KNOWLEDGE_ADAPTER_VERSION) {
|
|
555
|
+
bindings[key] = migrateLegacyBinding(raw, key);
|
|
556
|
+
}
|
|
557
|
+
else {
|
|
558
|
+
validateStoredBinding(raw, key);
|
|
559
|
+
bindings[key] = clone(raw);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return { version: KNOWLEDGE_ADAPTER_VERSION, bindings };
|
|
563
|
+
}
|
|
564
|
+
function migrateLegacyBinding(value, key) {
|
|
565
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
566
|
+
throw new KnowledgeAdapterError('store_corrupt', `Knowledge adapter binding ${key} is invalid`);
|
|
567
|
+
}
|
|
568
|
+
const raw = value;
|
|
569
|
+
validateLegacyStoredBinding(raw, key);
|
|
570
|
+
const { adoption, ...rest } = raw;
|
|
571
|
+
const migrated = {
|
|
572
|
+
...rest,
|
|
573
|
+
...(adoption === undefined ? {} : { legacy_adoption: clone(adoption) })
|
|
574
|
+
};
|
|
575
|
+
validateStoredBinding(migrated, key);
|
|
576
|
+
return migrated;
|
|
577
|
+
}
|
|
578
|
+
function validateStoredBinding(value, key) {
|
|
579
|
+
if (!value || typeof value !== 'object' || value.binding_id !== bindingIdFor(value.source) || key !== sourceKey(value.source)) {
|
|
580
|
+
throw new KnowledgeAdapterError('store_corrupt', `Knowledge adapter binding ${key} has an invalid identity`);
|
|
581
|
+
}
|
|
582
|
+
normalizeText(value.idempotency_key, `bindings.${key}.idempotency_key`, 512);
|
|
583
|
+
const source = normalizeSourceReference(value.source, `bindings.${key}.source`);
|
|
584
|
+
if (!hasExactSourceLocator(source))
|
|
585
|
+
throw new KnowledgeAdapterError('store_corrupt', `Knowledge adapter binding ${key} has no exact source locator`);
|
|
586
|
+
if (value.source_status_at_attach !== 'present' && value.source_status_at_attach !== 'quarantined') {
|
|
587
|
+
throw new KnowledgeAdapterError('store_corrupt', `Knowledge adapter binding ${key} has an invalid source status`);
|
|
588
|
+
}
|
|
589
|
+
normalizeConditions(value.conditions);
|
|
590
|
+
if (!Array.isArray(value.provenance))
|
|
591
|
+
throw new KnowledgeAdapterError('store_corrupt', `Knowledge adapter binding ${key} has invalid provenance`);
|
|
592
|
+
value.provenance.forEach((entry, index) => normalizeEvidenceReference(entry, `bindings.${key}.provenance[${index}]`));
|
|
593
|
+
if (value.adoption !== undefined && value.legacy_adoption !== undefined) {
|
|
594
|
+
throw new KnowledgeAdapterError('store_corrupt', `Knowledge adapter binding ${key} mixes adoption locator schemas`);
|
|
595
|
+
}
|
|
596
|
+
if (value.adoption !== undefined)
|
|
597
|
+
normalizeAdoptionReference(value.adoption, `bindings.${key}.adoption`);
|
|
598
|
+
if (value.legacy_adoption !== undefined)
|
|
599
|
+
validateLegacyAdoptionReference(value.legacy_adoption, `bindings.${key}.legacy_adoption`);
|
|
600
|
+
normalizeTimestamp(value.attached_at, `bindings.${key}.attached_at`);
|
|
601
|
+
}
|
|
602
|
+
function validateLegacyStoredBinding(value, key) {
|
|
603
|
+
const source = normalizeSourceReference(value.source, `bindings.${key}.source`);
|
|
604
|
+
if (value.binding_id !== bindingIdFor(source) || key !== sourceKey(source)) {
|
|
605
|
+
throw new KnowledgeAdapterError('store_corrupt', `Knowledge adapter binding ${key} has an invalid identity`);
|
|
606
|
+
}
|
|
607
|
+
normalizeText(value.idempotency_key, `bindings.${key}.idempotency_key`, 512);
|
|
608
|
+
if (!hasExactSourceLocator(source))
|
|
609
|
+
throw new KnowledgeAdapterError('store_corrupt', `Knowledge adapter binding ${key} has no exact source locator`);
|
|
610
|
+
if (value.source_status_at_attach !== 'present' && value.source_status_at_attach !== 'quarantined') {
|
|
611
|
+
throw new KnowledgeAdapterError('store_corrupt', `Knowledge adapter binding ${key} has an invalid source status`);
|
|
612
|
+
}
|
|
613
|
+
normalizeConditions(value.conditions);
|
|
614
|
+
if (!Array.isArray(value.provenance))
|
|
615
|
+
throw new KnowledgeAdapterError('store_corrupt', `Knowledge adapter binding ${key} has invalid provenance`);
|
|
616
|
+
value.provenance.forEach((entry, index) => normalizeEvidenceReference(entry, `bindings.${key}.provenance[${index}]`));
|
|
617
|
+
if (value.adoption !== undefined) {
|
|
618
|
+
validateLegacyAdoptionReference(value.adoption, `bindings.${key}.adoption`);
|
|
619
|
+
}
|
|
620
|
+
normalizeTimestamp(value.attached_at, `bindings.${key}.attached_at`);
|
|
621
|
+
}
|
|
622
|
+
function validateLegacyAdoptionReference(value, field) {
|
|
623
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
624
|
+
throw new KnowledgeAdapterError('store_corrupt', `${field} is invalid`);
|
|
625
|
+
}
|
|
626
|
+
const input = value;
|
|
627
|
+
normalizeText(input.id, `${field}.id`, 512);
|
|
628
|
+
normalizeRevision(input.revision, `${field}.revision`);
|
|
629
|
+
normalizeDigest(input.digest, `${field}.digest`);
|
|
630
|
+
}
|
|
631
|
+
function serializeSidecar(sidecar) {
|
|
632
|
+
const bindings = Object.fromEntries(Object.entries(sidecar.bindings).sort(([left], [right]) => left.localeCompare(right, 'en')));
|
|
633
|
+
return `${JSON.stringify({ version: KNOWLEDGE_ADAPTER_VERSION, bindings }, null, 2)}\n`;
|
|
634
|
+
}
|
|
635
|
+
function normalizeRevision(value, field) {
|
|
636
|
+
const revision = normalizeText(value, field, 512);
|
|
637
|
+
if (!/^[1-9][0-9]*$/u.test(revision))
|
|
638
|
+
throw new KnowledgeAdapterError('validation_error', `${field} must be a positive decimal revision`);
|
|
639
|
+
return revision;
|
|
640
|
+
}
|
|
641
|
+
function normalizeDigest(value, field) {
|
|
642
|
+
const digest = normalizeText(value, field, 128);
|
|
643
|
+
if (!/^sha256:[0-9a-f]{64}$/u.test(digest))
|
|
644
|
+
throw new KnowledgeAdapterError('validation_error', `${field} must be sha256:<64 lowercase hex>`);
|
|
645
|
+
return digest;
|
|
646
|
+
}
|
|
647
|
+
function normalizeTimestamp(value, field) {
|
|
648
|
+
const timestamp = normalizeText(value, field, 64);
|
|
649
|
+
if (!Number.isFinite(Date.parse(timestamp)))
|
|
650
|
+
throw new KnowledgeAdapterError('store_corrupt', `${field} must be an ISO timestamp`);
|
|
651
|
+
return timestamp;
|
|
652
|
+
}
|
|
653
|
+
function normalizeText(value, field, max) {
|
|
654
|
+
if (typeof value !== 'string')
|
|
655
|
+
throw new KnowledgeAdapterError('validation_error', `${field} is required`);
|
|
656
|
+
const normalized = value.normalize('NFKC').trim();
|
|
657
|
+
if (!normalized || normalized.length > max || /[\u0000-\u001f\u007f-\u009f]/u.test(normalized)) {
|
|
658
|
+
throw new KnowledgeAdapterError('validation_error', `${field} must be bounded text`);
|
|
659
|
+
}
|
|
660
|
+
return normalized;
|
|
661
|
+
}
|
|
662
|
+
function assertContext(context) {
|
|
663
|
+
normalizeText(context?.principal, 'context.principal', 512);
|
|
664
|
+
}
|
|
665
|
+
function assertSidecarPath(path) {
|
|
666
|
+
if (!path || path.startsWith('/') || path.includes('\\') || path.split('/').some((part) => !part || part === '.' || part === '..')) {
|
|
667
|
+
throw new KnowledgeAdapterError('validation_error', 'sidecarPath must remain relative to the SSOT root');
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
function clone(value) {
|
|
671
|
+
return structuredClone(value);
|
|
672
|
+
}
|
|
673
|
+
function sameJson(left, right) {
|
|
674
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
675
|
+
}
|
|
676
|
+
function formatError(error) {
|
|
677
|
+
return error instanceof Error ? error.message : String(error);
|
|
678
|
+
}
|
|
679
|
+
const KNOWLEDGE_RECORD_KINDS = [
|
|
680
|
+
'knowledge_event',
|
|
681
|
+
'knowledge_feedback',
|
|
682
|
+
'candidate',
|
|
683
|
+
'candidate_promotion',
|
|
684
|
+
'graph_maintenance_plan',
|
|
685
|
+
'human_gate_receipt',
|
|
686
|
+
'graph_maintenance_receipt',
|
|
687
|
+
'meeting_bridge_event',
|
|
688
|
+
'meeting_bridge_candidate'
|
|
689
|
+
];
|
|
690
|
+
const KNOWLEDGE_SOURCE_STATUSES = ['present', 'quarantined', 'retracted', 'not_found', 'denied'];
|
|
691
|
+
const KNOWLEDGE_ACL_STATUSES = ['allowed', 'denied', 'unknown'];
|