@unson/brainbase-mcp 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/dist/canonical-task-contract.d.ts +9 -0
  2. package/dist/canonical-task-contract.js +39 -0
  3. package/dist/canonical-task-principal.d.ts +16 -0
  4. package/dist/canonical-task-principal.js +36 -0
  5. package/dist/canonical-task-service.d.ts +251 -0
  6. package/dist/canonical-task-service.js +809 -0
  7. package/dist/company-os-evaluation.d.ts +193 -0
  8. package/dist/company-os-evaluation.js +1150 -0
  9. package/dist/company-os-impact-review.d.ts +248 -0
  10. package/dist/company-os-impact-review.js +663 -0
  11. package/dist/company-os-learning-adoption.d.ts +288 -0
  12. package/dist/company-os-learning-adoption.js +944 -0
  13. package/dist/company-os-objectives.d.ts +52 -0
  14. package/dist/company-os-objectives.js +197 -0
  15. package/dist/company-os-receipt-adapter.d.ts +109 -0
  16. package/dist/company-os-receipt-adapter.js +636 -0
  17. package/dist/constraint-exception-store.d.ts +23 -0
  18. package/dist/constraint-exception-store.js +108 -0
  19. package/dist/constraint-resolution.d.ts +175 -0
  20. package/dist/constraint-resolution.js +922 -0
  21. package/dist/decision-adapter.d.ts +145 -0
  22. package/dist/decision-adapter.js +586 -0
  23. package/dist/durable-wait-http.d.ts +51 -0
  24. package/dist/durable-wait-http.js +497 -0
  25. package/dist/durable-waits.d.ts +234 -0
  26. package/dist/durable-waits.js +944 -0
  27. package/dist/execution-authority-http.d.ts +28 -0
  28. package/dist/execution-authority-http.js +359 -0
  29. package/dist/execution-authority.d.ts +201 -0
  30. package/dist/execution-authority.js +634 -0
  31. package/dist/foundation-catalog.d.ts +26 -0
  32. package/dist/foundation-catalog.js +206 -0
  33. package/dist/foundation-constraint-store.d.ts +50 -0
  34. package/dist/foundation-constraint-store.js +215 -0
  35. package/dist/foundation-http.d.ts +90 -0
  36. package/dist/foundation-http.js +439 -0
  37. package/dist/foundation-store.d.ts +132 -0
  38. package/dist/foundation-store.js +654 -0
  39. package/dist/judgment-dag-composition.d.ts +224 -0
  40. package/dist/judgment-dag-composition.js +731 -0
  41. package/dist/judgment-dag.d.ts +2 -0
  42. package/dist/judgment-dag.js +1 -0
  43. package/dist/judgment-problem-snapshot.d.ts +185 -0
  44. package/dist/judgment-problem-snapshot.js +777 -0
  45. package/dist/judgment-view-http.d.ts +22 -0
  46. package/dist/judgment-view-http.js +128 -0
  47. package/dist/judgment-view.d.ts +222 -0
  48. package/dist/judgment-view.js +627 -0
  49. package/dist/knowledge-adapter.d.ts +112 -0
  50. package/dist/knowledge-adapter.js +691 -0
  51. package/dist/ontology-foundation.d.ts +209 -0
  52. package/dist/ontology-foundation.js +1000 -0
  53. package/dist/ontology.d.ts +13 -0
  54. package/dist/ontology.js +14 -0
  55. package/dist/organization-connection.d.ts +164 -0
  56. package/dist/organization-connection.js +428 -0
  57. package/dist/personal-knowledge.d.ts +120 -0
  58. package/dist/personal-knowledge.js +750 -0
  59. package/dist/problem-candidates.d.ts +149 -0
  60. package/dist/problem-candidates.js +898 -0
  61. package/dist/problem-selection.d.ts +238 -0
  62. package/dist/problem-selection.js +995 -0
  63. package/dist/resource-reservation-http.d.ts +31 -0
  64. package/dist/resource-reservation-http.js +371 -0
  65. package/dist/resource-reservations.d.ts +221 -0
  66. package/dist/resource-reservations.js +577 -0
  67. package/dist/server.d.ts +97 -0
  68. package/dist/server.js +180 -1
  69. package/dist/ssot.d.ts +6 -1
  70. package/dist/ssot.js +63 -5
  71. package/dist/streamable-http.d.ts +42 -0
  72. package/dist/streamable-http.js +288 -0
  73. package/dist/types.d.ts +20 -0
  74. package/dist/world-model.d.ts +181 -0
  75. package/dist/world-model.js +873 -0
  76. package/package.json +139 -3
  77. package/ui/judgment-view.css +238 -0
  78. package/ui/judgment-view.js +508 -0
  79. package/ui/objective-editor.css +180 -0
  80. package/ui/objective-editor.js +1139 -0
@@ -0,0 +1,439 @@
1
+ import { FoundationStoreError } from './foundation-store.js';
2
+ /**
3
+ * HTTP boundary shared by the OSS Objective UI and organization adapters.
4
+ *
5
+ * This module intentionally exposes a Fetch-style route handler instead of
6
+ * opening a listener. A local host can mount it beside Reservation,
7
+ * Decision, or another Company OS route on one server. Authentication,
8
+ * tenant scope, and CSRF are supplied by that host; none of them are read
9
+ * from a JSON request body.
10
+ */
11
+ export const FOUNDATION_HTTP_CONTRACT_VERSION = 'brainbase.foundation-http.v1';
12
+ export const FOUNDATION_HTTP_PREFIX = '/api/foundation';
13
+ export const FOUNDATION_OBJECTIVE_ROUTE_PREFIX = `${FOUNDATION_HTTP_PREFIX}/objectives`;
14
+ export const FOUNDATION_OBJECTIVE_ROUTES = Object.freeze([
15
+ 'GET /api/foundation/objectives',
16
+ 'GET /api/foundation/objectives/:id',
17
+ 'GET /api/foundation/objectives/:id/readiness',
18
+ 'GET /api/foundation/objectives/:id/constraints',
19
+ 'PUT /api/foundation/objectives/:id/constraints',
20
+ 'POST /api/foundation/objectives',
21
+ 'PUT /api/foundation/objectives/:id',
22
+ 'GET /api/foundation/stories/:id/objectives',
23
+ ]);
24
+ const DEFAULT_BODY_LIMIT_BYTES = 1024 * 1024;
25
+ const MUTATING_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);
26
+ const AUTHORITY_FIELDS = new Set([
27
+ 'acl',
28
+ 'scope',
29
+ 'storage',
30
+ 'provenance',
31
+ 'authorizedUses',
32
+ 'authorized_uses',
33
+ 'ownerId',
34
+ 'owner_id',
35
+ 'userId',
36
+ 'user_id',
37
+ 'memberId',
38
+ 'member_id',
39
+ 'readerIds',
40
+ 'reader_ids',
41
+ 'writerIds',
42
+ 'writer_ids',
43
+ 'tenant',
44
+ 'subjectIds',
45
+ 'subject_ids',
46
+ 'tenantId',
47
+ 'tenant_id',
48
+ 'organization',
49
+ 'organization_id',
50
+ 'organizationId',
51
+ 'org',
52
+ 'orgId',
53
+ 'org_id',
54
+ 'workspace',
55
+ 'workspaceId',
56
+ 'workspace_id',
57
+ 'principal',
58
+ 'actor',
59
+ 'actorId',
60
+ 'actor_id',
61
+ 'role',
62
+ 'roles',
63
+ 'raci',
64
+ 'authorization',
65
+ 'permission',
66
+ 'permissions',
67
+ ]);
68
+ class FoundationHttpError extends Error {
69
+ status;
70
+ code;
71
+ currentRevision;
72
+ constructor(status, code, message, options = {}) {
73
+ super(message);
74
+ this.name = 'FoundationHttpError';
75
+ this.status = status;
76
+ this.code = code;
77
+ this.currentRevision = options.currentRevision;
78
+ }
79
+ }
80
+ /**
81
+ * Compose OSS Company OS routes under one local server boundary. The
82
+ * resolver is called only for a matching route and the resulting trusted
83
+ * context is passed to that route. Unknown routes remain an ordinary 404 so
84
+ * the host can mount this handler beside other app routes.
85
+ */
86
+ export function createFoundationHttpRouter(options) {
87
+ if (!options || typeof options.resolveContext !== 'function') {
88
+ throw new TypeError('resolveContext is required');
89
+ }
90
+ if (!Array.isArray(options.routes) || options.routes.length === 0) {
91
+ throw new TypeError('at least one Foundation HTTP route is required');
92
+ }
93
+ const bodyLimitBytes = options.bodyLimitBytes ?? DEFAULT_BODY_LIMIT_BYTES;
94
+ if (!Number.isInteger(bodyLimitBytes) || bodyLimitBytes <= 0) {
95
+ throw new TypeError('bodyLimitBytes must be a positive integer');
96
+ }
97
+ const routes = Object.freeze([...options.routes]);
98
+ return {
99
+ contractVersion: FOUNDATION_HTTP_CONTRACT_VERSION,
100
+ routes,
101
+ async handle(request) {
102
+ const route = routes.find((candidate) => candidate.matches(request));
103
+ if (!route)
104
+ return jsonResponse(404, { error: { code: 'not_found', message: 'Route not found' } });
105
+ let context;
106
+ try {
107
+ context = await options.resolveContext(request);
108
+ }
109
+ catch {
110
+ context = null;
111
+ }
112
+ if (!context)
113
+ return jsonResponse(401, { error: { code: 'authorization_required', message: 'A trusted context is required' } });
114
+ if (MUTATING_METHODS.has((request.method || 'GET').toUpperCase())) {
115
+ if (!options.csrf) {
116
+ return jsonResponse(501, { error: { code: 'csrf_unconfigured', message: 'A CSRF verifier is required for mutations' } });
117
+ }
118
+ try {
119
+ const verified = await options.csrf.verify(request, context);
120
+ if (verified === false) {
121
+ return jsonResponse(403, { error: { code: 'csrf_failed', message: 'CSRF verification failed' } });
122
+ }
123
+ }
124
+ catch {
125
+ return jsonResponse(403, { error: { code: 'csrf_failed', message: 'CSRF verification failed' } });
126
+ }
127
+ }
128
+ try {
129
+ return await route.handle(request, { context, request });
130
+ }
131
+ catch (error) {
132
+ return errorResponse(error);
133
+ }
134
+ },
135
+ };
136
+ }
137
+ /** Convenience constructor for a local/organization host with Objective routes only. */
138
+ export function createObjectiveFoundationHttpHandler(options) {
139
+ return createFoundationHttpRouter({
140
+ ...options,
141
+ bodyLimitBytes: options.bodyLimitBytes,
142
+ routes: [createObjectiveFoundationRoute(options)],
143
+ });
144
+ }
145
+ export function createObjectiveFoundationRoute(options) {
146
+ if (!options?.objectives)
147
+ throw new TypeError('objectives service is required');
148
+ const bodyLimitBytes = options.bodyLimitBytes ?? DEFAULT_BODY_LIMIT_BYTES;
149
+ if (!Number.isInteger(bodyLimitBytes) || bodyLimitBytes <= 0)
150
+ throw new TypeError('bodyLimitBytes must be a positive integer');
151
+ const route = {
152
+ name: 'foundation-objectives',
153
+ methods: FOUNDATION_OBJECTIVE_ROUTES.map((value) => value.split(' ', 1)[0]),
154
+ paths: FOUNDATION_OBJECTIVE_ROUTES,
155
+ matches(request) {
156
+ const path = requestUrl(request).pathname;
157
+ return path === FOUNDATION_OBJECTIVE_ROUTE_PREFIX
158
+ || path.startsWith(`${FOUNDATION_OBJECTIVE_ROUTE_PREFIX}/`)
159
+ || /^\/api\/foundation\/stories\/[^/]+\/objectives$/.test(path);
160
+ },
161
+ async handle(request, input) {
162
+ const url = requestUrl(request);
163
+ const path = url.pathname;
164
+ const method = (request.method || 'GET').toUpperCase();
165
+ if (path === FOUNDATION_OBJECTIVE_ROUTE_PREFIX) {
166
+ if (method === 'GET') {
167
+ const list = options.objectives.list;
168
+ if (!list)
169
+ throw new FoundationHttpError(501, 'api_unavailable', 'Objective list API is not configured');
170
+ const records = await list.call(options.objectives, 'objective', input.context);
171
+ return jsonResponse(200, { state: records.length ? 'ready' : 'empty', records, absence_confirmed: true });
172
+ }
173
+ if (method === 'POST') {
174
+ const body = await readJsonBody(request, bodyLimitBytes);
175
+ const definition = await buildObjectiveDefinition(options, body, {
176
+ operation: 'create',
177
+ context: input.context,
178
+ });
179
+ const reference = await options.objectives.createObjective(definition, input.context);
180
+ return jsonResponse(201, { state: 'saved_unverified', reference, ref: reference });
181
+ }
182
+ return methodNotAllowed(['GET', 'POST']);
183
+ }
184
+ const storyMatch = path.match(/^\/api\/foundation\/stories\/([^/]+)\/objectives$/);
185
+ if (storyMatch) {
186
+ if (method !== 'GET')
187
+ return methodNotAllowed(['GET']);
188
+ if (!options.listStoryObjectiveLinks)
189
+ throw new FoundationHttpError(501, 'api_unavailable', 'Story relation API is not configured');
190
+ const storyId = decodePathPart(storyMatch[1]);
191
+ const links = await options.listStoryObjectiveLinks(storyId, input.context);
192
+ return jsonResponse(200, normalizeCollectionPayload(links, 'links'));
193
+ }
194
+ const objectiveMatch = path.match(/^\/api\/foundation\/objectives\/([^/]+)(?:\/(readiness|constraints))?$/);
195
+ if (!objectiveMatch)
196
+ throw new FoundationHttpError(404, 'not_found', 'Route not found');
197
+ const id = decodePathPart(objectiveMatch[1]);
198
+ const suffix = objectiveMatch[2] ?? null;
199
+ const revision = positiveRevision(url.searchParams.get('revision') ?? undefined);
200
+ if (suffix === 'readiness') {
201
+ if (method !== 'GET')
202
+ return methodNotAllowed(['GET']);
203
+ const readiness = await options.objectives.checkObjectiveReadiness(id, input.context, revision);
204
+ return jsonResponse(200, readinessPayload(readiness));
205
+ }
206
+ if (suffix === 'constraints') {
207
+ const objectiveReference = await readObjectiveReference(options, id, revision, input.context);
208
+ if (method === 'GET') {
209
+ if (!options.listObjectiveConstraintRefs)
210
+ throw new FoundationHttpError(501, 'api_unavailable', 'Constraint relation API is not configured');
211
+ return jsonResponse(200, normalizeCollectionPayload(await options.listObjectiveConstraintRefs(objectiveReference, input.context), 'refs'));
212
+ }
213
+ if (method === 'PUT') {
214
+ if (!options.replaceObjectiveConstraintRefs)
215
+ throw new FoundationHttpError(501, 'api_unavailable', 'Constraint relation API is not configured');
216
+ const body = await readJsonBody(request, bodyLimitBytes);
217
+ // Validate the raw envelope before extracting `refs` so an
218
+ // authority field cannot hide beside an otherwise valid collection.
219
+ assertNoAuthorityFields(body);
220
+ const refs = parseConstraintReferences(body);
221
+ const result = await options.replaceObjectiveConstraintRefs(objectiveReference, refs, input.context);
222
+ return jsonResponse(200, { state: 'saved_unverified', reference: objectiveReference, refs: result ?? refs });
223
+ }
224
+ return methodNotAllowed(['GET', 'PUT']);
225
+ }
226
+ if (method === 'GET') {
227
+ const record = await options.objectives.readObjective(id, input.context, revision);
228
+ if (!record)
229
+ throw new FoundationHttpError(404, 'not_found', `Objective ${id} was not found`);
230
+ return jsonResponse(200, { state: 'ready', record, objective: record });
231
+ }
232
+ if (method === 'PUT') {
233
+ const current = await options.objectives.readObjective(id, input.context, revision);
234
+ if (!current || current.definition.type !== 'objective')
235
+ throw new FoundationHttpError(404, 'not_found', `Objective ${id} was not found`);
236
+ const body = await readJsonBody(request, bodyLimitBytes);
237
+ const parsed = parseUpdateBody(body, request);
238
+ const definition = await buildObjectiveDefinition(options, parsed.body, {
239
+ operation: 'update',
240
+ context: input.context,
241
+ current: current.definition,
242
+ expectedRevision: parsed.expectedRevision,
243
+ });
244
+ const reference = await options.objectives.updateObjective(id, parsed.expectedRevision, definition, input.context);
245
+ return jsonResponse(200, { state: 'saved_unverified', reference, ref: reference });
246
+ }
247
+ return methodNotAllowed(['GET', 'PUT']);
248
+ },
249
+ };
250
+ return route;
251
+ }
252
+ async function readObjectiveReference(options, id, revision, context) {
253
+ const record = await options.objectives.readObjective(id, context, revision);
254
+ if (!record || record.definition.type !== 'objective')
255
+ throw new FoundationHttpError(404, 'not_found', `Objective ${id} was not found`);
256
+ return {
257
+ id: record.definition.id,
258
+ type: 'objective',
259
+ revision: record.definition.revision,
260
+ digest: record.digest,
261
+ };
262
+ }
263
+ async function buildObjectiveDefinition(options, payload, input) {
264
+ if (!options.buildDefinition)
265
+ throw new FoundationHttpError(501, 'api_unavailable', 'Objective definition builder is not configured');
266
+ const body = sanitizeMutationBody(payload);
267
+ const definition = await options.buildDefinition({ ...input, body });
268
+ if (!definition || definition.type !== 'objective') {
269
+ throw new FoundationHttpError(400, 'invalid_input', 'Objective builder returned a non-Objective definition');
270
+ }
271
+ if (!definition.id || !definition.revision)
272
+ throw new FoundationHttpError(400, 'invalid_input', 'Objective definition identity is incomplete');
273
+ if (input.operation === 'update') {
274
+ if (!input.current || definition.id !== input.current.id || definition.type !== input.current.type) {
275
+ throw new FoundationHttpError(400, 'invalid_input', 'An Objective update cannot change its logical identity');
276
+ }
277
+ if (JSON.stringify(definition.acl) !== JSON.stringify(input.current.acl)
278
+ || JSON.stringify(definition.scope) !== JSON.stringify(input.current.scope)
279
+ || JSON.stringify(definition.storage) !== JSON.stringify(input.current.storage)
280
+ || JSON.stringify(definition.provenance) !== JSON.stringify(input.current.provenance)
281
+ || JSON.stringify(definition.authorizedUses) !== JSON.stringify(input.current.authorizedUses)) {
282
+ throw new FoundationHttpError(403, 'authority_field_change', 'Objective authority and provenance fields are host-controlled');
283
+ }
284
+ }
285
+ return definition;
286
+ }
287
+ function parseUpdateBody(payload, request) {
288
+ // A PUT may carry a CAS envelope (`{ expectedRevision, definition }`).
289
+ // Check the envelope itself before unwrapping it; otherwise an authority
290
+ // field beside `definition` would be silently ignored by the builder.
291
+ assertNoAuthorityFields(payload);
292
+ const object = asRecord(payload);
293
+ const expectedRevision = positiveRevision(firstString(object, 'expectedRevision', 'expected_revision')
294
+ ?? request.headers.get('if-match')?.replace(/^W\//, '').replace(/^"|"$/g, ''));
295
+ if (!expectedRevision)
296
+ throw new FoundationHttpError(400, 'invalid_input', 'expectedRevision or If-Match is required for a CAS update');
297
+ return { body: object?.definition ?? payload, expectedRevision };
298
+ }
299
+ function sanitizeMutationBody(payload) {
300
+ const object = asRecord(payload);
301
+ if (!object)
302
+ throw new FoundationHttpError(400, 'invalid_input', 'JSON object body is required');
303
+ assertNoAuthorityFields(object);
304
+ const definition = { ...object };
305
+ delete definition.revision;
306
+ delete definition.expectedRevision;
307
+ delete definition.expected_revision;
308
+ if (definition.type !== undefined && definition.type !== 'objective') {
309
+ throw new FoundationHttpError(400, 'invalid_input', 'Objective definition type must be objective');
310
+ }
311
+ return Object.freeze(definition);
312
+ }
313
+ function assertNoAuthorityFields(payload) {
314
+ const object = asRecord(payload);
315
+ if (!object)
316
+ return;
317
+ for (const key of Object.keys(object)) {
318
+ if (AUTHORITY_FIELDS.has(key)) {
319
+ throw new FoundationHttpError(400, 'authority_field_in_body', `Request body cannot set ${key}`);
320
+ }
321
+ }
322
+ }
323
+ function parseConstraintReferences(payload) {
324
+ const object = asRecord(payload);
325
+ const values = Array.isArray(payload) ? payload : object?.refs ?? object?.references;
326
+ if (!Array.isArray(values))
327
+ throw new FoundationHttpError(400, 'invalid_input', 'refs must be an array');
328
+ return values.map((value, index) => {
329
+ const ref = asRecord(value);
330
+ if (!ref || ref.type !== 'constraint' || typeof ref.id !== 'string' || !positiveRevision(ref.revision)) {
331
+ throw new FoundationHttpError(400, 'invalid_input', `refs[${index}] must be a Constraint revision reference`);
332
+ }
333
+ return { id: ref.id, type: 'constraint', revision: ref.revision };
334
+ });
335
+ }
336
+ function readinessPayload(readiness) {
337
+ return { state: 'ready', ready: readiness.ready, issues: readiness.issues };
338
+ }
339
+ function normalizeCollectionPayload(value, key) {
340
+ if (Array.isArray(value))
341
+ return { state: value.length ? 'ready' : 'empty', [key]: value, absence_confirmed: true };
342
+ const object = asRecord(value);
343
+ if (object && Object.prototype.hasOwnProperty.call(object, key) && Array.isArray(object[key])) {
344
+ return {
345
+ ...object,
346
+ [key]: object[key],
347
+ // A collection without an explicit absence claim is not a confirmed
348
+ // empty result. Preserve false/unknown instead of inventing true.
349
+ absence_confirmed: object.absence_confirmed === true,
350
+ };
351
+ }
352
+ throw new FoundationHttpError(500, 'readback_mismatch', `The ${key} adapter returned an invalid collection`);
353
+ }
354
+ function methodNotAllowed(allow) {
355
+ return jsonResponse(405, { error: { code: 'method_not_allowed', message: 'Method not allowed' } }, { Allow: allow.join(', ') });
356
+ }
357
+ function errorResponse(error) {
358
+ const normalized = normalizeHttpError(error);
359
+ const body = {
360
+ error: {
361
+ code: normalized.code,
362
+ message: normalized.message,
363
+ ...(normalized.currentRevision ? { currentRevision: normalized.currentRevision } : {}),
364
+ },
365
+ };
366
+ return jsonResponse(normalized.status, body);
367
+ }
368
+ function normalizeHttpError(error) {
369
+ if (error instanceof FoundationHttpError)
370
+ return error;
371
+ if (error instanceof FoundationStoreError) {
372
+ const status = error.code === 'authorization_denied' || error.code === 'scope_violation' ? 403
373
+ : error.code === 'not_found' ? 404
374
+ : error.code === 'revision_conflict' ? 409
375
+ : error.code === 'unsupported_graph' ? 503
376
+ : error.code === 'readback_mismatch' ? 500
377
+ : 400;
378
+ return new FoundationHttpError(status, error.code, error.message, { currentRevision: error.currentRevision });
379
+ }
380
+ if (error instanceof Error && error.name === 'FoundationHttpError') {
381
+ return new FoundationHttpError(500, 'internal_error', error.message);
382
+ }
383
+ return new FoundationHttpError(500, 'internal_error', 'Foundation request failed');
384
+ }
385
+ function jsonResponse(status, body, headers = {}) {
386
+ return new Response(JSON.stringify(body), {
387
+ status,
388
+ headers: {
389
+ 'Content-Type': 'application/json; charset=utf-8',
390
+ ...headers,
391
+ },
392
+ });
393
+ }
394
+ async function readJsonBody(request, limitBytes) {
395
+ const contentLength = Number(request.headers.get('content-length'));
396
+ if (Number.isFinite(contentLength) && contentLength > limitBytes) {
397
+ throw new FoundationHttpError(413, 'body_too_large', 'Request body exceeds the configured limit');
398
+ }
399
+ const text = await request.text();
400
+ if (!text)
401
+ throw new FoundationHttpError(400, 'invalid_json', 'A JSON body is required');
402
+ if (new TextEncoder().encode(text).byteLength > limitBytes) {
403
+ throw new FoundationHttpError(413, 'body_too_large', 'Request body exceeds the configured limit');
404
+ }
405
+ try {
406
+ return JSON.parse(text);
407
+ }
408
+ catch {
409
+ throw new FoundationHttpError(400, 'invalid_json', 'Request body is not valid JSON');
410
+ }
411
+ }
412
+ function requestUrl(request) {
413
+ return new URL(request.url, 'http://localhost');
414
+ }
415
+ function decodePathPart(value) {
416
+ try {
417
+ const decoded = decodeURIComponent(value);
418
+ if (!decoded)
419
+ throw new Error('empty');
420
+ return decoded;
421
+ }
422
+ catch {
423
+ throw new FoundationHttpError(400, 'invalid_input', 'Path identifier is not valid');
424
+ }
425
+ }
426
+ function positiveRevision(value) {
427
+ return typeof value === 'string' && /^[1-9]\d*$/.test(value) ? value : undefined;
428
+ }
429
+ function asRecord(value) {
430
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
431
+ }
432
+ function firstString(value, ...keys) {
433
+ if (!value)
434
+ return undefined;
435
+ for (const key of keys)
436
+ if (typeof value[key] === 'string')
437
+ return value[key];
438
+ return undefined;
439
+ }
@@ -0,0 +1,132 @@
1
+ import type { FoundationDefinition, FoundationAcl, FoundationRelationEndpoint, FoundationRelationReference, FoundationRevision, FoundationScope, FoundationType } from './ontology-foundation.js';
2
+ import type { FoundationCatalogRecord, PersonalOs } from './types.js';
3
+ import type { FoundationRef } from './foundation-catalog.js';
4
+ export interface FoundationStoreContext {
5
+ /** Identity resolved by the caller's trusted auth boundary. */
6
+ principal: string;
7
+ /**
8
+ * Optional trusted subject scope. The request body never supplies this
9
+ * value; organization adapters may inject it for the default policy.
10
+ */
11
+ scope?: FoundationScope;
12
+ }
13
+ export type FoundationStoreAction = 'create' | 'read' | 'update' | 'list' | 'link';
14
+ /**
15
+ * Metadata returned for a non-foundation relation endpoint (for example a
16
+ * Story). It deliberately contains no Story body or private projection.
17
+ * `acl` and `scope` are the current values used for authorization even when
18
+ * `revision` points at an older immutable revision.
19
+ */
20
+ export interface FoundationEndpointResource {
21
+ id: string;
22
+ type: FoundationRelationEndpoint;
23
+ revision: string;
24
+ currentRevision: string;
25
+ acl: FoundationAcl;
26
+ scope: FoundationScope;
27
+ }
28
+ export type FoundationAuthorizationResource = FoundationDefinition | FoundationEndpointResource;
29
+ export interface FoundationEndpointResolverRequest {
30
+ endpoint: {
31
+ id: string;
32
+ type: FoundationRelationEndpoint;
33
+ revision?: string;
34
+ };
35
+ context: FoundationStoreContext;
36
+ /** The current aggregate loaded while the canonical SSOT lock is held. */
37
+ current: PersonalOs;
38
+ }
39
+ /**
40
+ * Trusted adapter for endpoint types that are not stored by this foundation
41
+ * catalog. A resolver is called only inside the canonical mutation lock and
42
+ * must return authorization metadata, never a private endpoint document.
43
+ */
44
+ export interface FoundationEndpointResolver {
45
+ resolve(request: FoundationEndpointResolverRequest): FoundationEndpointResource | null | Promise<FoundationEndpointResource | null>;
46
+ }
47
+ /**
48
+ * Minimal metadata record used by the OSS standalone Story resolver. This is
49
+ * an adapter input, not a second Story store: Story content and lifecycle stay
50
+ * owned by the consuming Story provider.
51
+ */
52
+ export interface LocalStoryRevision {
53
+ id: string;
54
+ revision: string;
55
+ acl: FoundationAcl;
56
+ scope: FoundationScope;
57
+ }
58
+ export interface FoundationAuthorizationRequest {
59
+ action: FoundationStoreAction;
60
+ context: FoundationStoreContext;
61
+ reference?: FoundationRevision;
62
+ current?: FoundationDefinition;
63
+ next?: FoundationDefinition;
64
+ /**
65
+ * Foundation records resolved from relation endpoints while the canonical
66
+ * SSOT lock is held. A link must never be authorized from an ID alone.
67
+ */
68
+ resources?: readonly FoundationAuthorizationResource[];
69
+ relation?: FoundationRelationReference;
70
+ }
71
+ export interface FoundationStorePolicy {
72
+ authorize(request: FoundationAuthorizationRequest): void | boolean | Promise<void | boolean>;
73
+ }
74
+ export interface FoundationUpdateInput {
75
+ reference: FoundationRevision;
76
+ next: FoundationDefinition;
77
+ /** Optional duplicate check for callers that carry an explicit CAS field. */
78
+ expectedRevision?: string;
79
+ }
80
+ export interface FoundationRevisionStore {
81
+ create(definition: FoundationDefinition, context: FoundationStoreContext): Promise<FoundationRef>;
82
+ read(reference: FoundationRevision, context: FoundationStoreContext): Promise<FoundationCatalogRecord | null>;
83
+ readLatest(type: FoundationType, id: string, context: FoundationStoreContext): Promise<FoundationCatalogRecord | null>;
84
+ update(input: FoundationUpdateInput, context: FoundationStoreContext): Promise<FoundationRef>;
85
+ list(type: FoundationType | undefined, context: FoundationStoreContext): Promise<FoundationCatalogRecord[]>;
86
+ addRelation(relation: FoundationRelationReference, context: FoundationStoreContext): Promise<void>;
87
+ }
88
+ export type FoundationStoreErrorCode = 'invalid_input' | 'not_found' | 'revision_conflict' | 'authorization_denied' | 'scope_violation' | 'unsupported_graph' | 'corrupt_catalog' | 'readback_mismatch';
89
+ export declare class FoundationStoreError extends Error {
90
+ readonly code: FoundationStoreErrorCode;
91
+ readonly currentRevision?: string;
92
+ constructor(code: FoundationStoreErrorCode, message: string, options?: {
93
+ currentRevision?: string;
94
+ });
95
+ }
96
+ export interface FoundationRevisionStoreOptions {
97
+ dataDir: string;
98
+ policy?: FoundationStorePolicy;
99
+ /** Trusted resolver for Story and other external relation endpoints. */
100
+ endpointResolver?: FoundationEndpointResolver;
101
+ }
102
+ /**
103
+ * Canonical foundation persistence over the existing Graph SSOT aggregate.
104
+ * There is intentionally no separate objectives/world-model file or writer.
105
+ */
106
+ export declare class GraphFoundationRevisionStore implements FoundationRevisionStore {
107
+ private readonly dataDir;
108
+ private readonly policy?;
109
+ private readonly endpointResolver?;
110
+ constructor(options: FoundationRevisionStoreOptions);
111
+ create(definition: FoundationDefinition, context: FoundationStoreContext): Promise<FoundationRef>;
112
+ read(reference: FoundationRevision, context: FoundationStoreContext): Promise<FoundationCatalogRecord | null>;
113
+ readLatest(type: FoundationType, id: string, context: FoundationStoreContext): Promise<FoundationCatalogRecord | null>;
114
+ update(input: FoundationUpdateInput, context: FoundationStoreContext): Promise<FoundationRef>;
115
+ list(type: FoundationType | undefined, context: FoundationStoreContext): Promise<FoundationCatalogRecord[]>;
116
+ addRelation(relation: FoundationRelationReference, context: FoundationStoreContext): Promise<void>;
117
+ private load;
118
+ private authorize;
119
+ private readback;
120
+ }
121
+ export declare function createFoundationRevisionStore(options: FoundationRevisionStoreOptions): FoundationRevisionStore;
122
+ export declare const createFoundationStore: typeof createFoundationRevisionStore;
123
+ /**
124
+ * Creates a concrete, in-memory Story endpoint adapter for OSS consumers.
125
+ * Only immutable revision/security metadata is supplied; no Story body is
126
+ * duplicated into the foundation catalog. Current ACL and scope are taken
127
+ * from the greatest registered revision while the requested revision is only
128
+ * checked for existence and retained in the relation.
129
+ */
130
+ export declare function createLocalStoryResolver(records: readonly LocalStoryRevision[]): FoundationEndpointResolver;
131
+ /** Descriptive alias for consumers that use the generic endpoint terminology. */
132
+ export declare const createStoryEndpointResolver: typeof createLocalStoryResolver;